EQ_ENVIRONMENT_FIELDS_CLARIFICATION r03（P28-02-EQ repair 2。base 2549cf7d6992560b850bbd11934e3c14b39a1988。EQ_S_TIMING_CLARIFICATION r01 と r02 の全文は、そのまま有効で、書き換えない。r02 の T1（閉じた対象）を、ここで閉じた一覧の F1〜F3 に広げて確定する。SPEC、TASK、6条件、eq/references、eq/registration、製品、原本の 30/88、権限、課金は変更しない）

範囲（静的に一括して点検したもの）
- Runtime: restore_snapshot（:587）、load_from_path（:4223）→ _finish_load_operation（:4683-4733）→ _complete_save_operation（:4734）→ _save_event_payload（:4745-4768）→ _publish_actor_event → domain_events（S の systems.runtime.domain_events.recent_events、E2 の記録）。
- SimulationSnapshot.validate。
- SaveService: save_snapshot（:29）、load_snapshot（:49）、_resolve_safe_save_path（:1684-1706）、_load_snapshot_absolute_v1（:946-1042）、_write_package_directory の manifest（:901-917）、_complete_timed_operation（:1751-1761）、_failure（:1956）。

根拠表（源泉 → 返却 → payload → S/E2）

| 欄 | 源泉 | S/E2 に入るか | 決定性 | 扱い |
|---|---|---|---|---|
| duration_us | SaveService._complete_timed_operation（:1756-1760） | 入る（payload.duration_us） | 実時間 | F1 |
| runtime_duration_us | Runtime._finish_load_operation（:4717） | 入る（payload.runtime_duration_us） | 実時間 | F2 |
| message（失敗の場合） | SaveService._failure(code, message)。target_path・world_path・manifest_path（:957、:963、:1011 ほか）と、"symbolic link at package read boundary: %s" などに、_resolve_safe_save_path が globalize した private profile の絶対 path が入る | 入る（payload.message） | PID ごとに profile の root が違う | F3 |
| message（その他） | error_string(...)、検証の errors の結合、format_version の文言、Runtime の RESTORE_FAILED の message（= 渡した save_path） | 入る | 決定的（path を固定すれば） | 正規化しない |
| save_path | Runtime の _with_operation_context の save_path（渡した user:// の文字列） | 入る | 決定的（下の D1） | 正規化しない |
| save_id | manifest の save_id = metadata.save_id か logical_name（:905） | 入る（load の成功の場合） | 決定的（下の D1・D2） | 正規化しない |
| saved_absolute_minute | manifest の absolute_minute | 入る | 決定的 | 正規化しない |
| ok、code、operation、reason、request_id、checkpoint_absolute_minute、loaded_clock_paused、preload_pause_restored | Runtime と SaveService の戻り値 | 入る（一部は payload の外の result だけ） | 決定的 | 正規化しない |
| created_at_real、updated_at_real | manifest の now = Time.get_datetime_string_from_system（:901、:907-908） | **入らない**（_save_event_payload は manifest から save_id と absolute_minute だけを取る） | 実時刻 | S/E2 の比較の対象外。package のファイルの記録として保持する |
| manifest の他の欄、thumbnail、journal | package のファイル | 入らない | — | S/E2 の比較の対象外。E3 の改ざんのファイルとして SHA を記録する |
| SaveService._performance_budget | 静的な診断 | 入らない（S/K/C の外。DIAG の low の注記） | 実時間 | PID ごとの前後の値を記録し、比べない |
| latest_save_operation_result | Runtime の変数 | 入らない（create_snapshot の外） | F1〜F3 を含む | 同じ PID の中で payload との一致を確かめる |

閉じた一覧（r02 の T1 を置き換える。これ以外は正規化しない）
- 対象の event: source_service が "save_service" で、event_type が9種（SAVE_CHECKPOINT_SUCCEEDED、SAVE_CHECKPOINT_FAILED、SAVE_LOADED、SAVE_LOAD_FAILED、SAVE_MIGRATION_SUCCEEDED、SAVE_MIGRATION_FAILED、SAVE_RECOVERY_LOADED、SAVE_RECOVERY_CANDIDATE_LOADED、SAVE_RECOVERY_FAILED）のいずれか。その payload の中だけ。
- F1 duration_us、F2 runtime_duration_us: r02 の T2 のとおり、typed の marker に置き換えて、sidecar に値を記録する。
- F3 message（文字列）の中の、private profile の絶対 root:
  - root は、その PID の ProjectSettings.globalize_path("user://").simplify_path() の値。これが、その PID の guard の receipt の actual_user_data_dir と一致することを確かめる。一致しなければ失敗にする。
  - message の中の、その root の文字列（末尾の "/" を含む prefix として現れる箇所すべて）を、固定の token "{PRIVATE_USER_ROOT}/" に置き換える。元の message の全体を、sidecar に記録する。
  - 逆の復元: token をその PID の root に戻すと、元の message と完全一致すること。
  - 置き換えた後の message は、PID の間・版の間で完全一致すること。
  - root 以外の部分（相対 path、code、文言）は、変更しない。

決定性の前提（harness の側で守ること。製品は変えない）
- D1: E3/E4/E5 が使う user:// の path は、全て固定の相対名にする（例: user://p28_eq/r/good、user://p28_eq/r/tampered_world）。uuid、時刻、PID、乱数を名前に入れない。
- D2: save に渡す metadata は、固定の値か空にする（save_id、display_name、created_at_real を時刻や乱数から作らない）。
- D3: 改ざんの方法は、固定の規則にする（RESTART と同じく、world.bin の offset len//2 の1バイトを XOR 0x01）。

検査と試験
- 同じ PID の中: F1〜F3 の正規化の前後で可逆であること。正規化の対象が、閉じた一覧の中だけであること。
- PID の間と版の間: 正規化の後の S、latest_snapshot、E2 の全イベントが完全一致すること。F1・F2 の値と、F3 の元の root は比べない（理由と各側の実際の値を記録する）。
- 閉じた一覧の外で PID の間に差があった場合は、正規化を広げない。失敗として、その path・型・両側の値を全て記録し、判断に戻す（native の実測で初めて分かる場合も、同じ扱い）。
- 試験（test_eq.py）を加える:
  - F3 の正常（root の置き換えと復元）
  - root が guard の actual_user_data_dir と一致しない場合の拒否
  - root 以外の部分を置き換える場合の拒否
  - 閉じた一覧の外の欄や event の正規化の拒否
  - D1 の違反（uuid や時刻を使った path）の拒否
  - manifest の時刻を S の比較に入れる場合の拒否
  - 復元できない sidecar の拒否
- 製品の状態、RNG、ID の counter、意味のある値は、正規化しない。

worker への手順
- 前回までは実装0なので、SPEC・TASK・r01・r02・この r03 に従い、eq/** に6ファイルを実装する。SPEC、TASK、eq/registration、eq/references は変更しない。
- 静的に分かる環境由来の差は、この表で閉じている。新しい欄に気づいた場合も、短く止めずに、上の「閉じた一覧の外」の規則（失敗として記録して判断に戻す）を harness に実装して進める。blocked にしてよいのは、原本の仕様・値・権限・課金・受入に影響する未確定事項がある場合だけ。
- engine を使わない test_eq.py の全件 PASS を局所で確かめ、生出力を報告し、正式な JSON で提出する。engine の起動、git への書込み、製品の変更、全回帰はしない。
