EQ_S3_STAGE_PLAN r02（P28-02-EQ。EQ_S3_STAGE_PLAN（r01）の分け方（S3a = checker の2点の修正 + E3、S3b = E4/E5）、共通の規則、S3b、その後の順序は、そのまま有効。この r02 は、実際の API に合わせて、S3a の「checker の修正 2」と「E3」を置き換えて確定する。SPEC、TASK、6条件、eq/references、eq/registration、補足 r01/r02/r03、段階の計画 r01/r02 は変更しない）

1. checker の修正 1（eq/check_eq.py:109）: r01 のとおり（phase の key の集合が正確に {0..10}、負の試験）。

2. checker の修正 2（eq/check_eq.py:147-150。r01 を置き換える）
- probe は、自分の observer を snapshot_published に接続している。そのため、製品の _publish_snapshot（autoload/simulation_runtime.gd:12616-12621）は、毎 tick、snapshot_published を出してから state_updated を出す（:12619 の条件が真になる）。
- 測定の区間の30 tick の各分について、**ちょうど1組の snapshot_published → state_updated** を要求し、その分の checkpoint の minute と結びつける。
- 測定の開始の時点で、snapshot_published と state_updated の接続の一覧（get_connections() の、接続先の object と method）を記録し、その中に probe 自身の接続があることを要求する。probe の接続が無い場合は、期待を「state_updated だけ」に下げることはせず、失敗にする。
- 製品で受け手が接続されていない場合の動作（state_updated だけ）は、この区間の対象の外とする。試験もしない。
- scenario の区間で出た signal は、別の区間の記録に入れ、測定の区間には混ぜない。
- 負の試験を加える:
  - 欠落、一部の分だけ、重複、順序の改ざん、分のずれ
  - scenario の signal の混入
  - probe の接続が一覧に無いのに合格にすること
  - 期待を state_updated だけに下げること

3. E3 失敗時の拒否（専用の scenario の区間。r01 を置き換える）

3.1 各 API の実際の戻り値（架空の code は作らない）
- SimulationRuntime.restore_snapshot(Dictionary) -> bool（:587）: 戻り値の bool を、そのまま記録する。code は null とし、理由を "API returns bool only" とする。
- SimulationSnapshot.validate(Dictionary) -> {ok, errors}（scripts/core/simulation_snapshot.gd:16-43）: 返った dict の全体（ok と、errors の全件）を、そのまま記録する。code は null とし、理由を "API returns ok/errors only" とする。
- load_from_path(String) -> Dictionary（:4223）: 実際の code を記録する。この API で code が欠けていれば、それは失敗として拒否する（上の2つの「API に code が無い」とは区別する）。

3.2 不正の分類と、それを適用する API（source に基づく表。適用しないものは、理由つきの NA として記録し、PASS に補わない）

| 分類 | restore_snapshot（Dictionary） | validate（Dictionary。envelope の外形だけを検査） | load_from_path（package） |
|---|---|---|---|
| 欠けた key | 適用: B1 の deep copy から clock を削除 → false を期待 | 適用: clock を削除 → errors に missing_clock を期待 | 適用: package の manifest.json から checksum_world を削除 → INVALID_MANIFEST を期待 |
| 違う型 | 適用: clock.absolute_minute を文字列 "x" に → false | 適用: 同じ → errors に invalid_absolute_minute | 適用: manifest.json の format_version を文字列に → INVALID_MANIFEST |
| 範囲外の値 | 適用: clock.speed を 9 に → false | 適用: 同じ → errors に invalid_speed | 適用: manifest.json の format_version を、対応していない大きな整数に → UNSUPPORTED_FORMAT（自動の migration を試みた場合は、その実際の code と、migration_attempted を記録） |
| 重複した ID（published_ids） | 適用: systems.runtime.domain_events.published_ids に、既存の ID を1つ追加 → false（DomainEventStream.restore_snapshot、scripts/core/domain_event_stream.gd:113-140 が拒否） | NA: validate は envelope の外形だけで、runtime の内部を検査しない（:16-43）。実際の戻り値（ok:true を予想）は記録するが、不具合とはしない | 適用（公開の API で作れる場合）: 重複を入れた snapshot を SaveService.save_snapshot で、固定の user:// の path に保存し、load_from_path で読む。save が拒否した場合は、その実際の code を記録し、load の側は理由つきの NA にする |
| 重複した ID（facility） | R で適用: 施設の instance の配列で、最初の entry を複製 → false（FacilityService.restore_snapshot、scripts/facility/facility_service.gd:701-703 が拒否）。O で NA: B1 の施設の件数が実際に0のとき、複製する既存の entry が無いため（件数を記録する）。施設を建設すること、R の B1 で代用すること、製品の値を作ることはしない | NA: 上と同じ理由 | R で、published_ids と同じ方法で適用。O で NA（理由は同じ） |
| world.bin の改ざん | NA: Dictionary の API は package を読まない | NA: 同じ | 適用: r03 の D3 の固定の規則 → CHECKSUM_MISMATCH を期待 |

- 不正な入力は、B1 の deep copy と、固定の user:// の相対 path と、固定の規則だけで作る。乱数・時刻・PID を使わない（r03 の D1〜D3）。
- 期待の結果は、上の source に基づくものとして記録する。実際の結果が期待と違う場合（たとえば、不正な入力が受理された）は、「既存の不具合の候補」として、全ての記録とともに残す。ただし、envelope の validate が runtime の内部の重複を受理することは、その API の範囲の外なので、不具合とは断定しない。

3.3 各操作で記録し、検査すること
- 前後の S/K/C 層（同じ PID の中で、完全に比べる）。拒否された場合は、変化が無いこと。
- 全イベントの差分と、その件数。restore_snapshot と validate は0件。load_from_path の失敗は SAVE_LOAD_FAILED がちょうど1件で、X(B, ev_fail) と、r03 の正規化の後で完全一致すること。
- 適用と NA の表を、そのまま report に入れる。fresh の whole-unit レビューが、適用の条件を判定できるようにする。

3.4 試験（test_eq.py）
- 正常の記録。
- 拒否の場合:
  - restore と validate の架空の code
  - load の code の欠落を合格にすること
  - NA を PASS として数えること
  - O の施設の重複を、件数が0なのに適用したと記録すること
  - 拒否の前後の状態の変化を見逃すこと
  - 件数の不一致
  - envelope の validate が重複を受理したことを、製品の不具合と断定すること

4. 終わり: r01 のとおり（PROGRESS.md を更新し、正式な JSON の status を blocked にし、findings を「段階 S3a 完了（失敗ではない。次は S3b）」とする）。
