# シーン定義仕様 (Scene Definition Schema)

## 概要 (Overview)

シミュレーション 1 回分の入力を 1 つの YAML ファイルにまとめる．光源・材料・ジオメトリ・センサ・評価面・レンダ設定を含む．`scene/builder` がこの YAML を Mitsuba 3 の scene dict に変換する．

## トップレベル構造 (Top-level Schema)

```yaml
name: <string>            # 必須．設計識別名（出力ディレクトリ名に使う）
description: <string>     # 任意．自由記述

geometry: { ... }         # 必須．メッシュ一覧
materials: { ... }        # 必須．名前付き BSDF 辞書
lights: [ ... ]           # 必須．光源リスト（1 個以上）
sensor: { ... }           # 必須．カメラ 1 個（複数センサは未対応）
render: { ... }           # 必須．レンダリングパラメータ
evaluation: { ... }       # 任意．評価設定（省略時は CLI で別 YAML 指定）
```

## 各セクション

### geometry

```yaml
geometry:
  meshes:
    - id: housing                     # シーン内ユニーク ID
      path: configs/meshes/housing.stl
      unit: mm                        # mm | m
      material: housing_inner         # materials の key を参照
    - id: diffuser
      path: configs/meshes/diffuser.stl
      unit: mm
      material: diffuser_pmma
    - id: whiteboard
      path: configs/meshes/whiteboard.stl
      unit: mm
      material: white_lambert
```

- `path` はプロジェクトルートからの相対パス
- 同一 STL を異なる材料で複数回参照したい場合は `id` を変えて別エントリにする

### materials

```yaml
materials:
  housing_inner:
    type: diffuse
    reflectance: [0.05, 0.05, 0.05]   # マットな筐体内壁

  white_lambert:
    type: diffuse
    reflectance: 0.95                 # スカラ可．RGB 同値の近似白

  diffuser_pmma:
    type: thindielectric              # 薄膜誘電体（半透明散乱の簡易モデル）
    int_ior: 1.49                     # PMMA 屈折率
    ext_ior: 1.0
```

**面の向きの扱い**: 不透明反射材（`diffuse` / `roughconductor` / 不透明のみで構成される
`blendbsdf`）はシーン登録時に自動的に Mitsuba の `twosided` で包まれる．Mitsuba の
これらの BSDF は片面であり，法線が裏返ったメッシュ面は入射光を一切反射せず
**エラーも警告もなく真っ黒**になるため（[TEST_13](../06_TEST/TEST_13_物理モデル健全性の一括監査.md)）．
法線が正しい場合の結果は完全に同一で，YAML 側の記述は不要．
透過を伴う `dielectric` / `thindielectric` は両面挙動が本質なのでラップしない．

サポート済みの `type`：

| type | Mitsuba 対応 BSDF | 用途 | 主要パラメータ |
| --- | --- | --- | --- |
| `diffuse` | `diffuse` | Lambertian 反射 | `reflectance: [r, g, b]` (RGB) or スカラ |
| `roughconductor` | `roughconductor` | GGX マイクロファセット鏡面（感度分析用） | `alpha: 0.0〜1.0` (粗さ), `distribution: ggx`/`beckmann`．`material="none"` 固定で eta=k=1 の完全反射体相当 |
| `blendbsdf` | `blendbsdf` | 2 BSDF の線形補間（混合材料モデル） | `weight: 0.0〜1.0` (components[1] の比率), `components: [BSDF, BSDF]` (2 要素必須，再帰ネスト可能) |
| `dielectric` | `dielectric` | 誘電体（厚みあり） | `int_ior` (内側屈折率), `ext_ior` (外側屈折率) |
| `thindielectric` | `thindielectric` | 薄膜誘電体（ディフューザー薄板） | `int_ior` (内側屈折率), `ext_ior` (外側屈折率) |

サポート予定の `type`：

| type | Mitsuba 対応 BSDF | 用途 |
| --- | --- | --- |
| `mask` | `mask` | 透過率マスク |

`blendbsdf` + `roughconductor` の例（Step 8-14 候補 β で導入）:

```yaml
materials:
  printer_white_with_specular:
    type: blendbsdf
    weight: 0.15                       # specular 成分の比率 (0=純 diffuse, 1=純 specular)
    components:
      - type: diffuse
        reflectance: [0.85, 0.85, 0.85]
      - type: roughconductor
        alpha: 0.3                     # GGX 粗さ (0=完全鏡面, 1=拡散的鏡面)
        distribution: ggx
```

### lights

```yaml
lights:
  - id: led_top
    type: point                      # point | area | directional | spot
    position: [0, 30, 10]            # mm 単位
    intensity: [1.0, 1.0, 1.0]       # RGB．スカラも可
    profile: lambertian              # lambertian | isotropic

  - id: led_ring_segment
    type: area
    shape: rectangle                 # rectangle | disk
    center: [0, 0, 20]
    size: [10, 10]                   # mm（shape=rectangle で必須）
    direction: [0, 0, -1]
    radiance: [50, 50, 50]

  - id: led_disk                     # 円形面光源（軸対称の理想化光源等，TEST_11 で追加）
    type: area
    shape: disk
    center: [0, 0, -142]
    radius: 1.766                    # mm（shape=disk で必須，正値）
    direction: [0, 0, -1]
    radiance: [50, 50, 50]

  - id: led_spot                     # 指向性 LED（データシート配光角に合わせる）
    type: spot
    position: [0, 30, 10]            # mm
    direction: [0, 0, -1]            # 光軸方向ベクトル
    cutoff_angle: 40                 # 光が完全に 0 になる角度 [deg]（0 < cutoff ≤ 90）
    beam_width: 20                   # 中央フラット領域の半角 [deg]（0 ≤ beam_width ≤ cutoff_angle）
    intensity: [1.0, 1.0, 1.0]       # 軸上強度 I_0 [W/sr]．スカラも可
```

サポート済みの `type`：

| type | Mitsuba 対応 emitter | 用途 | 主要パラメータ |
| --- | --- | --- | --- |
| `point` | `point` | 点光源（LED 単体モデル等） | `position`, `intensity`, `profile` |
| `area` | `area`（rectangle / disk 形状） | 面光源（矩形または円板．`shape` に応じて `size` / `radius` が必須） | `center`, `size` または `radius`, `direction`, `radiance` |
| `directional` | `directional` | 平行光源（無限遠光源） | `direction`, `irradiance` |
| `spot` | `spot` | スポット光源（円錐配光・LED 指向性モデル，Step 8-10 で追加） | `position`, `direction`, `cutoff_angle`, `beam_width`, `intensity` |

`spot` は `cutoff_angle`（光が 0 になる角度）と `beam_width`（中央フラット領域）の組み合わせで smoothstep 補間の配光を定義し，LED の指向性をデータシートの配光角に合わせて表現する用途で使う．`profile: ies`（IES 配光ファイル）は将来拡張．

### sensor

`type` は `perspective`（通常カメラ）または `orthographic`（平行投影）から選ぶ．

```yaml
# パターン 1: perspective（通常カメラ）— fov_deg 必須
sensor:
  type: perspective
  position: [0, 0, 50]               # mm
  target: [0, 0, 0]
  up: [0, 1, 0]
  fov_deg: 60                        # x 方向 FOV [deg]
  film:
    width: 1920
    height: 1920
    rfilter: gaussian                # ピクセルフィルタ

# パターン 2: orthographic（平行投影）— ortho_scale 必須
sensor:
  type: orthographic
  position: [0, 0, -50]              # mm（撮影面に対する距離）
  target: [0, 0, 0]                  # 撮影面の中心
  up: [0, 1, 0]
  ortho_scale: [50, 52]              # 撮影範囲の半幅 (sx, sy) [mm]．画像 X 軸 ±sx, Y 軸 ±sy にマップ
  film:
    width: 512
    height: 512
    rfilter: gaussian
```

`orthographic` は **評価板の表面上の照度マップを 2D 画像として直接取得** する用途で使う．
pixel と評価板の物理 mm 座標が 1 対 1 で対応し、パースペクティブ歪みがない．
詳細は [SPEC_03_基準設計の物理構成](SPEC_03_基準設計の物理構成.md) のカメラ（センサ）セクションを参照．

### render

```yaml
render:
  variant: llvm_ad_rgb               # 省略時は llvm_ad_rgb
  integrator: path                   # path | direct | volpath | ptracer
  max_depth: 8
  spp: 256                           # サンプル数
  seed: 0                            # 乱数シード（省略時 0・0 以上）
```

`integrator` の `ptracer` はフォワード光線追跡（光源→センサ）の粒子トレーサー．
`seed` は `mi.render` に渡す乱数シード．同じ seed のまま再レンダしても集計指標は実質同じ値になるため，
ノイズフロアを測る再レンダでは seed を変える（ENV_04「研究室サーバでの実行」参照）．

### evaluation（任意）

```yaml
evaluation:
  roi:
    type: image_pixel                # image_pixel | scene_face
    x: 539
    y: 1015
    width: 1400
    height: 1409
  zones:
    enabled: true
    boundaries: [0.33, 0.66]         # 中心d<0.33 / 中間 / 周辺d≥0.66
  metrics:
    cov: true
    std: true
    max_min: true
    radial_profile: true
    radial_min_max: false            # 動径 min/max 比の 4 列を metrics.csv に追加（既定 false）
    shape_metrics: false             # 形状指標（輝度重心 / 角度-放射分解 / PCA 主軸）を追加（既定 false）
    shape_reference: null            # SSIM / NCC 算出用の参照画像パス（.npy / .png）．shape_metrics=true 時のみ有効
  oetf:                              # 任意．省略時 OETF 無効（linear のまま評価＝後方互換）
    enabled: true                    # シミュ linear 出力を実測と同じ sRGB 空間へ変換
    transfer: srgb                   # srgb のみ（IEC 61966-2-1）
    exposure:
      mode: anchor_mean              # OETF 後 ROI mean を target_mean に対数二分で合わせる（実測 AE アンカー）
      target_mean: 146.7146          # 実測 ROI mean（uint8 [0,255] スケール）
    quantize_uint8: true             # OETF 後を実測 PNG 同様 uint8 量子化
  normalization:                     # 任意．省略時 normalization 無効（後方互換）．OETF と排他
    enabled: true
    type: mean_anchor                # mean_anchor のみ（将来 max_anchor / range_anchor 等を拡張予定）
    target_mean: 146.7146            # ROI mean を target_mean に線形ゲインで揃える（uint8 スケール想定）
```

省略した場合は `sm-evaluate` の `--config` で別ファイルを指定する．

#### oetf（任意・後方互換）

シミュは linear radiance、実測 PNG は ISP の sRGB ガンマ符号化値であり、無次元指標（CoV/勾配/比）は非線形変換下で不変でないため、**シミュ vs 実測の数値突合には OETF 順方向適用が必須**（詳細は [SPEC_04「OETF 順方向適用の仕様」](SPEC_04_評価仕様.md)）．`oetf.enabled=true` のとき `sm-evaluate` は ROI 抽出後・指標算出前に標準 sRGB OETF を適用する．露出は AE が制御する量＝実測 ROI mean にアンカー（線形ゲインなので形状指標に自由度を持ち込まない）．`oetf` キー省略時は `enabled=False` 扱いで従来どおり linear 評価．

**重要（非対称性）**: OETF はシミュ出力にのみ適用する．実測 PNG は既に sRGB 符号化済みのため、実測用評価設定（`configs/eval_real.yaml` 等）には `oetf` を**付けない**（二重適用は誤り）．

#### normalization（任意・linear モード用）

linear ドメインでの評価結果について **mean を target_mean に揃える線形ゲイン正規化** を行う．OETF の露出アンカーと同じ思想を，OETF を介さない linear 評価モードに対称展開したもの．線形ゲインなので **形状指標 (CoV / 勾配 / C/P / max-min 比) は厳密に不変**で，絶対値スケールだけが揃う．主用途は「同じ ROI に対し設定を変えた複数シミュレーション結果の形を視覚的・数値的に公平比較する」こと（[TEST_03 輝度マップ形状追跡](../06_TEST/TEST_03_輝度マップ形状追跡.md) 参照）．

**排他制御**: `oetf.enabled=true` と `normalization.enabled=true` の同時指定は **ValidationError**（OETF の露出ソルバが既に mean アンカーを行うため二重適用になる）．`oetf` を使う場合は `normalization` を省略 or `enabled: false` にする．

`type` は現状 `mean_anchor` のみ．将来 `max_anchor` / `range_anchor` 等を追加する余地のための Literal 設計．

## バリデーション

`scene/schema.py` で以下をチェック（Pydantic 想定）：

- 必須キー欠落
- ID の重複
- 参照整合性（meshes が存在しない materials を参照していないか）
- 単位指定の妥当性
- センサ FOV / 解像度の現実性（極端値の警告）

## 設計の意図

- **テキスト 1 ファイルで 1 設計**：履歴管理（git diff）で設計変更が見える
- **YAML を表面に，Mitsuba XML を裏に**：Mitsuba の柔軟性を保ちつつ，編集面では構造化された読みやすさを優先
- **mm を既定**：Fusion 360 と一致させ，変換ミスを防ぐ
- **評価設定をシーンと同居も別出しも可**：シーン固定で評価条件だけ振りたい場合に便利

## 将来拡張

- スペクトル光源（`llvm_ad_spectral` 用）の RGB → スペクトル変換層
- IES 配光ファイル対応
- パラメータスイープ（YAML 1 つから複数バリアント生成）
- マルチセンサ対応
