Files
TheTree_mock/docs/plans/original-completion-2026-10-02/execution/p28-02/SPEC.md
T

11 KiB
Raw Blame History

P28-02「意味を保つ軽量化」仕様(候補 r02。未登録・未実行)

  • 状態: 候補です。Root が原本の条件と照合して main に commit し、その実 SHA を base にしてから、通常の登録をします。
  • 前提: P28-01 は正式に完了しています(Q ae9ae47646434dd57fc21f3e8566d0d35b242ab1、execution/p28-01/P28_01_FORMAL_COMPLETION_AE9AE47.json f778ac8d…)。その測定(full9 db9d613、r02、RESTART)と、製品 source 1407 を引き継ぎます。

1. 原本の条件(変更しない)

  • 成果単位 P28-02(work_packages.json の P28.remaining_deliverables)
    • work: 失敗時拒否を含む同等性を先に固定し、snapshot変換・検証・コピー・経路接続の実測上位から改善する。
    • acceptance: 同条件の反復比較と全状態・全イベント一致を確認し、キャッシュの古さ・所有権・不正入力を検証する。
  • P28 の横断条件6件(原文): 原本条件とUI操作が一致 / Runtimeの実利用・会計・行動へ接続 / 途中保存・再起動で再現 / 異常/境界/中断を検証 / 関連回帰PASS / 要件別ログとソースハッシュを保存。
  • パッケージの依存: P28 は P27 に依存し、P27 は in_progress です。この依存は、P28 パッケージを最終的に完了と判定するときの前提です。P28-02 の診断と作業を止める理由にはしません(既存の、人間による順序変更の許可を保持します)。P27 が完了したとは主張しません。
  • 分母は 30/88 で、部分点はありません。現在の正式完了は 23/88、パッケージは 4/30 です。予算の false 6件(R0 の2件、P1 の2件、P2 の2件)は、そのまま保持します。

2. 事実と不足(数値を結びつけない)

  • 実測(P28-01 から引き継ぐ。それぞれの区間の値で、互いに結びつけない):
    • full9 の R: kernel の process_real_delta の1回の呼出し(frame)が、P1 x1 で平均 479.210 ms、P2 x3 で P95 5062.697 ms。
    • r02(headless・Ops OFF・x1。別の区間): 外側の1分 advance が平均 826.6 ms、外側の snapshot が 77.6 ms(count 5)、保存は約 300 ms、読込は約 3.2 s。
  • source から確定していること: 毎 tick の PUBLISH_SNAPSHOT で create_snapshot() が呼ばれます(autoload/simulation_runtime.gd:12616-12621)。ただし、r02 の 77.6 ms を、P1 の 479 ms の中の同じ呼出しの固定の費用として扱うことはしません(区間も条件も違うため)。
  • 不足: 現在の source での、tick の中の phase ごとの wall 区間、snapshot・検証・コピーの内訳、経路の費用。推測で埋めません。

3. 段階A: P28-02-DIAG(診断。製品は変更しない。原本の加算は0)

3.1 live の状態の扱い

  • live の Runtime の状態を変えてよいのは、次の2つだけです。それ以外の、状態を変える API の呼出し、製品の hook や関数の差し替えは禁止です。
    • fixture の最初の導入(restore_snapshot で入力 R を読み込む)。
    • 決められた GameClock.advance_minutes(1) による進行。
  • 公開されている読取だけの API(create_snapshot() など)は、tick の外で呼んでかまいません。その後、live の全状態の var_to_bytes が、呼ぶ前と完全一致することを確かめます。一致しなければ失敗です。

3.2 tick の中の区間(受動的な観測。scripts/core/simulation_kernel.gd:122-157)

  • tick_started(m) で tick の ID と、進行の前の分 m を記録します。
  • APPLY_COMMANDS の tick_phase_started は、進行の前の分 m で発行されます。TIME_BOUNDARIES 以降の phase と tick_completed は、進行の後の分 m+1 で発行されます。phase を分で区切らず、tick の ID で結びつけます。
  • 区間は、隣り合う信号の間の inclusive な wall 区間として記録します。各 handler の純粋な排他時間だとは主張しません。区間ごとの内容は次のとおりです。
    • I0: tick_started → APPLY_COMMANDS の開始
    • I1: APPLY_COMMANDS → TIME_BOUNDARIES(clock の進行、_sync_calendar_fields、_handle_time_boundaries を含む)
    • I2〜I10: TIME_BOUNDARIES → MANA → … → PUBLISH_SNAPSHOT の各開始の間
    • I11: PUBLISH_SNAPSHOT → tick_completed(create_snapshot、同期の signal である snapshot_published / state_updated の受け手、snapshot_version の更新を含む)
  • observer 自身の費用: 各 callback の中で、自分の時間を別に計測して記録します。区間からは差し引きません(二重に数えないために、区間は「observer を含む」と明記します)。
  • 経路: PathService.path_query_measured(duration_us)(scripts/world/path_service.gd:4、:985)に受動的に接続し、query の件数と時間を、その時点の区間に結びつけます。これは query の費用だけで、connector の更新や再構築の費用は含みません。query の時間を、connector 全体の費用へ広げて解釈しません。

3.3 tick の外の内訳(独立した inclusive の計測。合算して100%にしない)

  • snapshot の変換: live で create_snapshot() 全体を N=5 回、各 service の create_snapshot() をそれぞれ N=5 回、別々に計測します。各値は独立した inclusive の計測で、排他的な内訳として足し合わせることはしません。N=5 は r02 の試行数に揃えたもので、R で1回約 80 ms のため負荷が小さいことが根拠です(製品の値ではありません)。
  • 検証・コピー・保存・読込: live の状態は使いません。使い捨ての scratch で、公開されている restore・save・load・validator だけを使います。
    • scratch に渡すのは、live の snapshot の deep copy です。保存先は private user:// の scratch の path です。
    • 各操作を N=5 回計測します。
    • 終わった後に、live の全状態が一致すること、scratch から live への参照の漏れや副作用が無いことを確かめます。
    • 公開の API だけで scratch を作れない場合(autoload に触れる必要がある場合)は、その項目を理由つきの未測定にして、判断に戻します。
  • 粒度: 製品の内部で分けられない変換・検証・コピーは、測れる粒度(たとえば「restore 全体」)のまま記録します。分けたことにはしません。
  • 値: 正当に測れた 0 は 0 として記録します。測れなかったものは null と理由にします。欠損を 0 で補うことは拒否します。

3.4 条件と順序

  • 入力・engine・machine は P28-01 の R と同じです(入力 eb4f7700…、engine c7cccbf8…、Mac16,10)。headless で実行します。
  • 区間: warm 30分、測定30分。別の PID で3回行います。3回の間で、区間の順位が一致したかを記録します。
  • 実行の順序(P08/P18 の通常の方式を再利用する):
    1. worker が S を提出します(局所の unit 試験の全件 PASS つき)。
    2. Root が、凍結した S を隔離した stage で、import・parse・DIAG の3 PID を native で実行します。
    3. Root が raw を結合し、bundle を作ります。
    4. 登録した validation(unit と、bundle に対する native-verify の両方)を実行します。
    5. 別の新しい Opus が、全4条件と全 raw をレビューします。
    6. prepare → 候補の source と測定した S の一致の資格確認 → Root の委任による承認 → integration → final。
    • native は、測定した実際の S のものとして記録します。最終候補 Q で測ったとは表記しません。資格確認で、Q と S の source・設定・engine・入力が一致することを確かめます。

4. 段階B: P28-02-OPT(DIAG の後に、根拠をもとに登録する)

  • 対象: DIAG で実測の上位になったもののうち、原 work の4つの分類(snapshot の変換・検証・コピー・経路接続)に入るものだけです。選ぶのは管制で、DIAG の表を根拠に記録します。上位が4分類の外にあった場合は、原 work の範囲の外なので、判断に戻します。
  • 同等性を先に固定します(変更の前に、試験として凍結する):
    • E1 全状態:
      • まず var_to_bytes の完全一致を確かめます。
      • 一致しない場合に限り、RESTART の修正8で確定した完全な typed の正規表現と比べます。これは、dict の key を文字で並べ(衝突は拒否)、全ての key と値の typeof と配列の順序と値を保つものです。それで一致すれば「dict の列挙の順序だけの差」として、その path を記録します。
      • それ以外の差は不一致です。SimulationDigest だけでの判定は不可です。
    • E2 全イベント:
      • DomainEventStream.subscribe(&"*", &"p28_02_equivalence_observer", …)(scripts/core/domain_event_stream.gd:5、:17、配送は :76-85)で受動的に記録し、全件を上限なく、順序・型・payload・ID・counter とともに保持します。
      • subscriber の登録が snapshot を変えないこと(create_snapshot の範囲の外)を確かめ、終わったら unsubscribe します。
      • recent_events(直近256件)の一致だけを、全イベントの一致として扱うことはしません。
    • E3 失敗時の拒否: 不正な入力(改ざん、型の違い、欠けた key、範囲外、重複した ID)が、変更の前と同じ code で、原子的に拒否されること。
    • E4 キャッシュの古さ: 無効化の全ての経路(コマンド、tick、restore、load)で、古い値を返さないこと。
    • E5 所有権: 公開した値を外から変更しても内部に影響せず、その逆も無いこと。
  • 比較は2種類に分けて固定します:
    • (a) phase の比較: DIAG と同じ区間(headless、advance_minutes)で、変更の前と後を別の PID で各3回。advance_minutes では performance_budget.record_simulation_frame が呼ばれず、_samples が空になるため、この比較では kernel の予算の値を使いません。
    • (b) frame の予算の比較: 製品の経路 process_real_delta を通る、受入済みの collector の windowed の P1(x1)と P2(x3)を、変更の前(現在の Q)と後で、別の PID で各3回。kernel 定義の標本で比べます。
  • RESTART の native の pair を1回行い、保存と再開の再現が保たれることを確かめます。製品の source が変わるので、最終候補で正式の全 roster を1回実行します。
  • 予算は P28-02 の受入の条件ではありません。実測をそのまま記録し、false は合格に補いません。同等性 E1〜E5 が1つでも崩れた変更は採用しません。
  • 独立レビュー: 原 work と原 acceptance、横断条件6件、補助の U1/U2 を、whole-unit として判定します。

5. 共通の制約

  • 予算値、原本の分母、横断条件の原文は変更しません。Runner 本体、権限、認証、課金も変更しません。修正回数の上限は null で、API の課金はしません。容量待ちは再導入しません。