diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md b/docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md index 58ecad7..f3baaf6 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md @@ -54,60 +54,145 @@ - 通常 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. 不足を埋める測定(この対象の新規分) +## 4. 入力の選択と前提(API監査の結果を反映) -windowed(非headless)の main scene で、1回の collector 実行の中に次の4 phase を置きます。 +### 4.1 Operations ON は代表入力では開始できない(事実) -| phase | Operations | 速度 | +- `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 | OFF | x1 | -| P2 | OFF | x3 | -| P3 | ON | x1 | -| P4 | ON | x3 | +| P1 | R(OFF) | x1 | +| P2 | R(OFF) | x3 | +| P3 | O(ON) | x1 | +| P4 | O(ON) | x3 | -各 phase の区間は warm-up 30ゲーム分のあと、計測120ゲーム分です。 +- x1 headless での保存・読込・経路・snapshot は r02 の値を採用し、windowed では測りません。 -1. **実UIと同じ Clock 経路を使う。** 速度は `main.gd::_set_speed`(速度ボタンと同じ処理)で設定し、時間は `SimulationKernel._process → process_real_delta`(MAX_TICKS_PER_FRAME 8、MAX_BACKLOG 120)だけで進めます。`advance_minutes(3)` などを直接呼んで出した値を x3 frame とは呼びません。 -2. **SHA と実数を分けて残す。** 元 fixture の SHA と、各 phase の準備状態の SHA を記録します。数は sample ごとに3種類を区別して残します。 - - 登録総数(客・職員・施設) - - 実稼働数(在館で、退館中でない客、勤務中の職員、稼働中の施設) - - 描画対象の数(viewport 内で可視の node) +**段階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 です。 - warm-up で客が減った後の値を「200客の測定」とは呼ばず、実数を併記します。 -3. **seed と RNG state を別欄にする。** seed は restore 後の `SimulationRuntime.world_seed` の実値です。RNG state(`rng_streams`)は別欄に記録し、state を seed の名前で代用しません。 -4. **未取得は null と理由にする。** GPU 時間は `RenderingServer.viewport_set_measure_render_time` と `viewport_get_measured_render_time_gpu` で取得します。非対応・未取得なら null と理由を残し、0 や合格で補いません。CPU の描画時間、frame 時間、`Performance.TIME_PROCESS` も同じ扱いです。 -5. **段階を踏んで実行する。** - - 所要時間の見積り: 1分 tick 827 ms から、(30+120)分 × 4 phase = 約500 s の CPU 時間。起動と import は別。 - - 同じ collector の段階0として、Operations ON の限定動作を先に1回行います。enable の結果、10ゲーム分の1分ごとの時間、実数、完了時間を見ます。 - - 段階0の実測から P3・P4 の所要時間を外挿し、記録してから長い計測へ進みます。 - - 時間の超過(phase ごとに上限1800 s)や実数の不足があれば、その時点の原記録を保存して止めます。次の最小手順は判断担当が決めます。 -6. **各 sample に残すもの** - - 機種: hw.model、コア数、メモリ - - build: engine の文字列・hash・SHA、project の commit、source map の SHA - - seed と RNG state(3. のとおり別欄) - - 速度、区間(開始・終了のゲーム分と wall 時刻) - - frame 時間、1分 tick、stall(50 ms 超の frame の数と最大)、描画 CPU/GPU のそれぞれについて、平均・P95・最大・標本数 - - 予算は合否を生の値で並べるだけで、新しい許容値は作りません。 +**各 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 の大きさを記録します -## 5. 対象外と非主張 +**判定**: 予算(§2)との合否を生の値で並べます。新しい許容値は作りません。 -- Windows 実機は未検証のまま残します(P02-03 / P28-03 の範囲)。 +## 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 の全生成は行いません。 -## 6. 完了の扱い +## 9. 完了の扱い -- **この Runner task**: collector、probe、checker が下の C1〜C8 を満たし、Runner の validation、独立レビュー、integration、final を通ること。これで原本の加算は0です。 -- **原本 P28-01 の完了**: Root が統合済みの collector で実測し、その receipt を結合した後の証拠ゲートが通ったときに限ります。予算が未達でも、記録の項目がそろえば P28-01 の受入は成立します。未達の予算は P28 パッケージの未達として残します。 +- **この Runner task**: collector、probe、checker が C1〜C9 を満たし、Runner の validation、独立レビュー、integration、final を通ること。これで原本の加算は0です。 +- **原本 P28-01 の完了**: Root が統合済みの collector で実測し、その receipt を結合した後の証拠ゲートが通ったときに限ります。記録の項目がそろえば、予算が未達でも P28-01 の受入は成立します。未達の予算と200客 Operations ON は、P28 の未達として残します。 -## 7. 登録条件(collector task の baseline) +## 10. 登録条件(collector task の baseline) -- **C1**: 速度は実UI/Clock の経路だけで与え、x3 を直接 advance の値で代用しないことを、checker が拒否試験で確認する。 -- **C2**: 元 fixture と各 phase の準備状態の SHA、および sample ごとの登録・稼働・描画の3種類の実数を、別々の欄に記録する。 -- **C3**: seed の実値と RNG state を別欄にし、取得できなければ null と理由にする。 -- **C4**: GPU や描画時間を取得できなければ null と理由にする。0 や合格への補完を checker が拒否する。 -- **C5**: 段階0(Operations ON の限定動作)と外挿の記録、phase ごとの上限、超過時に原記録を保存して止めることを実装する。 +- **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 が拒否する。 +- **C7**: 製品 source map が開始前と終了後で変わらないことを照合し、変われば拒否する。 +- **C8**: 機種・build・seed・速度・区間・平均・P95・最大の各欄を全 sample に要求し、欠けた欄や偽の値を checker が拒否する。 +- **C9**: §4.3 の起動順序(autoload の世界、main の `_ready` の実際の Operations の結果を記録)と、§6 の停止条件(開始前の不足と、計測中の自然な変化を区別する。再注入も状態の固定もしない)を実装する。 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/TASK_INSTRUCTIONS.md b/docs/plans/original-completion-2026-10-02/execution/p28-01/TASK_INSTRUCTIONS.md index eea3fb8..dccb4d2 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/TASK_INSTRUCTIONS.md +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/TASK_INSTRUCTIONS.md @@ -6,12 +6,13 @@ 1. **`collect_p28_01.py`**: r02 の `collect_perf.py` を基にした外部 collector です。 - 1版(main の現 source)だけを、私有の CoW stage に materialize します。 - - import は1回だけ行い、その後に段階0と P1〜P4 を1つずつ実行します。 + - import は1回だけ行い、その後は R の段階0、P1、P2、O の段階0、P3、P4 の順に、それぞれ別の engine プロセスで1つずつ実行します。 - `--check` は入力と予定の argv を検証して表示するだけで、stage も engine も作りません。 - `--run` は実測します。Root が実行します。 2. **`measure_p28_01.gd`**: windowed の main scene 用 probe です。 - - 代表入力の読込は r02 と同じく `FileAccess.get_var(false)`、`initialize`、`restore_snapshot` で行います。保存は private user:// だけです。 - - 速度は `main.gd::_set_speed` で設定し、`SimulationKernel._process` が進めるのを観測するだけにします。直接 advance を呼んではいけません。 + - 代表入力(R)は `FileAccess.get_var(false)` で読み、既定の autoload `/root/SimulationRuntime.restore_snapshot` に復元します。新規開始(O)は何も復元しません。どちらの場合も、別の Runtime の生成や `initialize` の再呼出しはしません(SPEC §4.3)。保存は private user:// だけです。 + - main.tscn は、復元の後に root へ追加します。`_ready` で起きた Operations の実際の結果を記録します。 + - 速度は `main._set_speed`、一時停止の解除は `main._toggle_pause` で行い、あとは `SimulationKernel._process` が進めるのを観測するだけにします。直接 advance を呼んではいけません。 - 計測は frame ごとに行います。 - stdout にちょうど1行 `P28_01_PERF_REPORT=` を出します。 3. **`check_p28_01_report.py`**: report と収集結果の checker です。合格の条件と、拒否すべき偽の report は下に示します。 @@ -19,7 +20,7 @@ ## 必須事項 -- SPEC.md の §4 と C1〜C8 を、そのとおりに実装します。 +- SPEC.md の §4〜§7 と C1〜C9 を、そのとおりに実装します。 - **checker が拒否すべきもの**(それぞれに対応する拒否試験を書く) - x3 が直接 advance で作られている(Clock の速度と frame の記録が一致しない) - 登録数を稼働数や描画数の欄に流用している @@ -36,7 +37,11 @@ - native engine の起動(実測は Root が統合後に行います) - fixture の生成 - P14 の再調査 -- 参照用の入力(r02 の `collect_perf.py`、`measure.gd`、`profile_guard.gd`、`run-r02/execution.json` と report)は、Root が `build/p28-01-readonly/` に読取専用で置きます。SHA は SPEC.md §3 のとおりです。それ以外の場所や、拒否されたパスにはアクセスしないでください。 +- 必ず守ること: SPEC §4.3 の起動順序と、§6 の停止条件。§4.1 のとおり、代表入力(R)で Operations は開始できず、200客での Operations ON は製品の上限で不可能です。clock を巻き戻すこと、上限を上書きすること、客を再注入すること、状態を固定することは禁止です。 +- 描画数は Node の数ではなく、SPEC §5 の model の定義で数えます。 +- checker が証明するのは形式と矛盾だけです(SPEC §7)。経路や seed が事実かどうかは、静的レビューと Root の実測で確かめます。 +- 依存は自己完結させます(SPEC §3)。gitignore 対象の windows_adapter.py とその legacy/support は import しません。import してよいのは `tools/run_all_tests.py` と `tools/regression/owned_outer.py` だけで、どちらも変更しません。unittest の中で、collector の import 先にこの2つ以外の外部パスがないことを確認します。 +- 参照用の入力(r02 の `collect_perf.py`、`measure.gd`、`profile_guard.gd`、`run-r02/execution.json` と report、さらに adapter の `windows_adapter.py`・`legacy/windows_adapter.py`)は、Root が `build/p28-01-readonly/` に読取専用で置きます。SHA は SPEC.md §3 と REGISTRATION_PROPOSAL.json の readonly_inputs_for_worker のとおりです。それ以外の場所や、拒否されたパスにはアクセスしないでください。 - commit はしません(Runner が submit します)。最終出力には変更したファイルと、試験の実行結果をそのまま出します。 ## 検証