# 開発コマンド (Development Commands)

日常の開発で使うコマンドをまとめる．人間とエージェント（tester・coder，`/auto-refactor`・`/auto-audit`）がコマンドを推測せずに済むよう，本ファイルを唯一の参照先とする．新しいコマンドが確定・変更されたら本ファイルに反映する．

## 前提 (Prerequisites)

- パッケージ管理・実行は **uv** に統一する．コマンドは原則 `uv run <...>` で実行する．
- 依存の同期: `uv sync`（`dev` グループを含めて全依存をインストール）
- Python は `>=3.12`（`pyproject.toml` の `requires-python`）．

## シミュレーション・評価 (CLI)

`pyproject.toml` の `[project.scripts]` で定義されたエントリポイント．

| コマンド | 役割 | 実装 |
| --- | --- | --- |
| `uv run sm-render <scene.yaml>` | シーン YAML をレンダリングし PNG / luminance.npy を出力（`--variant` で Mitsuba variant を上書き可） | `cli/render.py` |
| `uv run sm-evaluate <入力> <config.yaml>` | PNG / luminance.npy を評価し metrics.csv + figures/ を出力（`--no-figures` で図を省き指標だけ出す．描画分 約 2 秒が省ける） | `cli/evaluate.py`（本体は `evaluation/pipeline.py:evaluate_image`） |
| `uv run sm-visualize <scene.yaml>` | シーンを 3D 表示 / スクリーンショット出力 | `cli/visualize.py` |
| `uv run sm-remote <サブコマンド>` | 研究室サーバでの計算（コード送信・tmux でのジョブ起動・状態確認・結果取得）．下記「研究室サーバでの実行」参照 | `cli/remote.py` |

各コマンドの詳細オプションは `--help` で確認する（例: `uv run sm-visualize --help`）．

## テスト (Tests)

テスト方針の詳細は `.claude/rules/testing.md` を参照．

```bash
uv run pytest                 # 通常実行
uv run pytest -m "not slow"   # 重い（レンダリングを伴う）テストを除外
uv run pytest tests/evaluation/test_roi.py   # 個別ファイル
```

- `slow` マーカーは `pyproject.toml` の `[tool.pytest.ini_options]` に登録済み．

物理モデルの健全性検査（積分球の解析解一致・法線監査・面光源の軸マッピング）は
**通常実行に含まれる**（合計 2 秒程度）．`slow` に隔離するとレンダ経路が壊れても
気づかないため（[TEST_13](../06_TEST/TEST_13_物理モデル健全性の一括監査.md)）．

```bash
uv run pytest -m ""    # slow を含む全テスト．シーン幾何を変えた後は一度通す
```

> ⚠ `vtk` は `pyproject.toml` で **9.4.2 に固定**している．9.6.1 では Windows の
> Smart App Control が 14 個の DLL をレピュテーション判定でブロックし，`sm-visualize` が
> 起動できなくなる（[TEST_13](../06_TEST/TEST_13_物理モデル健全性の一括監査.md) 検査 8）．
> バージョンを上げるときは必ず `sm-visualize` の起動確認を行うこと．
> `tests/visualize/test_vtk_import_guard.py` が通常実行で import 可否を常時検査している．

> ⚠ `mitsuba` は `pyproject.toml` で **3.7.1 に固定**している．3.8.0 では Smart App Control が
> 同梱の `IlmThread-mitsuba.dll` 1 個をブロックし mitsuba が import 不能＝レンダが全滅する
> （2026-09-01 実測．2 週間動いた後にクラウド側の判定だけが反転した．
> [TEST_16](../06_TEST/TEST_16_立方体キャビティ導入と球との比較.md) 失敗パターン参照）．
> バージョンを上げるときは import 確認と `uv run python -m pytest -m ""`（絶対放射量検証込み）を
> 必ず通し，過去レンダとの比較が続く実験中ならアンカー再レンダで等価性を確認すること．
> なお `pytest.exe` ランチャも同時にブロックされたため，テスト実行は `uv run python -m pytest` を使う．

> ⚠ 2026-09-13，開発機の Smart App Control が**評価モードからオンに切り替わり**，mitsuba 3.7.1 の
> `mitsuba_ext.cp312-win_amd64.pyd` までブロックされて import 不能になった（処理中に出た通知のボタン操作が契機とみられる．
> CodeIntegrity イベント 3118 に `PreviousEnablementState = 2` が記録されている）．SAC にはファイル単位の例外が無く，
> 評価モードにも戻せず，同じファイルの再インストール（ハードリンク・コピーとも）でも直らなかったため，
> **ユーザー判断で SAC をオフにして解消した**（2026-08-30 の「SAC 無効化は不採用」の判断を変更）．
> 上記の vtk / mitsuba の固定は再現性のため据え置く．SAC が有効な別の Windows 機で環境を作る場合は同じブロックが起こりうる．

## Lint / フォーマット (Lint & Format)

規約の詳細は `.claude/rules/coding-style.md` を参照．ruff を唯一の lint / format ツールとする．

```bash
uv run ruff check .          # 検査のみ
uv run ruff check . --fix    # 自動修正
uv run ruff format .         # フォーマット適用
```

## 研究室サーバでの実行 (Lab Server)

研究室の GPU サーバ（既定 `Newton`）で計算するときは `sm-remote` を使う．コマンドはすべて手元（PowerShell / Git Bash）で実行する．初回の環境構築は `docs/02_ENV/ENV_02_環境構築手順.md` の「研究室サーバ編」を参照．

```powershell
# コードを送る（Newton:~/lab/SmTIAS-LightSim へ上書き展開）
uv run sm-remote push
uv run sm-remote push --include configs/_tmp_offaxis   # 除外対象の _tmp も一緒に送る
uv run sm-remote push --only configs/_tmp_offaxis      # _tmp だけ送る
uv run sm-remote push --prune                          # 送信後，手元で消した src/ tests/ scripts/ のファイルをサーバからも消す
uv run sm-remote push --dry-run                        # 送る一覧と合計サイズだけ表示する

# ジョブを起動する（tmux で継続実行．-- の後ろが uv run に渡るコマンド）
uv run sm-remote status                                # 実行中ジョブ・最近のログ・GPU の空き・負荷
uv run sm-remote run --name offaxis --gpu 0 -- python scripts/sweep_offaxis_led.py
uv run sm-remote run --name r100 --gpu 0 -- sm-render --scene configs/scene_sphere_r100.yaml --output output/r100
uv run sm-remote run --name cpu --yaml-variant --threads 16 -- sm-render --scene configs/scene_baseline.yaml --output output/cpu
uv run sm-remote run --name place --gpu 0 --threads 16 -- python scripts/sweep_led_placement.py --workers 2   # スイープ（GPU 1 枚・2 並列）

# 様子を見る・止める
uv run sm-remote logs offaxis        # ログ末尾（-n 行数）
uv run sm-remote logs offaxis -f     # 追従（Ctrl+C で抜ける．ジョブは継続）
uv run sm-remote attach offaxis      # tmux に接続（Ctrl+B → D で離脱．PowerShell から使う）
uv run sm-remote kill offaxis        # ジョブを止める

# 結果を持ち帰る（サーバの output/<PATH> → 手元の同じ場所．同名ファイルは上書き）
uv run sm-remote pull offaxis_led_sweep
uv run sm-remote pull _remote_logs   # ジョブログ一式
```

- `run` はサーバのプロジェクトルートで `~/.local/bin/uv run <COMMAND>` を tmux セッション（名前 = `--name`．既定は起動時刻）でデタッチ起動する．接続が切れても継続し，出力は `output/_remote_logs/<NAME>.log` に開始行・終了コード行付きで追記される．同名のジョブが実行中ならエラーになる
- `run` は既定で `SM_RENDER_VARIANT=cuda_ad_rgb` を渡す（sm-render が YAML の variant より優先して使う．GPU は手元の CPU の 37〜72 倍速く，Newton の `llvm_ad_rgb` と結果が一致することを 2026-09-13 に確認済み）．YAML の variant のまま回すときは `--yaml-variant`
- レンダ結果は，同じコード・同じ YAML・同じ seed でも**実行ごとに画素単位で変わる**（GPU・CPU とも．2026-09-13 に Newton で確認）．同じシーンオブジェクトを 2 回レンダした差は丸め誤差（並列加算の順序）だけで平均も一致するが，シーンを読み込み直すと約 97% の画素が MC ノイズ程度に変わる（平均の差 3〜7e-4）．コーネルボックスでは読み込み直しても丸め誤差だけなので，このシーン固有の現象．読み込み時に並列で作られる加速構造かサンプリング用の表の丸め差が，最大 16 回の反射で経路の分岐として増幅されていると推定している（どちらかは未切り分け）．sm-render は毎回シーンを読み込むので，1 回ごとに別のノイズ実現と考える．集計指標の条件比較には問題ないが，画素単位で過去のレンダと突き合わせる用途には向かない
- CPU（`llvm_*` / `scalar_*`）で回すときは `--threads N` で Dr.Jit のスレッド数に上限を付ける（共用サーバの 64 スレッドを占有しないため）
- GPU で回すときの CPU 負荷は小さい（球 10 条件のスイープで CPU は平均 156%・最大 342%＝64 スレッド中 1.5〜3.4 スレッド分．2026-09-13 実測）．1 本のスイープに GPU は 1 枚（`--gpu`）で足りる
- **スイープの 1 条件あたりの内訳**（`scripts/profile_sweep_step.py` で計測．2026-09-14 Newton GPU 0・spp=1024・512²）: GPU のレンダ本体 4.4〜5.2 秒が床．旧方式（`uv run sm-render` / `sm-evaluate` をサブプロセスで呼ぶ）はそれに起動・import・YAML・PLY 書き出しで約 2.5 秒，図の描画で約 2 秒が乗り，さらに遮蔽検証（`verify_baffle_occlusion`）を YAML の llvm で回すと球で約 25 秒かかっていた．`scripts/sweep_led_placement.py` 以降のスイープは `render/pipeline.py:render_yaml` と `evaluation/pipeline.py:evaluate_image(save_figures=False)` をプロセス内で呼び，`runner.ensure_variant` で先に cuda を確定してから遮蔽検証する（球 40 秒 → 約 6 秒，立方体 13 秒 → 5.5 秒）．`--workers N` で同じ GPU に複数プロセスを載せられるが，GPU 律速なので実測は 1→5.5 秒・2→5.0 秒・3→4.5〜5.8 秒（1 条件あたり）．**`--workers 2` を既定の推奨**とする（CPU 側の処理を隠す分だけ得．3 以上は効果なし）
- 同一シーンを seed を変えずに再レンダしても集計指標は 4 桁まで一致する（画素はノイズ程度に変わるが集計では消える）．**ノイズフロアを測るときは `render.seed` を変える**（`RenderConfig.seed`．`sweep_led_placement.py` の再現性条件は rep ごとに seed を変えた YAML を書く）
- GPU は `status` で空きを見てから `--gpu` で指定する（未指定だと CUDA は 0 番を使う）．その他の環境変数は `--env KEY=VALUE` で渡せる
- サーバで pytest を回すときは `--yaml-variant` を付ける（variant の上書きをテストに持ち込まないため）: `uv run sm-remote run --name pytest-all --yaml-variant -- python -m pytest -m ""`
- 各サブコマンドの詳細は `uv run sm-remote <サブコマンド> --help`．接続先は `--host` / `--remote-dir` で変えられる

**注意**:

- **ジョブの実行中に push しない**．tar の上書き展開なので，実行中のジョブが途中から新しいコードを読み込み，結果が混ざるおそれがある（push 時にサーバで tmux セッションが動いていれば警告が出る）
- push は上書き展開だけで，**手元で削除・改名したファイルはサーバに残る**．src/ tests/ scripts/ は `--prune` で掃除できる（output/・configs/ はサーバ側の生成物があるので対象外）
- pull は手元の同名ファイルを上書きする．ジョブ実行中に pull すると途中の状態を持ち帰る（tar が「読み取り中に変化した」と警告する）
- 2026-09-13 以前に生成した `configs/_tmp_*` の YAML は Windows の絶対パスを含み，サーバでは読めない（push 時に警告が出る）．生成スクリプトを `--prepare-only` で再実行すると YAML からの相対パスで作り直せる．スイープスクリプト自体をサーバで実行する場合は YAML がサーバ上で作り直されるので問題ない
- Windows で YAML とメッシュが別のドライブにあると相対パスにできないため，生成スクリプトは警告ログを出して絶対パスを書き込む（手元ではそのまま動く）．その YAML は push 時に Windows 絶対パスとして警告されるので，サーバへ持ち出すときはメッシュを同じドライブ（通常は `configs/meshes/`）に置いて作り直す
- pull はサーバから受け取った tar を検査し，要求したパスの外に出る名前（`..`・`.`・空の要素・バックスラッシュ・ドライブ文字）とリンク（シンボリック・ハード）を含むと展開を中止する．中止までに展開したファイルは残る．通常の出力で起きることはなく，起きたらサーバ側の `output/` を確認する
- Git Bash では `~` や `/` で始まる引数が手元で展開・変換される．`--remote-dir '~/lab/X'` のように引用符で囲む（`/` 始まりの絶対パスをそのまま渡したいときは `MSYS_NO_PATHCONV=1` を付ける）
- `sm-remote.exe` が Smart App Control にブロックされた場合は `uv run python -m sm_tias_lightsim.cli.remote <サブコマンド> ...` で同じことができる
- `sm-remote` はサーバ上（VS Code Remote の端末など）で実行すると，自分自身へ ssh しようとするので止まって案内を出す（このマシンのホスト名が接続先と一致するか，リポジトリの場所が接続先のプロジェクトルートと一致したとき）．サーバ上で直接回すときは tmux の中で `uv run ...` を実行する．判定が誤りなら環境変数 `SM_REMOTE_ALLOW_SELF=1` で無効にできる

## 環境変数 (Environment Variables)

- `DRJIT_LIBLLVM_PATH` — Mitsuba の LLVM JIT に必要（未設定時は `runtime/llvm_init.py` が標準パスを自動探索．Ubuntu の `libLLVM-<版>.so` 形式も探す）．詳細は `docs/02_ENV/ENV_02_環境構築手順.md` を参照．
- `SM_RENDER_VARIANT` — sm-render が使う Mitsuba variant を YAML の `render.variant` より優先して上書きする（`sm-render --variant` がさらに優先）．スイープスクリプトは sm-render をサブプロセスで呼ぶので，環境変数にしておけば配下のレンダすべてに効く．`sm-remote run` が既定で `cuda_ad_rgb` を設定する．
- `SM_DRJIT_THREADS` — Dr.Jit の CPU スレッド数の上限（未設定なら全論理コア）．`render/runner.py`・`visualize/baker.py` が起動時に `dr.set_thread_count` で適用する．`sm-remote run --threads` で設定する．1 以上の整数以外（`0`・負数・整数でない文字列）はエラーになる（sm-render は終了コード 1．黙って全コアで回すことはしない）．
- `SM_REMOTE_HOST` / `SM_REMOTE_DIR` / `SM_REMOTE_UV` / `SM_REMOTE_SSH` / `SM_REMOTE_VARIANT` — `sm-remote` の既定値の上書き．それぞれ接続先ホスト（既定 `Newton`）・サーバ上のプロジェクトルート（既定 `~/lab/SmTIAS-LightSim`）・サーバ上の uv（既定 `~/.local/bin/uv`）・手元の ssh（Windows の既定は `C:\Windows\System32\OpenSSH\ssh.exe`）・`run` が渡す variant（既定 `cuda_ad_rgb`）．
- `SM_REMOTE_ALLOW_SELF` — `1` にすると，`sm-remote` の「サーバ上で実行されている」検出を無効にする（誤検出したときの逃げ道）．
