Finite acceptance RS1-RS8 from the preserved draft (byte-identical DRAFT.md),
pinned to the collector final Q and the integrated full9 receipt on db9d613.
Docs only, outside the product source map; nothing registered or run.
Original credit unchanged (22/88, 4/30).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 KiB
11 KiB
P28-01-RESTART 最終草案(未登録・未実行)
- 状態: Desktop 管制による草案です。collector の final が出るまで、登録、worker の起動、engine の実行はしません。
- 目的: 原 P28 の横断条件「途中保存・再起動で再現」を、R と O のそれぞれについて、writer と reader を別PIDにした1組で示します。性能の数値はここでは比べません。
- 原本の扱い: collector だけでは原本に加算しません(22/88 のまま)。P28-01 の加算には、次の4つがすべて必要です。
- collector の final
- 実測
- RESTART の pair の PASS
- 証拠ゲート
- 予算の未達、200客での Operations ON、Windows は、未達のまま残します。
1. 範囲と入力
| writer(PID A) | reader(PID B、別の engine プロセス) | |
|---|---|---|
| R(602分、Ops OFF) | SPEC §4.3 と同じ起動(autoload への復元 → main の追加 → _set_speed(1))。実際の clock 経路で10ゲーム分、または simulation_overloaded までの短い方を進め、clock が止まった状態で保存する |
新規開始で main を追加(_ready の Ops 開始結果をそのまま記録)→ UI の読込で R の保存を読む |
| O(新規開始、Ops ON) | 新規開始の main で実際の x1 を進める。最初の実際の arrival(下の §4)が起きた後の最初の分で、clock を止めて保存する。上限は120ゲーム分 | R と同じ |
- 保存には、main の実際の手動保存
main._save_manual_at_path(path, name)を使います(main.gd:2243 →save_to_path:4195 →_perform_checkpoint_save)。 - 読込には、main の実際の読込
main._load_manual_at_path(path, true)を使います(main.gd:2288 →load_from_path:4223)。 - 保存先は private user:// だけで、通常 player の保全を前後で照合します。
2. 成否の取り方(_save_manual_at_path は bool、_load_manual_at_path は void)
-
_save_manual_at_path(main.gd:2243)は bool を返し、_load_manual_at_path(main.gd:2288)は void です。 -
保存の bool 戻り値は記録し、signal の
okと一致することを要求します。ただし、bool の true だけで成功とはしません。 -
保存も読込も、成否は下の signal、
latest_save_operation_result、実状態の3つで判定します。 -
呼出しの前に、
SimulationRuntime.save_operation_completed(:9)へ受動的に接続しておきます。 -
成否は、次の3つがそろったときに成功とします。
- signal の result(ok、code、operation、save_id、saved_absolute_minute)
- 直後に読んだ
latest_save_operation_result。§3 の IO イベントの payload と一致すること - 実際の状態。clock の
absolute_minuteが saved_absolute_minute と一致し、§3 の比較が成立すること
-
読込の void 戻り値も、保存の bool 戻り値の true も、それだけでは成功とみなしません。signal が来なければ、失敗として原記録を残します。保存の bool と signal の ok が食い違えば、不整合として失敗にします。
-
latest_save_operation_resultはcreate_snapshot()(simulation_runtime.gd:521-583)に含まれません。そのため状態比較の対象にはせず、別欄に記録して IO イベントの payload との一致だけを確かめます。
3. IOイベントの狭い対応規約(広い削除は禁止)
3.1 事実
- snapshot の中の場所
systems.runtime.domain_events = {version, published_ids, recent_events}(scripts/core/domain_event_stream.gd:96-112)published_idsは、これまでに公開した全 ID を文字列で sort した配列で、上限はありません。recent_eventsは直近の最大256件(RECENT_EVENT_LIMIT、:6)。append のとき、256件を超えると先頭が1件落ちます(:58-60)。
systems.runtime.id_generator.counters["domain_event"](キーは String、値は int)
- 正式な保存と読込の完了時には、
_complete_save_operation(:4734)が_publish_actor_event(:12558)を1回だけ呼びます。このとき採番される ID は1つで、E = "domain_event.%012d" % (c + 1)です(id_generator :13-18)。ここで c は package の snapshot P の counter です。- writer のイベントの型は
SAVE_CHECKPOINT_SUCCEEDED、reader はSAVE_LOADEDです。 - どちらも
source_system=save_service、entity_ids=[]、payload は_save_event_payload(result)です。 - writer と reader は同じ P から採番するので、E は同じ ID になります。
- writer のイベントの型は
3.2 期待状態の構成と、全状態の完全一致
除外して合わせるのではなく、P に IO イベント ev を1件だけ規則どおり適用した期待値 X(P, ev) を作り、実際の snapshot 全体と完全一致(typed var_to_bytes)させます。
id_generator.counters["domain_event"]= P の値 + 1。他の counter はすべて P と同じ。published_ids= sort(P.published_ids ∪ {E})。E は P に含まれていてはいけません。recent_eventsは P.recent_events の長さで分けます。- 256 未満のとき: P.recent_events に ev を末尾に足したもの
- 256 のとき: P.recent_events の先頭1件を落とし、ev を末尾に足したもの。落ちた先頭1件は、全内容を原記録に残します。
- ev は、event_id が E、型・source・entity_ids・minute は実際の値で、payload は signal の result から作ったもの(=
latest_save_operation_result)です。 - 上記3つのパス以外は、P とバイト単位で同じでなければなりません。
3.3 比較の種類
| 比較 | 規約 |
|---|---|
a. writer の保存直前 create_snapshot() と P(SaveService.load_snapshot(path) で読んだもの。runtime は変えない) |
完全一致。不一致なら、差のあるパスの全一覧を残して止め、判断に戻す |
b. writer の保存直後と X(P, ev_save) |
完全一致 |
c. reader の読込直後と X(P, ev_load) |
完全一致 |
| d. 継続 K 分の各分 t について、writer_t と reader_t | 全状態の完全一致。違ってよいのは、recent_events の中の event_id E の要素1件だけです。E がまだ直近256件の窓の中にある間は、reader 側のその要素を writer 側のものに置き換えた状態が、writer_t と完全一致しなければなりません。窓から落ちた後は、置き換えなしで完全一致です。published_ids と counter は、置き換えなしで一致します。置き換えた両方の要素は全内容を記録します |
- digest は
SimulationDigest.computeで計算し、上の比較が成立した状態について記録します。digest 同士の一致だけで比較の代わりにはしません。 - 規約の外で違う箇所が1つでもあれば失敗です。差のあるパスの一覧を返します。規約を広げるには、新しい判断が必要です。
4. Operations の境界と、実際の進行
- O の writer は、390分の時点で次のものを記録します。
- 実際の clock
get_operations_report()(coordinator の current_minute、population)- 処理済みの arrival 分
- 実際に来館した guest-auto の数
- その間の domain_events
- 保存できるのは、実際の arrival(来館数 > 0)が起きた後だけです。境界に到達しただけで、arrival を処理したことにはしません。120分までに arrival が起きなければ
no_arrival_within_capとし、Ops 境界の項目は null と理由にします。 - 継続 K:
- 各側で clock を止めた状態にして、
advance_minutes(1)を1回ずつ呼びます(止めた状態での手動の stepping、simulation_kernel.gd:104-119)。 - 呼出しの前後で
absolute_minuteを照合し、進まなければstop_reason=no_tick_advanceで止めます。 - 実際の K は、実際に進んだ分の数です。両側の実際の K が一致することを要求し、K=30 のときだけ30分と表記します。
- 各側で clock を止めた状態にして、
5. 異常と境界(最小限)
- 改ざんした保存の原子的な拒否: package のコピーで
world.binの1バイトを反転させます。UI の読込が拒否され、試行の前後で runtime の全 snapshot が完全に一致することを確かめます。 - 止まった状態での保存(R): overload で止まった writer の保存は、読込後の reader も止まった状態であること、backlog を消費しないことを確かめます。
- Ops の境界をまたぐ(O): 継続 K の d. の比較が、次の arrival 境界で重複も欠落もないことを示します。
6. 静的な禁止検査(collector の check_probe は丸ごと import しない)
| 区間 | 許可するもの | 禁止するもの | 用途 |
|---|---|---|---|
| UI 区間(起動、実際の clock、UI での保存と読込) | _set_speed、_save_manual_at_path、_load_manual_at_path、受動的な接続 |
advance_、set_paused(false)、clear(、spawn_guest、SimulationRuntime.new、initialize(、enable_production_ |
状態と IO の観測だけ。性能値は出さない |
| 決定性区間 | 単一の func deterministic_step() の中の GameClock.advance_minutes(1) だけ。呼び出せるのは UI 区間が終わって clock が止まった後だけ |
Time.get_ticks_usec と *_us の統計欄、および上の UI 区間の禁止 token(許可した1つを除く) |
writer と reader の比較だけ。性能・予算・速度には使わない |
checker の拒否試験は、少なくとも次のものです。
deterministic_stepの外での advance- 決定性区間から出た timing 値
- 呼出し回数で K を数えること
- 規約外のパスの除外や置き換え
- cap が256のときに「末尾1件の除外」として扱うこと
- E 以外のイベントの置き換え
- 読込の void 戻り値や、保存の bool の true だけで成功とみなすこと
- 保存の bool と signal の ok の食い違いを見逃すこと
- 境界に到達しただけで arrival を処理したとみなすこと
- signal が来ていないのに成功とすること
7. 実装と実行
- 置き場所:
docs/plans/original-completion-2026-10-02/execution/p28-01/restart/**(source map の外)。 - 前後で source map が1407件のまま一致することを照合し、正式全192の証拠を維持します。製品を変えた場合は全 roster をやり直す必要があり、旧証拠は流用できません。
- 登録: collector の baseline C1〜C9 は凍結されているので、別の小さな project と task(
P28-01-RESTART)として登録します。- spec_ref はこの草案を Git で追跡する文書にしたものです。
- worker は通常の Runner worker、レビューは独立した Opus です。
- 統合済みの collector の owned-phase の実行、guard、private profile は、読取専用で import します。
- 実行: Root が、実測と並べて R に2プロセス、O に2プロセスを、owned PID と raw の結合つきで実行します。