# 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()` で復元します。 - 復元が成功し、登録数が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 の停止条件(開始前の不足と、計測中の自然な変化を区別する。再注入も状態の固定もしない)を実装する。