EQ_STAGED_IMPLEMENTATION_PLAN r02（P28-02-EQ repair 3 以降。EQ_STAGED_IMPLEMENTATION_PLAN（r01）の工程 G1〜G6、段階 S1〜S4、Root への依頼は、そのまま有効。この r02 は、固定インターフェースのうち、状態とイベントの運搬の表現と、normalize_events の返り値を置き換えて確定する。SPEC、TASK、6条件、eq/references、eq/registration、補足 r01/r02/r03 は変更しない）

A. 型を保つ運搬の表現（typed tree。JSON は運び方であって、型の情報は落とさない）

A1 node の形式（JSON の配列）
- 各値は [type_code, payload]。type_code は Godot の typeof() の値。
  - null: [0]
  - bool: [1, 0|1]
  - int: [2, 整数]
  - float: [3, "<8 byte little-endian IEEE754 の hex>"]（10進の文字列にしない）
  - String: [4, "文字列"]
  - StringName: [21, "文字列"]
  - Vector2/Vector2i/Vector3/Vector3i/Rect2/Rect2i/Color など: [type_code, [各成分の node]]（成分は int か float の node）
  - Dictionary: [27, [[key_node, value_node], …], typed_info]。**元の挿入の順序のまま**並べる。typed_info は、型つきの Dictionary なら key と value の builtin 型・class 名・script、それ以外は null。
  - Array: [28, [element_node, …], typed_info]。typed_info は get_typed_builtin()・get_typed_class_name()・get_typed_script() の値、型の無い Array なら null。
  - PackedByteArray: [29, "<base64>"]。他の Packed*Array も、要素ごとの node の列にする（float は hex）。
- Object、Callable、Signal、RID が現れたら、運搬せずに失敗にする（snapshot と event の dict には、本来現れないもの）。

A2 無損失の証明（Godot の中で行う。probe の必須の処理）
- tree = encode(value) を作った後、decode(tree) で Variant を作り直す。var_to_bytes(decode(tree)) == var_to_bytes(value) であることを確かめる。一致しなければ失敗にする。
- report には、checkpoint ごとに、次の3つを記録する。
  - raw_bytes_sha = sha256(var_to_bytes(value))
  - tree_sha = sha256(tree を key の順序を変えずに JSON.stringify した UTF-8)
  - roundtrip_ok
- Python は、受け取った tree から tree_sha を計算し直して結合する。これで、Python で比べる tree が、Godot の元の bytes と無損失に対応することが保証される。

A3 保存の場所と大きさ（深さの制限は設けない。全体を再構成できるようにする）
- 各 PID は、checkpoint の列（B1 と、30分の各 tick の最後）について、S 層と latest_snapshot を、次の形で、private user:// の evidence/ に書き出す。driver は、それを RESTART の copy_states と同じ方式で out へ複製し、SHA を結合する。
  - checkpoint 0（B1）: tree の全体（JSON、zstd で圧縮してよい。圧縮した場合は、圧縮の前後の SHA を両方記録する）。
  - checkpoint k（k≥1）: checkpoint k−1 の tree に対する、path を指定した差分の操作の列。操作は set（path, node）、dict_insert（path, index, key_node, value_node）、dict_remove（path, key_node）、array_splice（path, start, delete_count, [node…]）。順序つきで、再構成すると、checkpoint k の tree と完全一致すること。
  - latest_snapshot は、各 checkpoint で、var_to_bytes が S 層と同じなら same_as_S=true とし、違えば、その tree（または S に対する差分）を保存する。
- Python は、checkpoint 0 から差分を順に適用して、全ての checkpoint を再構成する。checkpoint ごとに tree_sha と一致することを確かめる。

A4 比較（compare_versions.py。全て tree の上で行い、深さの制限は無い）
- 第一の比較は、tree の完全一致（型、key、順序、値。全ての node）。
- 一致しない場合に限り、正規化した比較を行う。dict の key を文字で並べ（同じ文字の key が2つあれば拒否）、それ以外の型・値・配列の順序を保ったまま比べる。それで一致すれば「dict の列挙の順序だけの差」として、その全ての path を記録する。それ以外の差は、全ての path と両側の node を記録して、不一致にする。
- F1〜F3（r03）の正規化は、tree の node の上で行う。sidecar から、元の tree へ完全に戻せること（tree_sha が一致すること）を確かめる。

B. E2 のイベントの運搬
- 各 event について、event.to_dictionary()（製品の直列化。scripts/core/domain_event.gd:47-59）の tree を記録する。あわせて、元の object の欄の型（event_id と event_type が StringName であるか、entity_ids の型、payload の dict の typed_info）を、original_field_types として記録する。
- 受け取りの順序で番号をつけ、開始の時点の counter と、published_ids の数を記録する。
- イベントの列は、A3 と同じく evidence/ に書き出し（tree の列。圧縮してよい）、SHA を結合する。Godot の中で、各 event について A2 の roundtrip を確かめる。
- compare_reports は、この tree の列を受け取り、全ての欄と event_id を**そのまま**比べる（ID の付け替えはしない）。F1〜F3 は、payload の該当する node にだけ適用する。

C. normalize_events とその仲間の返り値（固定）
- normalize_events(events, profile_root) -> (normalized, sidecar)。返り値は2つの要素のまま。
  - normalized: tree の列。閉じた一覧（r03）の node だけを marker に置き換えたもの。
  - sidecar: dict で、key は次の3つに固定。
    - "entries": [{"event_index", "path", "key", "original_node"}…]。F1〜F3 で置き換えた全ての node の元の値。F3 の場合は original_node に元の message の文字列を入れ、"root" に、置き換えた root を入れる。
    - "violations": [{"event_index", "path", "kind", "node"}…]。閉じた一覧の外にあった、*_us / *_ms の key、profile_root を含む文字列など。**存在しただけでは不合格にしない**。正規化はせずに、そのまま保持して記録する。
    - "profile_root": 使った root の文字列と、guard の actual_user_data_dir との一致の結果。
- restore_events(normalized, sidecar, profile_root) -> events: entries から元に戻す。violations の node は、もともと変えていないので、そのまま。
- 状態の tree にも、同じ形の normalize_tree(tree, profile_root) -> (normalized, sidecar) と restore_tree を用意する。
- r03 の規則は、そのまま。violations にあるものを正規化しようとしたら拒否する。violations にあるものが、比べる2つの側で実際に異なれば、その差は不一致（失敗）として、path と両側の値を記録する。

D. 段階への影響
- S1（今回）: compare_versions.py に、A4 の比較、C の4つの関数、A3 の差分の再構成、tree_sha の再計算、check_source_closure を実装する。試験は合成の tree で行い、次のものを確かめる。
  - 型の違い（int と float、String と StringName、型つきの配列）を区別すること
  - dict の順序だけの差の path
  - 差分の再構成の完全一致
  - sidecar からの逆の復元
  - violations の扱い（存在だけでは不合格にしない、正規化しようとしたら拒否、差があれば不一致）
- S2: eq_probe.gd に、A1 の encode と decode、A2 の roundtrip、A3 の書き出し、B の記録を実装する。
- S3 と S4 は、r01 のとおり。
