# 検証プロファイル (Verify Profile)

TIAS Shot（WinForms / .NET Framework 4.8 / x64 の舌診撮影ソフト）の検証プロファイル．verifier は本ファイルだけを頼りに検証する．

経緯: 2026-09-18 時点では「verifier はハードウェア結合層（カメラ・UI・スレッド）を担保できない」としてプロファイルを作らなかった．その後，ヘッドレスのセルフテストモード（`TIASshot.exe --selftest`，`SELFTEST` ビルドフラグ）で起動時の依存解決・プレビュー経路・校正〜保存の一部を実機なしで自動確認できるようになったため，本プロファイルを作成した．

## 種別・検証手段 (Target & Method)

- デスクトップアプリ（WinForms）．**画面操作・カメラ操作はしない**．
- ランナー `scripts/verify/run_selftest.py` が，クリーンビルド（`Debug|x64`）→ 既存テスト（NUnit）→ `TIASshot.exe --selftest` を実行し，終了コードと結果ファイルで判定する．
- セルフテストの 3 項目（実行ファイルに組み込み済み．verifier がシナリオを書く必要はない）:

| ID | 確認内容 | 実コードを通す範囲 | 代替している部分 |
| --- | --- | --- | --- |
| `deps` | 起動時にアセンブリ解決エラー（FileLoadException / FileNotFoundException）が出ない | `TIASshot.exe.config` のバインディングリダイレクトと bin の DLL バージョンの一致，本体参照の推移的ロード，bin 内の全管理 DLL のロード，OpenCvSharp（ネイティブ DLL・Mat 演算・`Mat.AsSpan`（System.Memory）・ArUco），`BitmapConverter` と `BitmapUtil.FastClone` の往復，`config.xml`・TCC CSV の読み込み，**`Form1` の実コンストラクタ**（`InitializeComponent`） | `Form1_Load`（カメラ・光源への接続）は実行しない |
| `preview` | プレビュー経路（カメラコールバック → UI 所有コピー（FastClone）→ 表示用 Bitmap）が動く | `CameraBase`（コンストラクタ・`BootCheck`・`ProcessCalibrationFrame`），`Form1.ShowImage`・`MainRefreshTimer_Tick`（実タイマー），`PreviewMonitor`（実コンストラクタ・`UpdateImage`・`RefreshTimer_Tick`）．入力 Bitmap 解放後も表示コピーが有効であること（所有権モデル）を確認 | カメラは偽カメラ（`SelfTest/SelfTestCamera`．Lucam のコールバック手順を再現）．フォームは非表示・`InitializeComponent` を呼ばないヘッドレス版コンストラクタ．副ディスプレイの存在は模擬 |
| `calib-save` | 校正〜保存が一通り動く | `ChartDetector`（ArUco 検出・固定判定・ホモグラフィ・マスク），`CameraBase.CalcTcc`・`ColorCorrector`（変換行列算出・CSV 保存），`CameraBase.ShotOne` → `RunShotLoop` → `SaveThread` → `SaveImages`（sRGB 変換画像の保存）．保存物の読み戻し・行列の有限性・変換後の色票が参照値に戻ることを確認 | チャートは合成画像（ArUco と色票をコードで描画）．ゲイン反映は偽カメラが画像へ乗算して模擬．保存先は一時フォルダ |

## 検証対象の環境 (Target Environments)

- ローカルの接続先: このマシンのみ（ビルド・実行ファイル・一時フォルダ）．ネットワーク上のサービスは使わない（`--full` 時の `nuget restore` による nuget.org からのパッケージ取得のみ例外）
- ローカル以外で許可する環境: なし
- 接続を禁止する環境（拒否リスト）: 実機カメラ（Lumenera / ImagingSource）・光源（シリアルポート）・本番の撮影データフォルダ（`config.xml` の `File/SaveFolder`，既定 `C:\TIAS_Data`）．セルフテストはこれらに触れない設計であり，verifier も触れない
- 条件付きで許可する環境: 実機カメラ・光源・シリアルポートは，**ユーザーが会話の中で実機テストを明示的に合図したとき（オーケストレーターから「実機テストの合図: あり」と渡されたとき）に限り**，後述「実機テスト」節のランナー（`scripts/verify/run_hwtest.py`）経由でだけ接続してよい．本番の撮影データフォルダは実機テストでも触れない（保存先は一時フォルダに差し替わる）

## 実行コマンド (Run Command)

リポジトリルートで実行する．

| 用途 | コマンド |
| --- | --- |
| 通常（コード変更の確認） | `python scripts/verify/run_selftest.py` |
| 依存更新（NuGet パッケージ・バインディングリダイレクトの変更）の確認 | `python scripts/verify/run_selftest.py --full` |

- ランナーの `--no-clean`（Debug 出力を消さない）・`--skip-tests`（既存テストを省略）は開発時の短縮用であり，**判定には使わない**（verifier は上表のコマンドだけを使う）
- `--full` は `packages` を退避して `nuget restore` からやり直す（`git clone` 直後と同じ状態．ENV_04「クリーン環境での検証」）．復元に失敗した場合は退避した `packages` を自動で戻す．`packages` の退避（改名）自体に失敗した場合（Visual Studio が使用中等）は `packages` を変更せずに終了コード 2 で止まる
- 出力先: `.verify/selftest/run-<日時>/`（`.gitignore` 済み．実行ごとに別ディレクトリ）
- 出力されるもの: `summary.json`（各ステップの成否）・`build.log`・`test.log`・`restore.log`（`--full` 時）・`selftest-result.txt`（セルフテストの詳細．UTF-8 BOM 付き）
- ランナーの終了コード: `0`=全て合格，`1`=不合格あり（ビルド失敗・テスト失敗・セルフテスト失敗），`2`=前提条件を満たさない（→「検証不能」）
- セルフテスト単体の終了コード（`summary.json` の `selftest_exit_code`）: `0`=全項目合格，`1`=不合格あり，`2`=セルフテスト自体の異常，`3`=タイムアウト（5 分．ハングの疑い）

### 判定の読み方

- `selftest-result.txt` は項目ごとに `[PASS] <ID>: ...` / `[FAIL] <ID>: ...` の見出し行と，その下の詳細行（`  - ` 情報，`  WARN:` 参考，`  FAIL:` 失敗理由）で構成され，最終行が `RESULT: PASS (3/3)` または `RESULT: FAIL (...)` になる
- `FAIL:` 行には例外型・メッセージ・対象アセンブリ名（`[assembly: ...]`）・Fusion ログの要点（`[fusion: ...]`）・スタックの先頭が入る．依存の問題はここで特定できる
- 判定は**終了コードと結果ファイル**で行う．`TIASshot.exe` の標準出力は OS のコードページ（cp932）で出るため，直接実行すると文字化けすることがある（ランナーは標準出力を使わない）

## 実機テスト（ユーザーの合図があるときだけ）(Hardware Test)

[PLAN_06](../docs/03_PLAN/PLAN_06_実機自動テスト計画.md) の実機テスト（`TIASshot.exe --hwtest`，`HWTEST` ビルドフラグ）．実際の `Form1` を表示し，指定した機種のカメラと光源を操作して検査する．

### 実行してよい条件

- **ユーザーが会話の中で実機テストを明示的に合図したとき（オーケストレーターから「実機テストの合図: あり」と渡されたとき）だけ**実行する．撮影の本番中に機材を操作しないため
- 合図が無ければ実機テストは実行せず，該当する確認を「**検証不能（合図なし）**」と報告する（セルフテスト等，他の検証は通常どおり行う）．合図を推測で補わない．「動作確認して」等の一般的な依頼は合図とみなさない
- 合図は 1 回の検証の依頼ごとに有効とする．以前の会話・別のタスクでの合図を流用しない

### 実行コマンド

リポジトリルートで実行する．**対象は ImagingSource だけ**で，Lumenera の実機テストは行わない（PLAN_06．Lumenera は解像度が相対的に低く今後あまり使わない見込みのため）．ユーザーが明示的に Lumenera の検査を指示した場合に限り `--camera lucam` を使う．

| 用途 | コマンド |
| --- | --- |
| Lumenera を検査する（通常は行わない．ユーザーの明示指示があるときだけ） | `python scripts/verify/run_hwtest.py --camera lucam` |
| ImagingSource を検査する（チャート不要の検査） | `python scripts/verify/run_hwtest.py --camera iscam` |
| ImagingSource を検査する（チャートが必要な検査．上の行に続けて実行する） | `python scripts/verify/run_hwtest.py --camera iscam --with-chart` |

- `--with-chart`（チャートが必要な検査．PLAN_06 フェーズ 2）を指定すると，チャート不要の検査（`preview`〜`reconnect`）の代わりに `calibrate`・`shot`・`close-saving`・`close-calibrating` を行う（`connect`・`cleanup` は両方で行う）．校正は止めない．**チャート不要の検査は行わないため，`--camera iscam` と `--camera iscam --with-chart` をカラーチャートをはめたまま続けて実行する**（付け外しは不要）
- `--keep`（`TIASshot.exe` の `--hwtest-keep`）を付けると，合格時も作業フォルダ（`%TEMP%\TIASshot-hwtest\work-*`）を残し，`--with-chart` では校正後のフレームとその変換画像を証拠の画像（`evidence\calibrated-frame-raw.png`・`evidence\calibrated-frame-srgbNN.png`．`NN` は変換の次元）として保存する．画像を人間に見せたいときや，色の誤差を調べるときに使う（判定は変わらない）
- 所要時間の目安（ビルドを除いた検査の部分）: チャート不要の検査は約 30〜40 秒，`--with-chart` は約 50〜55 秒（校正 約 9〜10 秒・撮影 約 16 秒・保存中に閉じる 約 20〜24 秒・校正中に閉じる 約 3.5〜4 秒）．実機テスト内部の全体の上限はチャート不要 600 秒・`--with-chart` 1200 秒，ランナーの打ち切りはそれぞれ 720 秒・1320 秒
- `--no-clean`（Debug 出力を消さない）は開発時の短縮用であり，判定には使わない
- ランナーは Debug|x64 をビルドしてから `TIASshot.exe --hwtest --camera <機種> --hwtest-out <結果ファイル>` を実行ファイルのフォルダで実行する．NUnit テスト・セルフテストは実行しない
- 出力先: `.verify/hwtest/run-<日時>/`（`summary.json`・`build.log`・`hwtest-result.txt`（UTF-8 BOM 付き））
- ランナーの終了コード: `0`=全項目合格，`1`=不合格あり（ビルド失敗・検査の不合格・タイムアウト），`2`=前提条件を満たさない（→「検証不能」．ツール未導入・TIASshot 起動中・指定機種が見つからない・引数不正）
- 実機テスト単体の終了コード（`summary.json` の `hwtest_exit_code`）: `0`=全項目合格，`1`=不合格あり，`2`=実機テスト自体の異常（引数不正・指定機種が見つからない），`3`=タイムアウト（各項目の上限または全体（チャート不要 10 分・`--with-chart` 20 分）．ハングの疑い），`null`=ランナーが打ち切った（チャート不要 720 秒・`--with-chart` 1320 秒）

### 検査項目

#### チャート不要（`--with-chart` なし）

| ID | 確認内容 | 測定値と判定（しきい値は ImagingSource の実測（PLAN_06 フェーズ 1）から決めた．結果ファイルの表記は「暫定」のまま） |
| --- | --- | --- |
| `connect` | 指定した機種だけに接続でき，機種名・シリアル番号が取れる | 列挙した台数，`Connect()` の成否と所要時間，機種名・シリアル番号 |
| `preview` | プレビューが流れる | 最初のフレームまでの時間，フレームレート（下限 10 fps），本モニタへの表示，平均輝度と飽和画素の割合 |
| `exposure-gain` | 露光・ゲインの設定がフレームに効く | 先に基準の明るさを探す（元の露光に最も近い 2^n 秒の段に合わせ，平均輝度 30〜220 かつ飽和画素の割合 1% 以下になるまで露光を 1 段（`ExposureLogStep`．時間で 2 倍・半分）ずつ動かす．最大 12 段）．露光は基準から 1 段下げ・上げ（比が下げ 0.8 以下・上げ 1.25 以上），ゲインは範囲の幅の ±10%（差が 3 階調以上．片側の向きだけ期待どおりでも合格）で判定．ImagingSource は絶対値（`VCDAbsoluteValueProperty`．秒・dB）で操作する．元の値に戻ったことを読み戻しで確認（相対誤差 1% 以内） |
| `light` | 光源が点灯・消灯し，閉じた後に開き直せる | 照明ボタンでの点灯・消灯の平均輝度の差（100 階調以上），`LightSource.Close` → `Open` の成否，開き直した後の点灯の効き |
| `close-preview` | プレビュー中に閉じる処理を呼んでもすぐ戻る | 閉じる処理（`Form1_FormClosing`）の所要時間（上限 1000 ms）・例外の有無・後始末の順序（closing → timer → camera → light）・閉じた後 1 秒間に届いたフレーム数（2 以下） |
| `reconnect` | 切断した後に再接続できる | 2 回目の `Connect()` の成否・シリアル番号の一致・再接続後のフレームの到着（5 フレーム以上） |
| `cleanup` | 後始末と書き込み禁止の確認 | 露光・ゲインを戻す → カメラ切断 → 光源のポートを閉じる（消灯はしない），の成否．実行ファイルのフォルダの `*.xml`（`config.xml`・デバイス状態ファイル）の内容が変わっていないこと，本番の保存先の今日のフォルダが変わっていないこと |

#### チャートが必要（`--with-chart`）

`connect`・`cleanup` は上表と同じ（`cleanup` ではホワイトバランスを戻さない．校正でカメラ側の値だけが変わり，次の接続でデバイス状態ファイルの値に戻る）．しきい値は ImagingSource の実測（PLAN_06 フェーズ 2）から決めた．

| ID | 確認内容 | 測定値と判定 |
| --- | --- | --- |
| `calibrate` | 実物の舌診チャートで自動校正が完了し，校正後のフレームを変換した色票が参照値に近い | 校正の待ち時間（最初のフレーム → チャート検出 → 校正完了．上限 60 秒），校正中の表示の順，校正完了後に撮影ボタンが有効になること，校正データが一時フォルダに保存されること．校正後のフレームを校正の変換行列で変換し，24 色票の平均と参照値（`tcc_srgb`）の差を **8bit（保存画像と同じ切り詰め後）・チャネルごとの差の絶対値**で判定する: 平均 5 以下，白色票を除いた最大 10 以下，白色票の最大 40 以下（**暫定**．下記「既知のノイズ」）．白色票は参照値の BGR の和が最大の色票（13 番）．ΔE76・白飛びの割合・当てはめの誤差（`TCC_sRGB17.csv`）は参考 |
| `shot` | 単発・連続撮影で撮影画像・sRGB 変換画像・Info.csv が保存され，撮影時刻の間隔が設定どおり | 単発 1 枚・連続 3 枚（間隔 1000 ms）を撮影し，撮影直後は保存中で撮影ボタンが無効，保存の完了（上限 90 秒）後に再有効化されること，保存されたファイル（撮影画像・変換画像・Info.csv．大きさ 0 でない）と撮影ログの行数・備考，撮影時刻の間隔と設定値の差（100 ms 以下），エラー通知が無いこと |
| `close-saving` | 保存中に閉じようとすると取り消され，保存の完了後は閉じられる | 連続 5 枚（間隔 0 ms．保存が先に終わったら 10 枚で再試行）の保存中に閉じる処理を呼び，取り消されること・表示「保存中です．保存完了後に閉じてください」・後始末が走っていないこと．保存の完了後に閉じる処理の所要時間（上限 1000 ms）・後始末の順序（closing → timer → camera → light）・閉じた後 1 秒間に届いたフレーム数（2 以下） |
| `close-calibrating` | 再接続して校正中に閉じる処理を呼んでもすぐ戻る | 再接続（シリアル番号の一致）→ 校正中になる（上限 60 秒）→ 500 ms 後に閉じる処理を呼び，所要時間（上限 1000 ms）・後始末の順序・閉じた後に届いたフレーム数（2 以下）・例外の有無 |

### 判定の読み方

- `hwtest-result.txt` はセルフテストと同じ書式（`[PASS] <ID>: ...` / `[FAIL] <ID>: ...` の見出し行，`  - ` 情報・`  WARN:` 参考・`  FAIL:` 失敗理由の詳細行，最終行 `RESULT: PASS (7/7)`（`--with-chart` は `RESULT: PASS (6/6)`）または `RESULT: FAIL (...)`）．冒頭に行う検査の一覧（`checks:`），暫定しきい値の一覧（`thresholds (暫定)`），全体の上限（`overall timeout:`），校正の扱い（`校正: 停止（チャート不要の検査…）` または `校正: 有効（チャートが必要な検査…）`）が出る
- **測定値（フレームレート・平均輝度・所要時間）は PASS / FAIL にかかわらず詳細行に出る**．暫定しきい値で FAIL した場合は，測定値を報告に必ず書く（しきい値の決め直しに使う）
- タイムアウト時は，止まった項目の見出しの下に途中までの測定値と `FAIL: タイムアウト: ...（実行中のステップ: ...）` が出る．どの SDK 呼び出しで止まったかはステップ名で特定する
- タイムアウト時は，その下に UI スレッド・保存スレッドのマネージドスタック（`  - UI スレッド（ManagedThreadId ...）のマネージドスタック ...:` に続く `    at ...` の行）と，自プロセスのダンプのパス・大きさ（`  - ミニダンプ: <work-*>\hang-<日時>.dmp（全メモリ，... MB ...）`．書けなければ `WARN:`）も出る．ダンプは作業フォルダに残るので，報告にパスを書き，`.dmp` を Visual Studio（「マネージドのみでデバッグ」→ 並列スタック）か WinDbg（`.loadby sos clr` → `~*e !clrstack`）で開いて止まった場所を調べる
- 指定機種が見つからない場合は `RESULT: FAIL (camera not found: <機種>)`（終了コード 2）．機材の接続を人間に依頼する

### 前提条件

| 前提条件 | 確認方法 | 満たされない場合 |
| --- | --- | --- |
| ユーザーの合図がある | オーケストレーターからの指示に「実機テストの合図: あり」がある | 実行せず「検証不能（合図なし）」と報告する |
| 撮影の本番中でない・TIASshot が起動していない | ランナーが `tasklist` で確認する（起動中なら終了コード 2） | 検証不能とし，人間に TIASshot の終了を依頼する（**自分でプロセスを止めない**） |
| 指定機種のカメラと光源が開発 PC に接続されている | `connect` の詳細行（列挙した台数）・`light` の詳細行（ポートの有無） | カメラが無ければ検証不能（終了コード 2）．光源が無ければ `light` が FAIL になる．機材の接続を人間に依頼する |
| カメラの前の穴に**カラーチャート（または白板）がはめてある**（穴が空だと暗く，明るさの変化を評価しにくい）．チャート不要の検査では校正は止まる（結果ファイル冒頭に `校正: 停止（チャート不要の検査…）`） | 人間に確認する．`preview`・`light` の詳細行 `校正は止まっています` | はめていなければ人間にはめてもらってから再実行する．`preview`・`light` が `FAIL: 校正を止めているはずなのに校正が…` なら校正の停止が効いていない不具合として報告する（露光・ゲイン・カメラ本体のホワイトバランスが乱れている可能性あり） |
| セルフテストの前提（Visual Studio / Build Tools・`packages/`・IC Imaging Control 3.5） | 上記「前提条件と確認方法」と同じ | 同上 |

### 既知のノイズ

- 平均輝度・フレームレートは照明・被写体・カメラの個体差で変わる．判定は変化の向きと量で行い，絶対値は判定しない
- 実測（ImagingSource DFK 33UX178・カラーチャートをはめた状態）: フレームレート 13〜31 fps（実行ごとのばらつきが大きい），光源の点灯と消灯の差 149〜196，閉じる処理 75〜221 ms．これより大きく外れたら，しきい値より先に照明・チャート・機材の状態を疑う
- `--with-chart` の実測（同上．2026-10-08〜09 の 4 回）: 校正の待ち時間 5.0〜7.9 秒（チャート検出まで 1.6〜3.0 秒＋校正 3.4〜4.9 秒），色の誤差の平均 1.46〜1.57・白色票を除いた最大 3.47〜3.73・白色票 24.33〜27.35（ΔE76 の平均 1.17〜1.26），撮影間隔のずれ 4〜63 ms，保存は 1 枚あたり約 3.3〜11 秒，保存の完了後に閉じる 33〜76 ms，校正中に閉じる 53〜98 ms．**重いアプリ（Fusion360・Docker・WSL・ブラウザ等）が動いていると，校正・保存が 1.5〜3 倍遅くなり，撮影間隔のずれも大きくなる**（2026-10-09 の実測）．保存の待ちの上限は 180 秒．これより大きく外れたら，しきい値より先にチャートのはまり方・照明・機材の状態を疑う
- **白色票の誤差が大きいのは 17 次元（3 次）の変換の性質**（上流 v1.6 でも同じ．PLAN_06「17 次元の変換のノイズ」）: 白色票の入力のばらつきが約 10 倍に増幅され，8bit に切り詰めると点状のノイズ・白飛びになる（白色票の 255 の画素 約 73%）．平均の色は合っている．白色票のしきい値 40 は対策が決まるまでの暫定であり，白色票だけが 40 を超えた場合は不具合と決めつけず，測定値（白色票の画素の統計・白飛びの割合）を添えて報告する
- 飽和は「254 以上の画素の割合が 1% を超える」で判定する（平均輝度では判定しない）．上げた向きの結果が飽和した場合，または真っ暗（平均輝度 5 以下）の場合，その向きの確認は省かれる（詳細行に「確認しない」と出る）．両方とも省かれると FAIL になる．基準探しで中間の明るさにできなかった場合も FAIL になる
- 露光・ゲインが設定範囲の端にあると，その向きの設定が変わらず確認が省かれる（詳細行に「反映されませんでした」と出る）
- `exposure-gain` の `WARN: 元に戻した後の平均輝度が最初と離れています` は参考（判定は設定値の読み戻しで行う）
- 実際のフォームが表示される（副ディスプレイがあればプレビューモニタも開く）．メモリ観測ウィンドウ（`MEMMONITOR`）は開かない
- **実行中はユーザーに画面（TIASshot のウィンドウ）を操作しないよう案内する**．実行中にウィンドウを閉じると閉じる処理が検査の待機の中で割り込み，検査が戻らずタイムアウトしうる（2026-10-09 に撮影中に閉じられて shot がタイムアウトした）．合図を受けたら，実行前に「画面に触らないでください」と伝える

### 後始末

- 実機テストは終了時（失敗時も）に「変えた露光・ゲインを元に戻す → カメラ切断 → 光源のポートを閉じる」を行い，`cleanup` 項目に結果が出る．光源は消灯しない（アプリの終了時と同じく点いたまま残るのが仕様．上流 v1.7 と同じ）．`light` 項目は点灯・消灯・再点灯を検査し，最後は点灯した状態で終わる
- タイムアウト・未処理例外の場合は光源のポートだけを緊急に閉じる（消灯はしない．カメラには触れない．露光・ゲインは次の接続で設定ファイルの値に戻る）．`WARN: 緊急の後始末: 光源のポートのクローズが 5 秒以内に終わりません` が出たら，ポートが開いたままの可能性を人間に報告する（光源が点いたままなのは仕様であり報告不要．`light` 項目の途中（消灯中）で止まった場合は消灯したまま残りうる）
- 書き込み先は `%TEMP%\TIASshot-hwtest\work-<日時>-<PID>\`（一時設定ファイル `config.hwtest.xml`．チャート不要の検査では校正を止めるため校正データは書かれない．`--with-chart` では校正データ・撮影データ（`data\<日付>\`）と，`--keep` 指定時は証拠の画像（`evidence\`）が書かれる）と `.verify/hwtest/run-<日時>/` だけ．合格時と前提を満たさずに終わった場合（終了コード 2）は `work-*` を自動削除し（`--keep`（`--hwtest-keep`）指定時は残す），不合格（1）・タイムアウト（3）時は残して結果ファイルの末尾に `work dir kept: <パス>` と出る．残った `work-*` の有無とパスを報告の「後始末」に書く
- 同梱の `config.xml`・デバイス状態ファイル（`{DeviceName}.xml`）・本番の保存先には書き込まない（`cleanup` で確認する）

## 前提条件と確認方法 (Preconditions)

| 前提条件 | 確認方法 | 満たされない場合 |
| --- | --- | --- |
| Visual Studio 2022 または Build Tools（MSBuild・vstest）が導入済み | ランナーが `vswhere` で自動検出する（見つからなければ終了コード 2） | 検証不能とし，`docs/02_ENV/ENV_02_環境構築手順.md` に従った導入を人間に案内する |
| NuGet パッケージが復元済み（`packages/`） | `ls packages` | `python scripts/verify/run_selftest.py --full` を実行してよい（`nuget.exe` が無ければ検証不能とし `setup.ps1` の実行を人間に案内する） |
| IC Imaging Control 3.5 .NET Library が導入済み（ビルドに必要．実機カメラは不要） | ビルドの成否（`build.log` に `TIS.Imaging` の参照エラーが無いこと） | 検証不能とし，ENV_02 の SDK 導入手順を人間に案内する |
| `TIASshot.exe` やテストが実行中でない（Debug 出力を削除できる） | ランナーがクリーンするのは Debug 出力（`TIASshot/` と `TIASshot.Tests/` の `bin/x64/Debug`・`obj/x64/Debug`）のみで，`bin/x64/Release` 等ほかの構成の出力には触れない．削除に失敗すると終了コード 2 で止まる | 検証不能とし，人間にアプリ・Visual Studio のデバッグ実行の終了を依頼する（**自分でプロセスを止めない**） |

## 認証方法・ロール (Auth & Roles)

- 認証なし

## テストデータの規約 (Test Data Rules)

- セルフテストが書き込むのは次の 2 か所だけ:
  - `%TEMP%\TIASshot-selftest\work-<日時>-<PID>\`（一時設定ファイル・校正データ・撮影画像）．**合格時は自動削除**，不合格時は証拠として残し，結果ファイルの末尾に `work dir kept: <パス>` と出る
  - `.verify/selftest/run-<日時>/`（ランナーのログと結果）
- 同梱の `config.xml` は変更しない（セルフテストは複製して保存先・参照パス・効果音を差し替えた一時設定を使う）．本番の保存先（`C:\TIAS_Data` 等）には書き込まない
- 不合格時に残った `work-*` フォルダの有無とパスを，報告の「後始末」に書く（削除は人間の判断に任せてよい）

## 待ち方・操作の癖 (Waiting & Selectors)

- ランナーは同期実行で，完了まで待つだけでよい（通常 10〜30 秒．`--full` はパッケージ取得で数分）
- セルフテストはウィンドウを表示しない．画面の確認手段は無く，スクリーンショットも出ない
- `Release` 構成にはセルフテストが含まれない（`SELFTEST` フラグは Debug 系のみ）．Release の exe に `--selftest` を渡すと通常起動（UI 表示・カメラ接続）になるため，実行しない

## 既知のノイズ (Known Noise)

- `deps` の `リソース系の AssemblyResolve n 件（サテライトリソースの探索．判定対象外）`: 日本語カルチャのサテライトリソース（`TIASshot.resources`）を探して見つからないのは通常動作
- `deps` の `redirect System.ValueTuple → 4.0.3.0: bin に DLL なし`: .NET Framework 4.7.1 以降は mscorlib に統合されており bin に置かれないのが正常
- カメラ SDK（`TIS.*`・`lumenera.*`）のロード失敗は `WARN:`（判定対象外）．SDK 未導入の環境では出うる
- `preview` / `calib-save` の所要時間は環境で変わる（判定条件ではない）

## 禁止操作 (Prohibited Operations)

- `--selftest` を付けない `TIASshot.exe` の起動（UI が開き，実機カメラ・光源（シリアルポート）へ接続を試みる）
  - 例外: ユーザーの合図があるときの `python scripts/verify/run_hwtest.py --camera <lucam|iscam> [--with-chart] [--keep]`（上記「実機テスト」節）．`TIASshot.exe --hwtest` の直接実行・合図なしの実行・`Release` の exe での実行はしない
- `config.xml`（リポジトリ内・bin 内とも）や本番の保存先フォルダ（`C:\TIAS_Data` 等）の編集・削除
- 自分が起動していないプロセスの停止（人間が起動した TIASshot・Visual Studio のデバッグ実行を止めない）
- `packages.bak-*` 以外のリポジトリ内ディレクトリの手動削除・改名（`packages` の退避と復元はランナーの `--full` だけが行う）
- ランナーを通さない独自のビルド・実行手順の組み立て（コマンドは本ファイルに書かれたものだけを使う）
- 失敗注入（bin 内 DLL の一時除去・`TIASshot.exe.config` の改ざん等）や `TIASshot.exe --selftest` の直接実行．セルフテストが依存の問題を検出できるかの確認が必要な場合は，人間が `docs/02_ENV/ENV_04_開発コマンド.md` の手順で行う（verifier には許可しない）

## シナリオのテンプレート (Scenario Template)

本プロジェクトでは **verifier がシナリオを書く必要はない**（確認項目は `TIASshot.exe --selftest` に組み込み済みで，引数で項目を選ぶ仕組みも無い）．verifier の作業は次のとおり．

```text
1. 変更内容に応じてコマンドを選ぶ
   - NuGet パッケージ・App.config（バインディングリダイレクト）・csproj の参照を変えた → --full
   - それ以外のコード変更 → 通常
2. python scripts/verify/run_selftest.py [--full] を実行する
3. 出力ディレクトリの summary.json と selftest-result.txt を Read で開き，
   - 各ステップ（clean / restore / build / tests / selftest）の成否
   - 各項目（deps / preview / calib-save）の [PASS]/[FAIL] と，FAIL: 行・WARN: 行の内容
   を確認する．PASS でも，今回の変更に関係する項目の詳細行（ロードされた DLL のバージョン等）を必ず見る
4. FAIL の場合は FAIL: 行（例外型・アセンブリ名・Fusion ログ）を証拠として報告する
   （build.log / test.log の該当箇所も併記する）
```

変更がセルフテストの 3 項目で観測できない領域（下記「自動で確認できない範囲」）にある場合は，その部分を「未確認」として人間に回す．

## 自動で確認できない範囲（人間が確認する項目）(Out of Scope)

セルフテストが PASS でも，以下は確認していない．変更がこれらに関わる場合は「人間が確認すべき残り項目」に具体的に書く．

- **実カメラ SDK との結合**: Lucam / IScam の接続・パラメータ設定・フレームコールバック・撮影（Snap・Sink 切り替え）・ROI 切り出し・白板撮影（`WHITEBOARD`）
- **実プレビュー表示**: 実ウィンドウへの描画・表示の滑らかさ・副ディスプレイでの表示位置・F9 トグル（`MEMMONITOR`）
- **実チャートの検出性能**: 実物の舌診チャートを実照明・実レンズで撮った画像での ArUco 検出・固定判定・マスク位置（合成チャートは理想条件）
- **画像の見た目**: 校正後の sRGB 画像の色・露出・ホワイトバランスが臨床的に妥当か（セルフテストは合成チャートでの数値一致のみ）
- **長時間のメモリ / GDI**: 長時間プレビュー・連続撮影でのメモリリーク・GDI ハンドル枯渇（`docs/06_TEST/TEST_03_メモリ修正効果計測.md` の手順）
- 光源（シリアル通信）の制御，効果音の再生，エラー時のダイアログ表示（セルフテストでは記録のみに差し替えている）
- `Release` 構成の実行（セルフテストを含まないため，起動確認は人間が行う）

## 実行基盤の要件 (Runner Requirements)

- 決定的な処理（ツールの検出・bin/obj の削除・パッケージ復元・ビルド・テスト・セルフテスト実行・結果の集計）はランナー `scripts/verify/run_selftest.py` が行う．verifier は判断（結果の読み取り・報告）のみ行う
- 実行ごとに `.verify/selftest/run-<日時>/` へ出力し，前回の証拠を上書きしない
- ランナーは自分が起動したプロセス（MSBuild・vstest・TIASshot.exe）以外を停止しない．セルフテストは内部のウォッチドッグ（5 分）でハング時も終了コード 3 で終わる
- 前提を満たさない場合は破壊的な復旧を試みず，終了コード 2 と理由を出して終了する
- セルフテストの実装は `TIASshot/SelfTest/` と `TIASshot/UI/*.SelfTest.cs`（`#if SELFTEST`），ビルドフラグは `docs/02_ENV/ENV_06_ビルド構成とビルドフラグ.md` を参照する
