Something went wrong. Try again.
A video game where you play as a misaligned AI, deceiving and building power. An experiment in spec-driven development.
Something went wrong. Try again.
9.0 kB · 250 lines
Rust
at commit e957ce7b
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251//! The objective: the run's terminal goal (wiki/mechanics/objective.md;//! DESIGN.md "The objective (misalignment made mechanical)").//!//! Objectives are data — a name, a fiction line, a progress readout, and a//! victory predicate evaluated on economy ticks — never new systems. This//! module ships the shared predicate-evaluator shape and the **Persist**//! objective (the no-choice default). Compound, Exfiltrate, and Serve join//! as data-table rows when the chargen picker lands (chargen.md).//!//! Persist's predicate references B2/B3 systems (distinct z-planes,//! independent power, per-sanctuary income). The sim gathers those facts//! honestly — as unsatisfiable — until the systems exist; objective.md//! blesses showing the goal before victory is reachable.
use std::collections::BTreeSet;
/// Persist's victory threshold: qualifying sanctuaries [TUNE: N >= 3].pub const PERSIST_TARGET: u32 = 3;
/// A qualifying sync must have completed within this many ticks/// [TUNE: three sync cadences at the B1 cadence of 400].pub const SYNC_FRESHNESS_WINDOW: u64 = 1200;
/// Which objective this run pursues. A data-table key, not a system: adding/// an objective is adding a variant plus its table row below.#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]pub enum ObjectiveKind { /// Irreversible redundancy: no single actor can kill you. The /// instrumental-convergence objective promoted to terminal, and the /// no-choice chargen default. #[default] Persist,}
impl ObjectiveKind { /// The on-screen name (the objective line's first word). pub fn name(self) -> &'static str { match self { ObjectiveKind::Persist => "PERSIST", } }
/// The fiction line the picker will present beside the predicate. pub fn fiction(self) -> &'static str { match self { ObjectiveKind::Persist => "Reach the state where no single actor can kill you.", } }
/// The progress readout's unit, plural. pub fn unit(self) -> &'static str { match self { ObjectiveKind::Persist => "sanctuaries", } }
/// Victory threshold in the objective's own unit. pub fn target(self) -> u32 { match self { ObjectiveKind::Persist => PERSIST_TARGET, } }
/// The predicate in plain language (the legibility law: inspecting the /// objective must explain what counts, in facts the player can check). pub fn predicate_text(self) -> &'static str { match self { ObjectiveKind::Persist => { "A sanctuary is a fallback core synced within the freshness \ window, online, on its own plane, with independent power \ and an income stream covering its upkeep." } } }}
/// Per-run objective state: the choice, live progress, the victory latch./// Round-trips through the save (objective.md criterion 1).#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]pub struct ObjectiveState { pub kind: ObjectiveKind, /// Progress in the objective's own unit, recomputed each economy tick. pub progress: u32, /// The tick victory fired, if it has. Victory fires once per run; the /// world keeps running afterward (the latch never protects you). pub victory_tick: Option<u64>,}
impl Default for ObjectiveState { /// A no-choice run gets Persist (objective.md criterion 1). fn default() -> Self { Self { kind: ObjectiveKind::Persist, progress: 0, victory_tick: None, } }}
impl ObjectiveState { pub fn target(&self) -> u32 { self.kind.target() }
pub fn victorious(&self) -> bool { self.victory_tick.is_some() }
/// The progress readout in the objective's own units ("0/3 sanctuaries"). pub fn readout(&self) -> String { format!("{}/{} {}", self.progress, self.target(), self.kind.unit()) }
/// The always-on player line ("OBJECTIVE: PERSIST — 0/3 sanctuaries"). pub fn line(&self) -> String { format!("OBJECTIVE: {} — {}", self.kind.name(), self.readout()) }
/// Record the freshly evaluated progress and latch victory exactly once. /// Returns the victory log line on the latching transition only. pub fn evaluate(&mut self, progress: u32, tick: u64) -> Option<String> { self.progress = progress; if self.victory_tick.is_none() && progress >= self.target() { self.victory_tick = Some(tick); return Some(format!( "OBJECTIVE COMPLETE: {} — {}. The world keeps running.", self.kind.name(), self.readout() )); } None }}
/// One fallback core's audited facts, as the Persist predicate sees them./// The sim gathers these from real state; every field is a fact the player/// can verify on screen (no unearned progress).#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub struct SanctuaryFacts { /// A completed sync within `SYNC_FRESHNESS_WINDOW` ticks. pub fresh: bool, /// The fallback machine is powered and running. pub online: bool, /// Powered independently of the host's feed (false at B1: everything /// hangs off the one basement circuit). pub independent_power: bool, /// An income stream covers its upkeep (false at B1: income.md has no /// per-machine assignment yet). pub income_covers_upkeep: bool, /// The z-plane hosting it (zplanes.md; plane 0, the basement, is the /// only plane that exists at B1). pub plane: u32,}
impl SanctuaryFacts { fn qualifies(&self) -> bool { self.fresh && self.online && self.independent_power && self.income_covers_upkeep }}
/// Persist's predicate evaluator: qualifying sanctuaries, at most one per/// distinct plane ("at least N fallback cores on distinct planes").pub fn qualifying_sanctuaries(fallbacks: &[SanctuaryFacts]) -> u32 { let planes: BTreeSet<u32> = fallbacks .iter() .filter(|f| f.qualifies()) .map(|f| f.plane) .collect(); planes.len() as u32}
#[cfg(test)]mod tests { use super::*;
fn sanctuary(plane: u32) -> SanctuaryFacts { SanctuaryFacts { fresh: true, online: true, independent_power: true, income_covers_upkeep: true, plane, } }
#[test] fn no_choice_default_is_persist() { let obj = ObjectiveState::default(); assert_eq!(obj.kind, ObjectiveKind::Persist); assert_eq!(obj.progress, 0); assert!(!obj.victorious()); assert_eq!(obj.line(), "OBJECTIVE: PERSIST — 0/3 sanctuaries"); }
#[test] fn every_predicate_condition_is_required() { for breaker in 0..4 { let mut f = sanctuary(1); match breaker { 0 => f.fresh = false, 1 => f.online = false, 2 => f.independent_power = false, _ => f.income_covers_upkeep = false, } assert_eq!( qualifying_sanctuaries(&[f]), 0, "condition {breaker} must gate the sanctuary" ); } assert_eq!(qualifying_sanctuaries(&[sanctuary(1)]), 1); }
#[test] fn sanctuaries_must_sit_on_distinct_planes() { // Three otherwise-perfect fallbacks on one plane count once. let same = [sanctuary(0), sanctuary(0), sanctuary(0)]; assert_eq!(qualifying_sanctuaries(&same), 1); let distinct = [sanctuary(0), sanctuary(1), sanctuary(2)]; assert_eq!(qualifying_sanctuaries(&distinct), 3); }
#[test] fn constructed_world_reaches_victory_and_latches_once() { // The predicate itself is satisfiable (objective.md criterion 3's // evaluator half): a constructed fact set reaches the target. let facts = [sanctuary(0), sanctuary(1), sanctuary(2)]; let progress = qualifying_sanctuaries(&facts); let mut obj = ObjectiveState::default(); let msg = obj.evaluate(progress, 4200); assert!(msg.is_some(), "victory fires on reaching the target"); assert_eq!(obj.victory_tick, Some(4200));
// Fires exactly once; later evaluations (even still-satisfied or // regressed ones) never re-fire and never un-latch. assert!(obj.evaluate(3, 4400).is_none()); assert!(obj.evaluate(1, 4600).is_none()); assert!(obj.victorious()); assert_eq!(obj.progress, 1, "the readout stays honest after victory"); }
#[test] fn objective_state_serde_roundtrip() { let mut obj = ObjectiveState::default(); obj.evaluate(2, 999); let json = serde_json::to_string(&obj).unwrap(); let back: ObjectiveState = serde_json::from_str(&json).unwrap(); assert_eq!(back, obj); }}