From 89ca988ba28455c6211d6f5e89ee8e116a2e8d74 Mon Sep 17 00:00:00 2001 From: "david.yapfest.club" Date: Mon, 7 Sep 2026 21:55:54 -0400 Subject: [PATCH] test: verify presentation reload identity through replay --- .../hexwork-cli/tests/phase12/presentation.rs | 78 ++++++++++++++++++- .../tests/pty/presentation_scenario.py | 10 ++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/crates/hexwork-cli/tests/phase12/presentation.rs b/crates/hexwork-cli/tests/phase12/presentation.rs index 9d270cd..256e4ad 100644 --- a/crates/hexwork-cli/tests/phase12/presentation.rs +++ b/crates/hexwork-cli/tests/phase12/presentation.rs @@ -269,6 +269,45 @@ fn run_pty_scenario(harness: &Harness, mode: &str) -> serde_json::Value { serde_json::from_slice(&output.stdout).expect("small PTY JSON summary") } +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn assert_actual_durable_replay(harness: &Harness, label: &str, saved: &hexwork_save::DecodedRun) { + let final_tick = saved.current.tick; + let final_hash = saved.current.try_hash_state().unwrap(); + // Replay provenance is exclusively this decoded, actual durable living save: + // its complete identity, recorded event history, current tick, and current hash. + let replay = hexwork_save::ProductionReplay { + identity: saved.identity.clone(), + events: saved.events.clone(), + expected_final_tick: final_tick, + expected_final_hash: final_hash, + }; + let replay_path = harness + .root + .join(format!("presentation-{label}-actual.hexdemo")); + fs::write(&replay_path, hexwork_save::encode_replay(&replay).unwrap()).unwrap(); + let replayed = Harness::run({ + let mut command = harness.command(); + command + .arg("--content-root") + .arg(&harness.content) + .arg("--replay") + .arg(&replay_path); + command + }); + assert!( + replayed.status.success(), + "{label} actual-durable replay failed: stdout={} stderr={}", + String::from_utf8_lossy(&replayed.stdout), + String::from_utf8_lossy(&replayed.stderr) + ); + let stdout = String::from_utf8(replayed.stdout).unwrap(); + assert!(stdout.contains("HEXWORK verified event-text replay")); + assert!(stdout.contains(&format!( + "run {} tick {final_tick} hash {final_hash:#x}", + saved.identity.run_id + ))); +} + #[test] #[cfg(any(target_os = "linux", target_os = "macos"))] fn binary_presentation_override_waits_for_application_store_lock() { @@ -296,13 +335,50 @@ fn binary_presentation_override_waits_for_application_store_lock() { #[cfg(any(target_os = "linux", target_os = "macos"))] fn binary_pty_presentation_overrides_persist_and_reload() { let harness = Harness::new("presentation-persist"); + assert_eq!(harness.data.parent(), Some(harness.root.as_path())); let summary = run_pty_scenario(&harness, "persist-reload"); assert_eq!(summary["published_before_quit"], true); assert_eq!(summary["reload_completed"], true); assert_eq!(summary["bytes_unchanged"], true); assert_eq!(summary["mtime_unchanged"], true); - assert_eq!(summary["identity_unchanged"], true); + assert_eq!(summary["run_id_unchanged"], true); assert_eq!(summary["reload_status_has_values"], true); + assert_eq!(summary["first_rendered"], true); + + let pack = hexwork_content::RuntimePack::load(&harness.content).unwrap(); + let first = hexwork_save::decode_run( + &fs::read(harness.root.join("presentation-first-actual.run.save")).unwrap(), + pack.hash(), + ) + .unwrap(); + let second = hexwork_save::decode_run( + &fs::read(harness.root.join("presentation-second-actual.run.save")).unwrap(), + pack.hash(), + ) + .unwrap(); + + assert_eq!( + first.identity, second.identity, + "complete run identity changed" + ); + assert_eq!( + first.events, second.events, + "recorded event history changed" + ); + assert!( + first.events.is_empty(), + "presentation-only sessions must record no gameplay events" + ); + assert_eq!(first.identity.run_id, second.identity.run_id); + assert!( + second.current.tick >= first.current.tick, + "reload final tick {} preceded first-session final tick {}", + second.current.tick, + first.current.tick + ); + + assert_actual_durable_replay(&harness, "first", &first); + assert_actual_durable_replay(&harness, "second", &second); } #[test] diff --git a/crates/hexwork-cli/tests/pty/presentation_scenario.py b/crates/hexwork-cli/tests/pty/presentation_scenario.py index 423bac9..874205b 100644 --- a/crates/hexwork-cli/tests/pty/presentation_scenario.py +++ b/crates/hexwork-cli/tests/pty/presentation_scenario.py @@ -154,18 +154,22 @@ def main(): before_quit.append(True) first, _ = drive(binary, data_dir, content, explicit_args(), PLAY_CUE, published, b"\x1b") run_id = only_run_id(data_dir) - first_save = (data_dir / "runs" / run_id / "run.save").read_bytes() + run_save_path = data_dir / "runs" / run_id / "run.save" + capture_root = data_dir.parent + first_save_capture = capture_root / "presentation-first-actual.run.save" + second_save_capture = capture_root / "presentation-second-actual.run.save" + first_save_capture.write_bytes(run_save_path.read_bytes()) before_bytes = path.read_bytes() before_mtime = path.stat().st_mtime_ns second, _ = drive(binary, data_dir, content, ["--load", run_id], PLAY_CUE, lambda _: None, b"\x1b") - after_save = (data_dir / "runs" / run_id / "run.save").read_bytes() + second_save_capture.write_bytes(run_save_path.read_bytes()) plain_second = stripped(second) summary = { "published_before_quit": bool(before_quit), "reload_completed": b"HEXWORK session over" in plain_second, "bytes_unchanged": path.read_bytes() == before_bytes, "mtime_unchanged": path.stat().st_mtime_ns == before_mtime, - "identity_unchanged": first_save == after_save and only_run_id(data_dir) == run_id, + "run_id_unchanged": only_run_id(data_dir) == run_id, "reload_status_has_values": b"source=file gamma=1.5 fov=60 palette=protan commit=unchanged" in plain_second, "first_rendered": PLAY_CUE in stripped(first), } -- 2.54.0