Files
TheTree_mock/docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md
T
TakeshiandClaude Opus 5.5 8fe128c5a8 P28-01: Fix Ops ON input, startup order, render counts and proof roles in spec
Representative input (602 min, 200 guests) cannot start Operations
(Day1 06:00 only, population cap 100), so Ops ON uses the real new-game
opening and 200-guest Ops ON stays unmet. Pin the autoload/main startup
order, define render counts from the world_preview model, separate input
shortfall from natural change, split checker/static review/owned-run proof,
and make the collector self-contained (tracked run_all_tests.py and
owned_outer.py only). Budgets and original denominator unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:13:56 +09:00

18 KiB
Raw Blame History

P28-01 現HEADの性能基準:最小バッチ仕様

この文書は Runner の spec_ref です。work_packages.json 全体の代わりに、この対象に必要な原本定義だけを固定します。

1. 原本定義(変更しない)

  • 分母は原30パッケージ・88成果単位で、部分点はありません。開始時点の正式完了は22/88、完了パッケージは4/30です。
  • 成果単位 P28-01「現HEADの性能基準」(work_packages.json の P28.remaining_deliverables[0])
    • work: 実Runtimeの20,000マス・200客・50職員・150施設を基準にTick、経路、snapshot、描画、保存を測る。
    • acceptance: 機種、ビルド、シード、速度、測定区間、平均・P95・最大を残す。
  • パッケージ P28「性能・操作感・長期バランス」
    • requirement_ids: B-1.1, B-3.1a, B-4.3a, B-4.3c, B-11.1, B-12.1, B-14.4
    • decisions: D04, D08, D10
    • package acceptance(P28-01 の完了では満たしたことにしない): 既存性能予算と新しいWindows描画目標を満たす。到達不可能な収支/信仰/ランク条件なし。高速自動再生だけでプレイ時間・操作感を合格にしない。
  • P28 横断条件6件(各成果で適用し、適用外にするときは理由を記録する)
    1. 原本条件とUI操作が一致
    2. Runtimeの実利用・会計・行動へ接続
    3. 途中保存・再起動で再現
    4. 異常/境界/中断を検証
    5. 関連回帰PASS
    6. 要件別ログとソースハッシュを保存

2. 完全原本参照

docs/traceability/original/BASELINE.txt(SHA256 75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a)。行番号は requirement_coverage.json の source_lines です。

ID 行 この対象での意味
B-1.1 2–10 PC (Windows)、想定プレイ時間
B-3.1a 58–60 現実10秒=ゲーム1時間、Pause/x1/x2/x3
B-4.3a / B-4.3c 84–87 A*経路、EV待ち時間、渋滞時の50%減速
B-11.1 213–216 メイン画面、スクロール、ズーム、表と裏
B-12.1 228–230 ドット絵マップの描画
B-14.4 258 UIレスポンス向上

既存の予算値は scripts/core/performance_budget.gd:17-24 のとおりで、変更しません。x1 平均4 ms、x3 P95 12 ms、経路 P95 2 ms、main-thread stall の最大50 ms、保存5 s、読込8 s です。

3. 再利用する既存証拠(再生成しない)

  • source: main 8c36c100b2073baf8a9c536528f319a5d0837a63。製品 source map は正式全回帰 p14-03-formal-regression-83b170a-r01 と同じ1407件です。この対象では製品と source map の対象(autoload/scripts/scenes/data/tests/assets/tools と直下4ファイル)を変更しません。変更がなければ、正式192 suite の証拠はそのまま有効です。
  • 代表入力: artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant(SHA eb4f7700…f9bd)。20,000/200/50/150、Operations OFF です。全生成はしません。
  • engine: /Applications/Godot.app/Contents/MacOS/Godot(SHA c7cccbf8…8ccf、4.7.2.stable.official.ed1daf0bf)。
  • 既存の headless 実測 r02(p14-03-representative-perf-r01/run-r02/execution.json SHA 553971f5…9fc1)
    • 条件: Mac16,10・10コア・24 GB、Operations OFF、x1、300分
    • 1分 tick: 平均826.6 ms、P95 1702.7 ms、最大2918.1 ms
    • 経路 cold: P95 0.61 ms
    • snapshot: 平均77.6 ms
    • 保存: manual 297 ms、autosave 327 ms
    • 読込: 平均3193 ms、最大3368 ms
    • これは x1 headless・Operations OFF の行としてそのまま採用し、再測定しません。
  • collector の型: p14-03-representative-perf-r01/collect_perf.py(SHA 43f3e429…c063)と measure.gd(SHA 95e59678…f76a)、run-r02/profile_guard.gd(SHA 597696c7…a6f)。以下を引き継ぎます。
    • 私有の CoW stage、private user:// 保存
    • owned Popen による PID の捕捉、guard、cleanup
    • 通常 player の保全
    • 全 raw stdout と SHA の結合
  • 上の artifacts/ は gitignore 対象のため、Root が worker へ build/ 配下の読取専用として渡します。
  • collector の依存は自己完結させる。 r02 の collect_perf.py は、gitignore 対象の windows-current1297-tomari-release-adapter-prep-r01/windows_adapter.py を動的に import しています(:153)。そのadapterは、さらに legacy/windows_adapter.py と support/owned_outer.py を読み込みます。新しい collector はこの外部パスに依存しません。
    • import してよいのは、Git で追跡している次の2つだけで、どちらも変更しません。
      • tools/run_all_tests.py: source_refs、default_player、private_project、copy_source、profile_path
      • tools/regression/owned_outer.py: run_phase、finish_captured。SHA c2e3483b…66e7 で、adapter の support 版とバイト一致し、内部で owned_diagnostic_process.py を読み込みます
    • それ以外の補助処理は、allowed_paths の中に実装します。対象は、private import 用の project、run_engine 相当の owned phase の実行(上限は1800 s で、adapter の固定値1200 s ではない)、profile_files、no_live_engine、厳密な診断です。
    • adapter の2ファイルは仕様の参照にだけ使い、import しません。読取専用入力の SHA は REGISTRATION_PROPOSAL.json に記載します。

4. 入力の選択と前提(API監査の結果を反映)

4.1 Operations ON は代表入力では開始できない(事実)

  • SimulationRuntime.enable_production_operations()(autoload/simulation_runtime.gd:1534-1544)は、Day 1 06:00、つまり absolute_minute == OPERATIONS_START_MINUTE(360、:49)のときだけ開始できます。
    • 代表入力は602分(Day 1 10:02)に保存されているため、INVALID_START_TIME になります。
    • clock だけを巻き戻すことは禁止です。
  • Operations ON の人口上限は100人です(scripts/operations/operations_coordinator.gd:85、:203、:333)。
    • 開始時には set_population_count(guest_count)(simulation_runtime.gd:1579)が上限を照合するため、200客では INITIAL_POPULATION_INVALID になります。
    • 正規契約の上での200客 Operations ON の状態は存在しません(PERFORMANCE_AUDIT.md「100客上限と200客診断」)。
  • 既存の成果物の中に、代表規模の Operations ON の保存はありません。代表入力の build 記録は、operations_enabled_mixed_load_accepted: false です。

4.2 選択した入力(製品変更なし、fixtureの全生成なし)

入力 中身 Operations の実状態 使い道
R:代表入力 上の reference.variant(SHA eb4f7700…、602分、20,000/200/50/150) main の _ready が enable_production_operations() を呼び、INVALID_START_TIME で失敗する。この実結果を記録したうえで OFF 原 work の「20,000マス・200客・50職員・150施設」の windowed x1/x3
O:新規開始 起動時に autoload の SimulationRuntime._ready が行う initialize(GameClock, GameState, DEFAULT_WORLD_SEED)(:201-206)。Day 1 06:00 main の _ready が開始に成功した実際の ON 通常UIで始まる Operations ON の自然営業
  • O は代表規模ではありません。O の値を「200客」や「代表負荷」と呼ばず、実数を併記します。
  • 200客での Operations ON は、製品契約(上限100)のために測定できません。これは P28 の未達として残し、P28-01 では満たしたことにしません。

4.3 起動順序(独立した Runtime は作らない)

計測するのは、既定の autoload(/root/GameClock、/root/GameState、/root/SimulationRuntime)と、main が描画する同じ世界だけです。SimulationRuntime.new() や別の initialize で作った Runtime は計測しません。

  1. engine は phase ごとに1プロセスを起動し、r02 と同じ Guard の bootstrap(profile_guard.gd)と --script の wrapper を使います。windowed なので --headless は付けません。
  2. autoload の準備が終わったら、GameClock.set_paused(true) で時間を止めます。
  3. R の場合: /root/SimulationRuntime.restore_snapshot(<get_var(false)で読んだ入力>) で復元します。
    • 復元が成功し、登録数が20,000/200/50/150と完全に一致すること、および入力 SHA の一致を確認します。
    • 一致しなければ開始前の不足として停止します(§5)。
    • O の場合は何も復元しません。
  4. res://scenes/main/main.tscn を instantiate して root に追加します。main の _ready(main.gd:218-250)の処理は次のとおりで、それぞれの戻り値と get_operations_report().enabled の実値を記録します。
    • _pause_fresh_opening
    • enable_production_save_policy
    • enable_production_operations の自動呼出し
  5. focus_operating_area の deferred 処理が終わってから、camera(zoom、pan、layer、viewport の大きさ)を記録し、計測中はそのまま固定します。
  6. 速度は main._set_speed(n)(:2727、GameClock.set_speed を呼ぶ)で設定し、一時停止の解除は main._toggle_pause()(:2689)で行います。どちらも UI のボタンと同じ処理です。
    • 以降、時間は SimulationKernel._process → process_real_delta だけで進めます(MAX_TICKS_PER_FRAME 8、MAX_BACKLOG 120、simulation_kernel.gd:30-31、:54-72)。
    • advance_minutes、advance_one_minute、spawn_guest などの直接呼出しはしません。
  7. production save policy の下では autosave が起きることがあります。保存先は collector の private user:// に限り、通常 player の保全を前後で照合します。区間内に起きた autosave は stall の原因として記録します。

5. 測定

phase は次の4つです。それぞれ別の engine プロセスで実行し、区間は warm-up 30ゲーム分のあと、計測120ゲーム分です。

phase 入力 速度
P1 R(OFF) x1
P2 R(OFF) x3
P3 O(ON) x1
P4 O(ON) x3
  • x1 headless での保存・読込・経路・snapshot は r02 の値を採用し、windowed では測りません。

段階0(同じ collector)

  • P1・P2 の長い計測の前に、R を x1 で10ゲーム分だけ動かします。O についても P3・P4 の前に、x1 で10ゲーム分の限定動作を行います。
  • 各段階0で次のものを記録してから、各 phase の所要時間を外挿します。
    • Operations の enable の実結果
    • 1分ごとの時間
    • 実数
    • 完了時間
  • 見積り: R は 827 ms × 150分 × 2 ≒ 250 s です。O は段階0の実測から外挿します。
  • phase ごとの上限は1800 s です。

各 sample に残すもの(取得できない値は null と理由にし、0 や合格で補いません)

  • 機種: hw.model、コア数、メモリ
  • build: engine の文字列・hash・SHA、project の commit、source map の SHA
  • seed: restore 後、または新規開始後の SimulationRuntime.world_seed の実値
  • RNG state: rng_streams。seed とは別欄にします
  • 入力 SHA と、phase 準備状態の SHA
  • 速度と、GameClock.speed の実値
  • 区間: 開始・終了のゲーム分と wall 時刻、frame 数、各 frame で進んだ分数
  • 時間の統計: frame 時間、kernel の1分 tick(get_performance_reports)、stall(50 ms を超えた frame の数と最大)、描画 CPU/GPU の時間(RenderingServer.viewport_set_measure_render_time、viewport_get_measured_render_time_cpu/_gpu)、Performance.TIME_PROCESS。それぞれについて平均・P95・最大・標本数を記録します
  • 実数は3種類を分けて記録します
    • 登録総数: 客・職員・施設・歩行可能セル
    • 実稼働数: 退館中でない在館の客、勤務中の職員、稼働中の施設
    • 描画数: 描画に実際に使われた model の数です。world_preview は Canvas に即時描画するため(_draw :512、_draw_pixel_actor :1525)、人数分の Node はありません。そのため Node の数ではなく、次のように数えます。
      • actor: world_preview.get_life_observation_report()(:564)の actor_drawings のうち、現在の layer にあるもの(_observe_actors が layer で絞ったもの)の件数と、そのうち at が Rect2(Vector2.ZERO, size) の中にあるものの件数。observation_serial も添えます
      • 施設: 完成した施設のうち、現在の layer にあり、cell の外接矩形が viewport と交わるもの(_draw_world_entity_icons :966 と同じ判定)の件数
      • セル: bounds の中で get_cell_rect(cell) が viewport と交わるセル(_draw_world_cells :765 と同じ判定)の件数
      • あわせて layer、zoom、pan、viewport の大きさを記録します

判定: 予算(§2)との合否を生の値で並べます。新しい許容値は作りません。

6. 停止条件と人数の扱い

  • 開始前の不足: 次のいずれかに当たれば、その phase の計測を始めずに原記録を残して止め、input_shortfall とします。
    • R の入力 SHA の不一致
    • 復元の失敗
    • 登録数が20,000/200/50/150と一致しない
    • O の Operations が開始できない(enabled が false、または開始が360分でない)
  • 計測中の自然な変化: 客の退館・来館、職員の状態の変化は、すべての sample の実数として記録します。これは停止条件ではありません。
    • 計測の途中で客を再注入したり、状態を固定したり、復元をやり直したりすることは、通常の計測に入れません。
    • 区間の要約には、登録数と実稼働数の最小・最大を必ず併記します。登録が200で、実稼働数も示した区間だけを「200客」と表記できます。
  • 途中停止: 次のいずれかに当たれば、そこまでの原記録を残して止めます。次の最小手順は判断担当が決めます。
    • phase の上限(1800 s)を超えた
    • engine が異常終了した
    • Operations の runtime failure の latch、または terminal(破産)になった
    • 要対応イベントのために clock が止まり、進まない
      • collector がイベントを自動で選んで解消することはしません。

7. 証明の役割分担

担当 確認すること 確認しないこと
checker(check_p28_01_report.py)と、合成 JSON での unittest report の形式、必須の欄、null に理由があること、自己矛盾がないこと。たとえば次のものを拒否します。
・速度とゲーム分・frame の不整合
・seed 欄が RNG state と同じ値
・登録数が稼働数や描画数の欄に流用されている
・GPU 時間が 0 で理由がない
・「200客」の表記と実数の不一致
経路・seed・実稼働数が事実かどうか
probe の実経路の静的レビュー(独立 Opus) §4.3 の順序、_set_speed/_toggle_pause/_process の経路、autoload の世界を計測していること、直接 advance・再注入・別 Runtime がないこと 実行したかどうか
Root の実測(owned PID、raw の結合) 実際の engine プロセスで、PID が一致すること、raw stdout と report が一致すること、source map と入力 SHA を照合した実行であること —

この3つがすべてそろったときに限り、記録は事実として扱われます。

8. 対象外と非主張

  • Windows 実機は未検証のまま残します。
  • 200客での Operations ON は、製品の上限100のために測定できず、未達として残します。
  • P28 パッケージの予算達成、P28-02、P28-03、長期バランス、プレイ時間は対象外です。
  • 4回分の全回帰と、fixture の全生成は行いません。

9. 完了の扱い

  • この Runner task: collector、probe、checker が C1〜C9 を満たし、Runner の validation、独立レビュー、integration、final を通ること。これで原本の加算は0です。
  • 原本 P28-01 の完了: Root が統合済みの collector で実測し、その receipt を結合した後の証拠ゲートが通ったときに限ります。記録の項目がそろえば、予算が未達でも P28-01 の受入は成立します。未達の予算と200客 Operations ON は、P28 の未達として残します。

10. 登録条件(collector task の baseline)

  • C1: 速度は main._set_speed/_toggle_pause→SimulationKernel._process だけで与え、直接 advance の値を x3 とは呼ばない。checker は不整合を拒否し、probe は静的レビューの対象とする。
  • C2: 入力 SHA と phase 準備状態の SHA、登録・実稼働・描画(§5の定義)の3種類の実数を、別の欄に記録する。
  • C3: seed の実値(world_seed)と RNG state を別の欄にし、取得できなければ null と理由にする。
  • C4: GPU や描画時間を取得できなければ null と理由にし、0 や合格への補完を checker が拒否する。
  • C5: 段階0(R と O のそれぞれ)と外挿の記録、phase ごとの上限1800 s、超過時に原記録を保存して止めることを実装する。
  • C6: private user://、owned PID・guard・cleanup・通常 player の保全・全 raw ログと SHA の結合を、r02 と同等に保つ。
  • C7: 製品 source map が開始前と終了後で変わらないことを照合し、変われば拒否する。
  • C8: 機種・build・seed・速度・区間・平均・P95・最大の各欄を全 sample に要求し、欠けた欄や偽の値を checker が拒否する。
  • C9: §4.3 の起動順序(autoload の世界、main の _ready の実際の Operations の結果を記録)と、§6 の停止条件(開始前の不足と、計測中の自然な変化を区別する。再注入も状態の固定もしない)を実装する。