# コードレビュー依頼：夜の実行で台帳に書く所（サービスロボット導入シミュレーション・v12）

対象（読むだけ。書き換えない）：Unity プロジェクト `/Users/shimazueiichi/Robotics_UX/ServiceSim/`（ServiceSim/cs:19）

- `Assets/ServiceSim/Experiment/ServiceSimLedger.cs`（台帳に1行足す。鍵もここ。Editor の外へ移した版）
- `Assets/ServiceSim/Experiment/LedgerRow.cs`（1行の中身を作る。Editor と画面なしで共通）
- `Assets/ServiceSim/Experiment/HeadlessRun.cs`（画面なしの実行ファイルで1回回す入口）
- `Assets/ServiceSim/Editor/BuildHeadless.cs`（`build_info.json` を書く。台帳の `unity_cs` の出どころ）
- `tools/ledger.py`（夜の実行係が、止めた回・落ちた回の行を書く）
- `tools/night_runner.py`（何本も同時に起動し、やり直し・止める・判定を呼ぶ。`runs.csv` と `queue.json` の写しもここ）

---

## このコードがやろうとしていること

Unity で配膳ロボットの店を回すシミュレーションを、画面なしの実行ファイルで夜のあいだ何本も同時に回す。1回が終わるたびに、KIOXIA（外付けSSD）の台帳 `/Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv` に1行足す。台帳は唯一の正本で、壊すと戻せない。このレビューでは**台帳に書く所と、その回を後から辿れるようにする記録だけ**を見てほしい（店の回し方・ロボットの動きは対象外）。

## 設計仕様（台帳の決まり：data_manifest_rules.md 10節）

1. 1回 = 1行・追記のみ。既存の行の上書き・移動・削除をしない
2. 追記ガード：実体のヘッダとコードの列の並び（59列）が違えば書かない
3. 同じ出力フォルダ名の行を2度書かない（出力フォルダ名が照合キー）。やり直しは名前の後ろに `_try2` を付ける
4. 何本も同時に書くので、鍵ファイル `service_sim/unity/unity_ledger_v1.lock` を排他で開けてから書く。開けなければ待って試し直す。C#（実行ファイル）と Python（実行係）が同じ鍵を使う
5. 実行係が止めた回（時間切れ）・落ちた回は、実行係が status = stopped / failed の行を書く（鍵をかけて）
6. 書く前に KIOXIA がつながっているか確かめる。つながっていなければ何も書かない（Mac の中に fallback しない）
7. `unity_cs` は `ServiceSim/cs:N` の形。値は実行ファイルのそばの `build_info.json` から来る
8. **`night/{夜}/runs.csv`**（2026-10-26 追加）：1回ごとの開始・終了の時刻を、completed も含めて全部1行ずつ残す。
   列は `folder,condition,seed,pass_by,tryno,launch_running,parallel,started_iso,ended_iso,started_unix,ended_unix,wall_s,status,exit_code`。
   **なぜ要るか**：`run_info.json` の `parallel_actual` は「その回を**起動したとき**に同時に走っていた本数」なので、並びの最後で同時本数が減るのは出てこない（減るのは回している途中）。開始・終了の時刻をそのまま残せば、「その回が回っている間ほかに何本走っていたか」を後から正確に計算できる。
   同時本数は測ってみると `stuck_count`・`rescues`・`robot_share`・`late_staff_dishes` を動かす（下の補足）ので、どの回が何本の中で回ったかを後から言えることが要る。
   **台帳の列は増やさない**（10節）。だから台帳の外の記録にした
9. **`night/{夜}/queue.json`**（2026-10-26 追加）：その夜に並べた回の写し。`queue.json` はプロジェクトの中で夜ごとに作り直し VCS に入れないので、写さないと「その夜に何をどの順で並べたか」がどこにも残らない。もうあれば上書きしない（同じ夜を続きから回したとき、最初の並びを残す）

## レビューしてほしいチェック項目

### A. ロジックの正しさ

A1. C# と Python の鍵は、本当に同じ鍵として待ち合えるか（使っている仕組み：ファイルの排他オープン／flock／ロックファイルの作成と削除 など。macOS の外付けSSD（exFAT か APFS）で同じように効くか）
A2. 鍵を持ったまま落ちた場合（実行ファイルが落ちる・実行係が強制終了される）、鍵が残って次の回が永久に待たないか。古い鍵の扱い
A3. 「ヘッダの確認」「もう同じフォルダの行があるか」「1行足す」が、すべて鍵の中で行われているか
A4. 1行を書く途中で落ちたとき、半端な行が残らないか（1回の write で書いているか・改行の扱い）
A5. 実行係が stopped / failed の行を書いたあと、遅れて実行ファイル自身も同じフォルダの行を書く、という二重書きが起きないか（時間切れで止めた回を SIGTERM したのに、終了処理で台帳に書いてしまう など）
A6. `_try2` の名前の付け方と、「completed は飛ばす」の照合が、C# と Python で同じ規則か
A7. 出力フォルダ名を決める式が C#（`HeadlessRun.FolderName`）と Python（`night_runner.folder_name`）で**完全に同じ**か。食い違うと、実行係が「もう回した」と誤って飛ばす／同じ回を2度回す
A8. 台帳の `unity_cs` は `build_info.json` から来る。**保留中の変更があるまま作った実行ファイル**でも、警告は出るが止まらない（`BuildHeadless.cs`）。この場合、台帳の `unity_cs` は `cs:N` なのに中身は `cs:N` と違う、という取り違えが起きる。止めるべきか、警告のままでよいか意見がほしい
A9. **`runs.csv`**：completed・stopped・failed・時間切れで止めた回のすべてで1行書かれるか。書き落ちる道はないか（`reap` を通らない終わり方があるか）
A10. **`runs.csv`** の `started_unix` / `ended_unix` から、任意の回について「回っている間に重なっていた本数」を正しく復元できるか（`ended = started + wall_s` の整合、時計の取り方、夏時間や時計合わせでずれないか）

### B. 副作用・破壊チェック

B1. 既存の行（117行）を書き換える経路が1つもないか（ファイルを開くモード：追記のみか）
B2. CSV のクオート・カンマ・改行を含む値（日本語の pending_changes など）で列がずれないか
B3. ヘッダが違うときに、黙って書かないだけでなく、失敗として記録・終了コードに出るか
B4. **`runs.csv` と `queue.json` の写し**が、台帳や回の出力フォルダを壊す道はないか（書く先が `night/{夜}/` の中だけか）
B5. **`queue.json` の写し**が、同じ夜を続きから回したときに最初の並びを上書きしないか
B6. `runs.csv` のヘッダがコードの並びと違うとき、黙って追記して列をずらさないか

### C. 暴走・安全リスク

C1. 鍵が取れないまま待ち続けて、夜じゅう止まる道はないか（待つ上限と、その後の扱い）
C2. 実行係が同じ回を何度も起動し続ける道はないか（やり直しは1度だけ）
C3. KIOXIA が途中で外れたとき、新しい回を始めず、Mac の中に書かないか
C4. **`runs.csv` が開けない・書けないときに、夜が止まってしまわないか**（記録のために本体を落としていないか）。逆に、黙って記録だけが欠ける場合、それが分かるか

### D. 制約ルールの遵守

D5. 台帳への追記処理を持たない「通し完走」の経路がないか（回の終わり方が completed / stopped / failed のどれでも1行残るか）
D6. KIOXIA へ書き込む所すべてに、マウントの確かめが入っているか（未マウント時にローカルへ fallback しない）
D7. 台帳の列を増やしていないか（59列・schema version 2 のまま。`parallel` / `parallel_actual` は `run_info.json` と `runs.csv` 側に置き、台帳には入れていない）
（D1〜D4 は実機のアーム用の項目なので対象外）

## レビュー結果のフォーマット

各項目について、✅ OK／⚠️ 要注意（何が懸念か）／❌ NG（該当行番号と修正案）で答えてほしい。
最後にサマリとして：**70回・4本同時**の夜に進んで良いか、進む前に直すべき項目があるか。

## 補足情報

- 実行ファイルは `Builds/headless/ServiceSim.app`（`build_id: cs19_20261026_1649`・保留 0・ARM64）。`-batchmode -nographics` で1本 = 1回
- 夜の同時本数は **N = 4** に決めた。実測で 49.5 回/時（70回に約63分）。CPU 5.7コア／8コア・メモリ 490 MB／16 GB
- 2026-10-26 に Unity 抜き・実物の両方で確かめた所（レビューの参考にしてよい）
  - 台帳の鍵：8本同時に追記して 8 行・59 列・ヘッダ無傷・鍵が残らない
  - C# と Python が同じ鍵を**両方向**で待ち合える（C# が 3 秒持つ間 Python が 2.5 秒待って取得／逆も）
  - 出力フォルダ名が C# と Python で 8 通り一致（版が読めないときの `csunknown` も一致）
  - 本物の台帳で 2本同時（名札 acheck）・1本（bcheck）・1〜4本同時（c1〜c4check）・
    1本×5回と4本同時×8回（busy1 / busy4）・1回（pcheck）＝ 合わせて 30 行を追記。
    すべて 59 列・completed・重複なし・退避版の 86 行は 1 byte も変化なし
  - 同時本数で動いた列は `stuck_count`・`rescues`・`robot_share`・`late_staff_dishes` の4つだけ
    （入店待ち・店員待ち・料理待ち・会計待ち・回転・店の中の時間は動かない）
- 台帳は確かめの前に `archive/unity_ledger_v1_preacheck_20261026/` にコピーで退避済み（md5 一致）。
  最初の夜の前にも `archive/unity_ledger_v1_prenight_{夜}/` に写す
- `runs.csv` と `queue.json` の写しは、まだ**本物の夜では動かしていない**（Unity 抜きで 19 件の確かめは通っている）
- `git clean` 禁止・`git add .` 禁止（個別ファイル指定）
