Something went wrong. Try again.
HEXWORK - A Lantern in the Deep: 3D ASCII terminal roguelike RPG in Rust.
Entirely designed and built by Muse Spark 1.3 to evaluate if I like the model.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392//! Phase 12 live boot presentation preference production gates.
use super::support::Harness;use hexwork_save::artifact::ArtifactStore;use std::fs;use std::path::{Path, PathBuf};use std::process::Command;use std::time::SystemTime;
const VALID: &[u8] = br#"{"schema_version":1,"gamma":1.0,"fov_deg":75.0,"palette":"default"}"#;
#[derive(Debug, PartialEq, Eq)]struct CheckedMetadata { is_file: bool, is_dir: bool, len: u64, modified: SystemTime,}
fn checked_metadata(path: &Path) -> CheckedMetadata { let metadata = fs::symlink_metadata(path).unwrap(); CheckedMetadata { is_file: metadata.file_type().is_file(), is_dir: metadata.file_type().is_dir(), len: metadata.len(), modified: metadata.modified().unwrap(), }}
#[cfg(target_os = "linux")]fn platform_default_data_root(harness: &Harness) -> PathBuf { harness.home.join("xdg").join("hexwork")}
#[cfg(target_os = "macos")]fn platform_default_data_root(harness: &Harness) -> PathBuf { harness .home .join("Library") .join("Application Support") .join("hexwork")}
fn interactive(harness: &Harness) -> Command { let mut command = harness.command(); command .arg("--data-dir") .arg(&harness.data) .arg("--content-root") .arg(&harness.content) .arg("--ancestry") .arg("1") .arg("--calling") .arg("1"); command}
fn assert_preterminal_failure(harness: &Harness, expected_exit: i32, needle: &str) { let output = Harness::run(interactive(harness)); assert_eq!( output.status.code(), Some(expected_exit), "stdout={} stderr={}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains(needle), "stderr={stderr}"); assert!(!output.stdout.contains(&0x1b), "terminal reached stdout"); assert!(!output.stderr.contains(&0x1b), "terminal reached stderr"); assert!(!harness.data.join("lineage-vault.json").exists()); assert!(!harness.data.join("runs").exists());}
#[test]fn binary_bad_presentation_refuses_before_terminal_or_allocation() { let malformed = Harness::new("presentation-bad-json"); fs::create_dir_all(&malformed.data).unwrap(); fs::write(malformed.data.join("presentation.json"), b"{").unwrap(); assert_preterminal_failure(&malformed, 2, "presentation.json");
let oversized = Harness::new("presentation-oversized"); fs::create_dir_all(&oversized.data).unwrap(); fs::File::create(oversized.data.join("presentation.json")) .unwrap() .set_len(16 * 1024 + 1) .unwrap(); assert_preterminal_failure(&oversized, 5, "16384");
let directory = Harness::new("presentation-directory"); fs::create_dir_all(directory.data.join("presentation.json")).unwrap(); assert_preterminal_failure(&directory, 4, "not a regular file");}
#[test]fn headless_ignores_invalid_presentation_file() { let harness = Harness::new("presentation-headless-ignore"); fs::create_dir_all(&harness.data).unwrap(); let path = harness.data.join("presentation.json"); fs::write(&path, b"not json").unwrap(); let before = fs::read(&path).unwrap(); let script = harness.script("quit.commands", "quit\n"); let output = Harness::run(harness.managed(&script)); assert!( output.status.success(), "headless stderr={}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(fs::read(path).unwrap(), before);}
#[test]fn explicit_presentation_flags_reject_read_only_operations() { let harness = Harness::new("presentation-readonly-flags"); for args in [ &["--help", "--gamma", "1.5"][..], &["--arena", "--fov", "60"][..], &["--recover", "--palette", "protan"][..], &["--replay", "missing", "--gamma", "1.5"][..], &["--replay", "missing", "--export-ansi", "out", "--fov", "60"][..], ] { let output = Harness::run({ let mut command = harness.command(); command.args(args); command }); assert_eq!(output.status.code(), Some(2), "args={args:?}"); }}
#[test]fn binary_recover_ignores_invalid_presentation_file() { for (label, kind) in [("malformed", 0_u8), ("oversized", 1), ("directory", 2)] { let harness = Harness::new(&format!("presentation-recover-{label}")); fs::create_dir_all(&harness.data).unwrap(); let path = harness.data.join("presentation.json"); match kind { 0 => fs::write(&path, b"bad").unwrap(), 1 => fs::File::create(&path) .unwrap() .set_len(16 * 1024 + 1) .unwrap(), 2 => fs::create_dir(&path).unwrap(), _ => unreachable!(), } let before = checked_metadata(&path); let bytes = before.is_file.then(|| fs::read(&path).unwrap()); let output = Harness::run({ let mut command = harness.command(); command .arg("--data-dir") .arg(&harness.data) .arg("--content-root") .arg(&harness.content) .arg("--recover"); command }); assert!( output.status.success(), "recover {label} stderr={}", String::from_utf8_lossy(&output.stderr) ); let after = checked_metadata(&path); assert_eq!( after, before, "recover changed checked metadata for {label}" ); if let Some(bytes) = bytes { assert_eq!(fs::read(path).unwrap(), bytes); } }}
#[test]fn binary_replay_ignores_invalid_presentation_file() { let harness = Harness::new("presentation-replay-ignore"); let script = harness.script("record.commands", "input wait\nquit\n"); let output = Harness::run(harness.managed(&script)); assert!(output.status.success()); let run_id = Harness::records(&output) .iter() .find(|record| record["code"] == "allocation-durable") .and_then(|record| record["run_id"].as_str()) .unwrap() .to_owned(); let pack = hexwork_content::RuntimePack::load(&harness.content).unwrap(); let saved = hexwork_save::decode_run( &fs::read(harness.data.join("runs").join(&run_id).join("run.save")).unwrap(), pack.hash(), ) .unwrap(); let replay = hexwork_save::ProductionReplay { identity: saved.identity, events: saved.events, expected_final_tick: saved.current.tick, expected_final_hash: saved.current.try_hash_state().unwrap(), }; let replay_path = harness.root.join("normal.hexdemo"); fs::write(&replay_path, hexwork_save::encode_replay(&replay).unwrap()).unwrap();
let default_root = platform_default_data_root(&harness); assert_ne!( default_root, harness.data, "replay preference root must be separate from the managed fixture root" ); for (label, kind) in [("malformed", 0_u8), ("oversized", 1), ("directory", 2)] { let path = default_root.join("presentation.json"); let _ = fs::remove_file(&path); let _ = fs::remove_dir_all(&path); fs::create_dir_all(&default_root).unwrap(); match kind { 0 => fs::write(&path, b"bad").unwrap(), 1 => fs::File::create(&path) .unwrap() .set_len(16 * 1024 + 1) .unwrap(), 2 => fs::create_dir(&path).unwrap(), _ => unreachable!(), } let before = checked_metadata(&path); let bytes = before.is_file.then(|| fs::read(&path).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(), "replay {label} stderr={}", String::from_utf8_lossy(&replayed.stderr) ); assert!(String::from_utf8_lossy(&replayed.stdout).contains("verified event-text replay")); assert_eq!( checked_metadata(&path), before, "replay changed checked metadata for {label}" ); if let Some(bytes) = bytes { assert_eq!(fs::read(&path).unwrap(), bytes); } }}
#[cfg(any(target_os = "linux", target_os = "macos"))]fn run_pty_scenario(harness: &Harness, mode: &str) -> serde_json::Value { let script = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/pty/presentation_scenario.py"); let output = Harness::run({ let mut command = Command::new("python3"); command .current_dir(&harness.cwd) .arg(script) .arg(env!("CARGO_BIN_EXE_hexwork")) .arg(&harness.data) .arg(&harness.content) .arg(mode); command }); assert!( output.status.success(), "PTY {mode} failed: stdout={} stderr={}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); 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() { let harness = Harness::new("presentation-lock"); fs::create_dir_all(&harness.data).unwrap(); let path = harness.data.join("presentation.json"); fs::write(&path, VALID).unwrap(); let metadata = fs::metadata(&path).unwrap(); let (store, _) = ArtifactStore::open(&harness.data).unwrap(); let summary = run_pty_scenario(&harness, "contended"); assert_eq!(summary["exit"], 6); assert_eq!(fs::read(&path).unwrap(), VALID); assert_eq!( fs::metadata(&path).unwrap().modified().unwrap(), metadata.modified().unwrap() ); drop(store); let summary = run_pty_scenario(&harness, "publish"); assert_eq!(summary["published_before_quit"], true); let value: serde_json::Value = serde_json::from_slice(&fs::read(path).unwrap()).unwrap(); assert_eq!(value["gamma"], 1.5);}
#[test]#[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["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]#[cfg(any(target_os = "linux", target_os = "macos"))]fn binary_pty_default_presentation_does_not_create_file() { let harness = Harness::new("presentation-default-pty"); let summary = run_pty_scenario(&harness, "defaults"); assert_eq!(summary["rendered"], true); assert_eq!(summary["presentation_absent"], true);}