====================================================================
依頼 v12 の 4 章：夜の実行係の道具（tools/）を作った
2026-10-25 19:45
====================================================================

結論

  ・tools/ に4本作り、**Unity 抜きで通しの試験まで通しました**。
    check-in は **cs:4**（tools）と **cs:5**（ignore.conf）です。
  ・**Assets は1バイトも触っていません**（18回を回している途中なので）。
  ・1〜3 章（Assets の中）は手を付けていません。18回が終わってからです。

★ 先に2つ、見ておいてほしいことがあります（6 章）。
  (1) いま **Assets の Robot まわり4本が書き換わっています**（19:41〜19:43）。
      私の分ではありません。「依頼 v13」と書かれているので、別の手で
      すれ違いの実装が進んでいるようです。
      **スクリプトの読み込み直しが起きるので、18回が止まっている疑いがあります。**
  (2) 段階Bの18回は、台帳に cs:3 の行が **0 件**・出力フォルダも **0 件**で、
      出力先に 30 分なにも書かれていません。


====================================================================
1. 作ったもの（tools/・Python の標準ライブラリだけ）
====================================================================

  tools/make_queue.py     条件 × 種を queue.json に並べる
  tools/night_runner.py   N 本同時に回す・台帳に書く・判定を呼ぶ
  tools/ledger.py         台帳に鍵をかけて1行足す（night_runner が使う）
  tools/judge_night.py    judge_night_v0.py を **1バイトも変えずに** 入れた

依頼文には3本と書いてありましたが、**台帳に書く所だけ ledger.py に分けました**。
依頼 5 章 D で「台帳を壊すと戻しにくいので codex にレビューをかける」と決まっているので、
レビューの的を1ファイル（230 行）にしたかったからです。

### make_queue.py

条件は **手で写さず** Unity と同じ所から引きます。
  Assets/ServiceSim/Experiment/serving-robot-params.json の unity_conditions
条件の名前は Unity の SimParams.Condition.Id と同じ形（fam_p1_s4_r1_a42）で、
そのまま実行ファイルの -ssCondition に渡せます。

  python3 tools/make_queue.py --out queue.json
  → 試算の版 v2.8 / 店の形の版 8a5b2f6a016a
    条件 6 / 種 0〜9 / 合わせて 60 回

queue.json には、条件ごとに store・staff・robots・arrivals_per_h・mode・trays・
fragile_by・registers も入れました（実行係が台帳の行を書くときに使います）。

### night_runner.py

  python3 tools/night_runner.py --queue queue.json --parallel 2 --night 20261026

依頼 4 章のとおり:
  ・始める前に KIOXIA・実行ファイル・台帳のヘッダ・版を確かめ、
    どれかが駄目なら **始めない**
  ・caffeinate でスリープを止め、終わりに必ず解く（KeyboardInterrupt でも解く）
  ・N 本まで同時。1本終わったら次を起こす（--stagger で少しずらす。既定5秒）
  ・台帳に completed の行がある回は飛ばす（途中で止めても続きから）
  ・1回の上限（--limit-min。既定90分）を超えたら SIGTERM → 10秒待って kill、
    status=stopped の行を台帳に足す
  ・落ちた回は status=failed の行を足す
  ・止まった・落ちた回は **その夜のうちに1度だけ** やり直す（フォルダに _try2、
    実行ファイルには -ssTry 2 を渡す）
  ・KIOXIA が外れたら、新しい回を始めずに走っている回を待ち、終わったら止まる
  ・時刻では止めない
  ・記録は service_sim/unity/night/{night}/runner_log.txt と logs/{回}.log
  ・全部終わったら judge_night.py を呼ぶ（service_sim/unity/ で）
  ・終了コード 0＝ぜんぶ completed／2＝止まった・落ちた回があった／1＝始められなかった

実行ファイルの呼び方（依頼 2 章の形のまま）:
  {実行ファイル} -batchmode -nographics -logFile {その回のログ}
      -ssCondition fam_p1_s4_r1_a42 -ssSeed 3 -ssLabel night -ssNight 20261026
      -ssParallel 2 [-ssTry 2]

### ledger.py

C# の ServiceSimLedger と同じ決まりにしました。
  ・**追記だけ**。前の行は読むだけ
  ・同じ run_folder の行は2度書かない
  ・**列の並びは実体の1行め（ヘッダ）が正**。
    C# 側が「ヘッダとコードの列が1字でも違えば書かない」を守っているので、
    実体のヘッダを信じれば C# と Python の列がずれません
    （列の一覧を Python にもう1つ持たせると、そこがずれる元になるので持たせていません）
  ・そのうえで、頭の並び（timestamp,run_folder,status,unity_cs,…）と
    ledger_schema_version があることだけ確かめる
  ・ヘッダに無い列名を渡したら例外（打ち間違いを通さない）
  ・値のカンマ・引用符・改行は落とす（C# の Clean と同じ）
  ・最後が改行で終わっていない台帳に足すときは、先に改行を入れる
  ・書いたら fsync する

鍵：unity_ledger_v1.lock を O_EXCL で作れたら持ち主、閉じるときに消す。
取れなければ 0.25 秒おきに最大60秒試す。
C# から new FileStream(lock, FileMode.CreateNew) で取るのと同じやり方なので、
依頼 3 章の Unity 側と同じ鍵を取り合えます。
持ち主が落ちて鍵が残ったら、120 秒より古ければ1度だけ壊します。


====================================================================
2. 確かめ A（Unity 抜きでやれた所）
====================================================================

(1) 構文とライブラリ
  4本とも py_compile を通る。import は argparse / csv / datetime / errno / glob /
  json / os / re / signal / socket / statistics / subprocess / sys / time /
  collections（ぜんぶ標準）。judge_night.py は元のファイルと **cmp で同一**。

(2) make_queue.py
  60 回（6条件 × 種0〜9）。試算の版 v2.8・店の形 8a5b2f6a016a を queue.json に写した。
  --conditions / --seeds で絞れることも確かめた。

(3) ledger.py（本物の台帳は読むだけ。書いたのは写しに対してだけ）
  23 件ぜんぶ OK。
    ・59 列のヘッダを読める／ledger_schema_version がある
    ・1行足せて、書いた値（status・seed・store）が読み直せる。測った値の列は空のまま
    ・同じ run_folder は2度書かない
    ・ヘッダに無い列名は例外で止まる
    ・run_folder が空なら書かない
    ・値のカンマ・改行を落とすので、足した行はどれも 59 列
    ・ヘッダが違う台帳には書かない（追記ガード）
    ・鍵は同時に2つ持てない／離せば取れる／離すとファイルが消える
    ・**10本から同時に足しても10行ぜんぶ入り、行が混ざらず、ヘッダも壊れない**
  ※ 台帳の行の列数は {49列: 36行, 59列: 新しい行} です。
    49 列は依頼 v10b で schema 2 にしたときに書き換えなかった古い行で、仕様どおりです。
  ※ 本物の台帳は sha256（頭12桁）2e0580691ee1 のまま変わっていません。

(4) night_runner.py（偽の実行ファイルで通しの試験）
  実行ファイルの代わりに、終了コードと待ち時間を変えられる偽物を作り、
  台帳も写しに向けて1通り流しました（4回・同時2本・上限0.2分）。

    種0・種1  終了コード0 → completed（偽物が自分で台帳に行を書く＝本物と同じ）
    種7       終了コード1 → failed。実行係が行を足し、_try2 でやり直し、
                            2回めも failed（**3回めはやらない**）
    種8       終わらない  → 上限で SIGTERM → stopped。行を足し、_try2 でやり直し、
                            2回めも stopped
    まとめ    completed 2 / failed 2 / stopped 2。終了コード 2
    判定      service_sim/unity/ で judge_night.py --runs '20261026_night_*'
              --out night/20261026 を呼び、judge.md を書いた

  できた台帳（写し）を確かめた結果:
    足された6行はぜんぶ 59 列・unity_cs は ServiceSim/cs:3・seed と wall_seconds が入る
    ヘッダは無事・鍵は残っていない

(5) 空回し（--dry-run）
  実行ファイルが無くても、打つコマンドだけ見られます。
  版は cm status から ServiceSim/cs:3 を拾い、フォルダの字は cs3 になりました。

やれなかった確かめ（1〜3 章が済んでから）
  依頼 5 章 A の「実行ファイルが作れる」「画面なしで短い1回が回る」「2本同時で
  台帳に2行」は、実行ファイルがまだ無いのでできません。
  いまできる範囲は、偽の実行ファイルで (4) まで見た形です。
  5 章 B（画面なしでも同じ結果か）・C（何本同時に回せるか）も 1〜3 章のあとです。


====================================================================
3. judge_night.py について（変えていません。直した方がよい所の報告）
====================================================================

glob の確かめ（本物のフォルダで・読むだけ）

    20261025_stageB_cs144_*   → 18 件   ← 基準データ v2。合う
    20261025_det_check_*      →  4 件
    20261025_split_check_*    →  2 件
    20261026_night_*          →  0 件（まだ回していない）
    20261025_stageB_cs3_*     →  0 件（6 章を見てください）

  → 新しい名前（{night}_{ラベル}_cs{N}_{店}_r{台数}_s{種}）でも
    `{night}_{ラベル}_*` でちゃんと拾えます。**glob は直す必要なし**です。

直した方がよいと思う所（**直していません**。別の課題か、v12 の続きで決めてください）

  ア. **落ちた回・止まった回を拾えません**（いちばん大事）
     judge は `glob` で **フォルダがある回** だけを見ます（collect の isdir）。
     出力フォルダを作る前に落ちた回は、台帳に failed の行があっても
     judge からは見えず、「回の失敗 … なし」と出ます。
     偽の実行ファイルの試験でも、6回のうち judge が見たのは **2回だけ**でした
     （completed の2回。failed 2・stopped 2 は見えていない）。
     → 直すなら「--runs に合うフォルダ」＋「台帳でその夜・そのラベルの行」の
       両方を見て、フォルダが無い行を「回の失敗」に入れる形です。

  イ. **基準データの既定が、前のプロジェクトのままです**
     BASELINE_DEFAULT = '20261025_stageB_cs144_*'（Robotics_UX_New の cs:144）。
     これから基準にするのは ServiceSim の18回なので、
     夜に呼ぶときは --baseline を明示する必要があります。
     いまの実行係は --baseline を渡さなければ既定のままにします
     （--baseline で渡せるようにしてあります）。

  ウ. **基準データが0件のとき、全部「Unity の作り」になります**
     試験で基準が0件だったとき、completed の2回がどちらも
     「Unity の作り」に振り分けられました（比べる範囲が無いので外れ扱いになる）。
     → 基準が0件なら、警告して止まる方が安全だと思います。

  エ. **_try2 の回と、元の回の両方が judge に入ります**
     `{night}_{ラベル}_*` は _try2 も拾います。
     元の回（stopped）と やり直し（completed）が両方入るので、
     元の回は「回の失敗」に数えられます。
     記録としては正しいのですが、「60回のうち何回が正常か」を見るときに
     分母が増えます。どう数えるかを決めてください。


====================================================================
4. 決めてほしいこと（1つだけ）
====================================================================

出力フォルダに入る「版の字」

  依頼 2 章の例      20261026_night_**ss12**_fam_p1_r1_s3
  いまの Unity 側    20261025_stageB_**cs144**_…／20261025_det_check_**cs2**_…
                     （ServiceSimStageB.FolderName が "cs{N}" にしている）

  いまは **cs{N}** にそろえてあります（service_sim/unity/ の中の名前が
  ぜんぶ同じ形になるので、台帳の名札の読み分けもそのまま使えます）。
  ss{N} にしたいなら、実行係は --cs-token 'ss{N}' で合わせられますが、
  **1〜3 章の実行ファイル側も同じにそろえる**必要があります。
  私は cs{N} のままが良いと思いますが、決めてください。


====================================================================
5. 最初の夜のコマンド（実行ファイルができてから）
====================================================================

  cd /Users/shimazueiichi/Robotics_UX/ServiceSim
  python3 tools/make_queue.py --out queue.json
  python3 tools/night_runner.py --queue queue.json --parallel 2 --night 20261026 \
      --baseline '20261025_stageB_cs3_*'

  ・--parallel は 5 章 C の実測で決める値です（いまは仮に 2）
  ・--baseline は基準にする18回の glob（6 章のとおり、まだ回り切っていません）
  ・上限を変えるなら --limit-min 90
  ・先に打つコマンドだけ見たいなら --dry-run を足す

  ★ 依頼 5 章 D のとおり、最初の夜の前に台帳を写します（コピーだけ）:
      mkdir -p /Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive
      cp /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv \
         /Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive/unity_ledger_v1_prenight_20261026.csv
    これは夜を始める直前に私がやります（いまやると18回の行が入りません）。


====================================================================
6. ★ 見ておいてほしいこと（私の分ではありません）
====================================================================

(1) Assets の Robot まわりが、いま書き換わっています

  cm status（19:45 時点）

    PR  Assets/ServiceSim/Robot/RobotPassBy.cs              19:42 に作られた（新しい）
    CH  Assets/ServiceSim/Robot/ServingRobotController.cs   19:43
    CH  Assets/ServiceSim/Robot/RobotSpec.cs                19:41
    CH  Assets/ServiceSim/Experiment/ExperimentRunner.cs    19:45 ごろ
    CH  Assets/ServiceSim/Experiment/TripLogger.cs          19:45 ごろ

  中身に「依頼 v13 の 依頼1」「すれ違い（実店舗の動画に合わせた）」と書かれており、
  RobotSpec に passByEnabled / passByDistanceM 2.0 / passBySpeedMps 0.3 /
  passBySeparationM 0.1 が足されています。
  前回私が「こう変える」と報告した すれ違いの実装そのものです。

  **私は Assets を1つも触っていません**（ご指示どおり）。
  別の手（エイイチか、別のセッション）で進んでいるのだと思います。
  ただ、ご指示の理由そのまま、**スクリプトの読み込み直しが起きるので、
  回している途中の回が壊れます**。心当たりがなければ確かめてください。

(2) 段階Bの18回が進んでいないようです

  台帳          ServiceSim/cs:3 の行が **0 件**
                （内訳は cs:140 18・cs:143 18・cs:144 18・ServiceSim/cs:2 6）
  出力フォルダ  20261025_stageB_cs3_* が **0 件**
  出力先        ここ30分で書かれたファイルが **0 件**
  run_all_log   最後の記録は det_check の2回（19時前）
  Unity         動いてはいる。Editor.log の最後はアセットの解放とライセンス
                （＝読み込み直しの後のような見え方）

  18回が始まっていないのか、(1) の読み込み直しで止まったのかは、
  Unity の Console と「ServiceSim/Stage B/押す前の確かめ」で分かります。
  cs:3 の18回をやり直すことになっても、台帳は名札＋版で照合するので
  「まだ」の回から続けられます（行が残っていないので18回とも「まだ」のままです）。


====================================================================
7. check-in
====================================================================

  cs:4  tools/make_queue.py・night_runner.py・ledger.py・judge_night.py を足した
  cs:5  ignore.conf に __pycache__ と *.pyc を足した（Python の中間ファイルを入れない）

  ★ Assets の5本（6 章 (1)）は **入れていません**。私の分ではないので触っていません。
  ★ 試験に使った偽の実行ファイル・台帳の写しは scratchpad の中だけで、
    プロジェクトにも KIOXIA にも残していません。
    から回しが作った service_sim/unity/night/20261026/ も消しました
    （本番の夜に作り直されます）。

  作ったファイル
    tools/make_queue.py     174 行
    tools/night_runner.py   440 行
    tools/ledger.py         230 行
    tools/judge_night.py    291 行（judge_night_v0.py と同一）
    ignore.conf             2 行足した

  次（18回が終わってから）
    ・1〜3 章：BuildHeadless.cs・実行ファイルの引数の受け取り・台帳の鍵（C# 側）
    ・5 章 A の残り・B・C
    ・5 章 D：ledger.py と C# 側の台帳まわりに codex のレビュー（依頼文は私が作ります）
