====================================================================
依頼 v11 の 5 章：ずれの切り分けの仕掛けを作った（回すのはエイイチ）
2026-10-25 18:04
====================================================================

結論

  ・試し1〜3 を押せる形にしました。名札は **det_check** で、
    段階Bの18回とも split_check とも混ざりません。組み立ては通っています（54 本・エラー 0）。
  ・**私からは Play を押せません**（Unity が ServiceSim のプロジェクトを押さえています）。
    4 章の手順で押してください。1回 11 分・2回ずつ3通りで 70 分ほどです。
  ・試しの直しは **check-in していません**。保留は私が直した 6 本だけです。
  ・★ ご指摘のとおりでした。3 章の「忙しいと pathPending が長くなる」は
    **私が確かめずに書いた見込み**です。1 章で訂正し、試し3 で確かめる形にしました。
  ・ops.NowS の洗い出しは 2 章です。**(0) の直しは入れていません。**
    会計・片付けの時間、60 秒であきらめる、600 秒で卓を空ける は NowS を使っておらず、
    2 分待ちは NowS を使っていますが「引き算」なのでずれは消えます。
    1 か所だけ、来客を止める時刻が絶対値で比べられていて、ずれが効きます。


====================================================================
1. 【訂正】pathfindingIterationsPerFrame は忙しさで変わらないはず
====================================================================

ご指摘のとおりです。Unity の説明では

  NavMesh.pathfindingIterationsPerFrame
    = 「1コマで処理する道探しの節の最大数」（既定 100）

で、**実時間ではなくコマあたりの回数**です。1コマ 0.05 秒に固定してあり、
注文が溜まる順番はシミュレーションの中身だけで決まるので、
**この枠が原因なら Mac の忙しさでは変わらないはず**です。

前の報告の 3 章で「忙しいと pathPending が長くなる」と書いたのは、
私が「非同期だから実時間に引っ張られるだろう」と見込みで書いたもので、
確かめていませんでした。取り消します。すみません。

では、2回がずれた元は何か。いまの時点で言えるのは次までです。

  確かなこと
    ・2回の回しで違っていた入力は、実時間の値だけ（plan_ms_max が1回めの往復から違う。
      wall_seconds は 654.5 秒 / 601.1 秒）
    ・1546.25 秒までは、出力が小数4桁まで完全に一致していた
    ・最初のずれは「スタッフの譲りが4コマ遅れた」で、
      その判断は _agent.pathPending が true の間は打ち切られる作りになっている

  まだ分かっていないこと
    ・pathPending の長さが、何によって2回で変わったのか。
      枠がコマあたりなら、残る筋は「道探しや局所回避がワーカースレッドで解かれていて、
      main スレッドが結果を読む時点で終わっているかどうかが、
      スレッドの込み具合で変わる」あたりですが、**確かめていません**。
    ・そもそも「忙しさ」ではなく、同じ Editor の中で2回続けて回したことによる
      持ち越し（前の回の状態）かもしれません。

  だから試し3 を「わざと重くして回す」に変えずに、**2つに分けました**（4 章）。
    試し3a  同じ条件でもう2回（何も重くしない）
            → 毎回ずれるのか、たまたまだったのかが分かる
    試し3b  ほかに2本回しながら1本
            → 忙しさで**結果が変わるか**。ご指摘どおりなら変わらないはず

  試し3b で結果が変わったら、「何が変えたか」を次の順で詰めます。
    ・run_info.json の wall_seconds と plan_ms（どれだけ重くなったか）
    ・det_frames.csv の path_pending 列（1コマごとに、道待ちの人が何人いたか）を
      3通りで並べる。忙しいときに path_pending が増えていれば、
      「枠はコマあたりでも、結果を読む時点で終わっていない」ことの裏づけになります
    ・増えていないのに結果が変わったら、道待ち以外（局所回避）が原因と分かります

  ついでに1つ消しました。
    通行者の種類（NavMesh.CreateSettings）が Play ごとに増えて、
    1回めと2回めで人用の種類の番号が違ったのでは、と疑いましたが、
    NavMeshAreas.asset は cs:1 のときも今も **Humanoid 1種のまま**でした。
    この筋は無しです。


====================================================================
2. ops.NowS を判断に使っている所（(0) の直しは入れていません）
====================================================================

NowS を読んでいる所は 56 か所ありました。判断に効くものだけ並べます。

(a) 引き算なので、NowS がずれても **影響しない**もの

    ServiceOps.cs:1317  if (NowS - d.readyS < wait)
      → ★ 料理の「2分待ち」（robotFirstWaitSec = 120）。
        readyS も NowS から入れているので、引き算でずれが消えます。安全
    GroupManager.cs:312  float now = ops.NowS → Arrive / SeatWaiting / Advance
      → 来客の間隔（now < _nextArrivalS）、料理ができる時刻（now < g.cookAt[i]）、
        食べ終わり（now < g.eatEndS）、呼び出し、ドリンクバー・トイレ。
        どれも「now ＋ 決めた秒数」で入れた時刻と now を比べているので、引き算。安全
    ServiceOps.cs:1057,1063  turnMin / seatWaitMin
    ServiceOps.cs:516       仕事が板で待った秒数（依頼 v10 の 2）
      → 記録。引き算なので安全

(b) **絶対値で比べていて、NowS のずれがそのまま効く**もの

    ExperimentRunner.cs:602  if (ops.NowS >= _arrivalsEndS && !groups.AnyGroupInStore)
    ExperimentRunner.cs:607  return ops.NowS >= _hardEndS;
    GroupManager.cs（Arrive） if (arrivalsEndS > 0f && now >= arrivalsEndS) return;
      → _arrivalsEndS = peak_min × 60 = **7200 秒**（定数）。
        NowS は float で足し続けているので、本当の 7200 秒のときには
        NowS = 7190.68 秒（**−9.32 秒**）。
        つまり **来客を 9.3 秒ぶん長く受け付けています**（本当の 7209.3 秒まで）。
        打ち切り（_hardEndS = 7200 + 600×60）も同じだけずれます。
      → ただし **2回の回しで同じようにずれる**ので、今回の「2回がずれる」の原因ではありません。
        直すと来客が 9 秒ぶん減るので、**基準データ v2 の数字は少し動きます**。

    ExperimentRunner.cs:560  if (ops.NowS >= _nextTimelineS)
      → timeline.csv の t_s（0・60・120…）。記録だけですが、
        終わりごろには本当の時刻と 20 秒以上ずれた札になります
        （179 分の回で測ると −23.4 秒）

(c) NowS を **使っていない**もの（ご指摘の項目はこちら）

    ★ 会計 90 秒・片付け 40＋40 秒・案内 45 秒の dwell
        → StaffAgent の `_timer += Time.deltaTime`
    ★ 60 秒であきらめる（staffGiveUpSec）
        → StaffAgent の `_stepS += Time.deltaTime`
    ★ 600 秒で卓を空ける（tableStuckFreeSec・依頼 v10 の 1）
        → GroupManager の `g.stuckS += dt`
    ・ロボットが 60 秒で動けなくなる（stuckAfterStoppedSec）、
      1往復の打ち切り（maxTripSec）、人どうしの詰まり（JamWatcher）
        → どれも dt の足し算
    ・StoppedNowS は名前に NowS が付いていますが、中身は積み上げた秒数です

  まとめ
    ・ご心配の「会計・片付けの時間、60 秒であきらめる」は NowS と無関係でした。
    ・「2 分待ち」は NowS を使いますが引き算なので安全です。
    ・効くのは **来客を止める時刻と打ち切りの時刻**（9.3 秒ぶん長い）だけです。
    ・なので (0) の直しは「記録をきれいにする」だけでなく、
      **来客の長さが 9 秒変わる＝基準データ v2 の数字が少し動く**直しになります。
      前の報告で「(0) なら v2 はそのまま使える」と書いたのは不正確でした。
      訂正します。(0) のうち
        ・plan_ms_max を比べる列から外す      … 動き変わらない
        ・stops.csv の t_s を NowS にそろえる  … 動き変わらない
        ・NowS をコマ数×0.05 にする            … **来客が 9 秒短くなる。v2 が動く**
      の3つに分けて決めてください。


====================================================================
3. 試し2 で一致しなかったときの、残りの元の見分け方
====================================================================

残るのは (B) 局所回避（RVO）と (C) 物理の返る順番です。
見分けられるように、2つの道具を足しました。

(C) 物理の返る順番 — **先に白黒がつきます**

  FrontSensor は箱に入った当たりを 32 個の配列で受け、
  超えたら切り捨てます（どれが落ちるかは返る順番で決まる）。
  実際に超えているかを数えて run_info.json に出すようにしました。

    "sensor_max_hits":  その回でいちばん多かった当たりの数
    "sensor_overflows": 32 を超えて切り捨てた回数

  → sensor_overflows が 0 なら、**切り捨ては起きていない＝(C) は原因から外せます**。
    （残るのは「ぴったり同じ距離で並んだとき」だけですが、
      小数の一致はまず起きないので、実質無しと見ます）
  → これは試し1 の出力でもう分かります。試し2 を待つ必要がありません。

(B) 局所回避（RVO） — ずれ方の形で分かります

  1コマごとの指紋を det_frames.csv に書くようにしました（名札 det_check のときだけ）。

    frame,t_s,people,robots,people_sum,robot_sum,path_pending

    people_sum / robot_sum  位置を 0.1 mm まで整数にして混ぜた数。
                            2回ぶんを並べると **どのコマで初めてずれたか** が1コマ単位で分かる
    path_pending            そのコマで道待ちだった人の数

  見分け方
    ・**RVO なら**：ずれ始めが **早い**（数分以内）。人が2人以上近づいた最初の場面から
      people_sum が違い始め、path_pending は同じまま。
      位置の差はごく小さく（1 mm 未満から）、だんだん大きくなる
    ・**道待ち（A）が残っているなら**：ずれ始めまで people_sum がぴったり一致し、
      ずれた**そのコマで path_pending が違う**。差は「コマ何個ぶん」の形で出る
      （いまの 1546 秒・4コマのずれと同じ形）
    ・どちらでもないなら、試し4（局所回避も切る）を押します。
      それで一致すれば RVO、しなければさらに別の元があると分かります

  念のため：試し1・試し2 でも指紋を書くようにしてあります。
  一致していても「最後まで people_sum が同じ」ことが確かめられます。


====================================================================
4. エイイチにお願いすること（押す順番）
====================================================================

  Unity に切り替えてください（私が直した .cs が取り込まれます）。Console の赤い字を確認。

  試し1 — 道探しの枠だけ大きくする
    1. ServiceSim → Stage B → 切り分けの設定 → 試し1：道探しの枠を 5000 にする
       （Console に「道探しの枠 5000 回/コマ / 人の道を同期 切 / …」と出ます）
    2. ServiceSim → Stage B → ずれの切り分け（③1台・種1 を2回）
       → {日付}_det_check_cs2_fam_p1_r1_s1 と _2 ができます

  試し2 — 人の道を同期で引く
    3. 切り分けの設定 → 試し2：人の道を同期で引く
    4. ずれの切り分け（③1台・種1 を2回）

  試し3a — 試し2 のまま、もう2回（たまたまかを見る）
    5. 4 をもう一度押す（_3 と _4 になります）

  試し3b — 忙しくして1回
    6. ほかの重い処理を2つ走らせながら（または別の Unity を2つ回しながら）、
       もう一度 4 を押す
       ※ 1回の実時間の上限は 40 分です。3本同時で超えそうなら、
         先に ExperimentPlan の maxWallSecondsPerRun ではなく
         ServiceSimStageB の上限（40 分）に当たるので、
         status が stopped になったら教えてください

  終わったら
    7. 切り分けの設定 → ぜんぶ切る（cs:2 と同じ動きに戻す）
    8. 出力フォルダ名を教えてください。det_frames.csv と run_info.json を私が並べます

  ★ 試し1〜4 はどれも **ExperimentPlan.asset を書き換えます**（切り替えの保存）。
    これも check-in しないでください。終わったら 7 を押して、
    私が cm undochange で元に戻します。


====================================================================
5. 私が直したファイル（check-in していません）
====================================================================

  いまの保留 6 件。ぜんぶ「試しの切り替えを入れた」分で、
  切り替えを切れば cs:2 と同じ動きになります。

  Experiment/ExperimentPlan.cs      切り替え4つを足した（初期値はぜんぶ切）
                                      detCheckPathIterations（0 = 触らない）
                                      detCheckSyncPeoplePaths
                                      detCheckNoAvoidance
                                      detCheckFrameFingerprint
  People/PeopleNav.cs               Go に同期で引く道を足した（SyncPaths が true のとき）。
                                    引けなければ今までどおり非同期に任せる
  Robot/FrontSensor.cs              当たりの最大数と切り捨て回数を数えた（MaxSeen / Overflows）
  Experiment/ExperimentRunner.cs    切り替えを入れる・戻す（ApplyDetCheck / RestoreDetCheck）、
                                    det_frames.csv を書く、run_info に det_check を出す
  Experiment/TripLogger.cs          run_info.json に det_check・sensor_max_hits・
                                    sensor_overflows を足した
  Editor/ServiceSimStageB.cs        名札 det_check・メニュー
                                    「ずれの切り分け（③1台・種1 を2回）」と
                                    「切り分けの設定」5つ

  変えていないもの
    ・店の回し方・ロボットの動き・人のよけ方の決まり（切り替えを切った状態で）
    ・ops.NowS（(0) の直しは入れていません。2 章のとおり決めてから）
    ・台帳の列（増やしていません）
    ・段階Bの18回の中身と順番

  増えたメニュー（ほかは cs:2 と同じ）
    ServiceSim/Stage B/ずれの切り分け（③1台・種1 を2回）
    ServiceSim/Stage B/切り分けの設定/試し1：道探しの枠を 5000 にする
    ServiceSim/Stage B/切り分けの設定/試し2：人の道を同期で引く
    ServiceSim/Stage B/切り分けの設定/試し4：局所回避も切る（要るときだけ）
    ServiceSim/Stage B/切り分けの設定/ぜんぶ切る（cs:2 と同じ動きに戻す）
    ServiceSim/Stage B/切り分けの設定/いまの設定を見る
