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.
3.3 kB · 133 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134//! Pure frontend scheduling and autosave cadence.//!//! This module never receives mutable simulation state. It only determines how//! many fixed ticks are due; the application controller advances each one via//! `GameSession::advance_one_tick` and stops at lifecycle boundaries.
pub const TICK_MS: u64 = 100;pub const AUTOSAVE_EVERY_DEFAULT: u64 = 600;
#[derive(Clone, Debug, Default)]pub struct Loop { acc_ms: u64,}
impl Loop { pub fn new() -> Self { Self::default() }
/// Accumulate bounded frame time and return due fixed steps without touching /// authoritative state. pub fn due_ticks(&mut self, dt_ms: u64) -> u64 { self.acc_ms = self.acc_ms.saturating_add(dt_ms); let due = self.acc_ms / TICK_MS; self.acc_ms %= TICK_MS; due }
#[cfg(test)] pub fn reset(&mut self) { self.acc_ms = 0; }}
#[derive(Clone, Debug)]pub struct AutosaveCadence { elapsed_since_success: u64, every: u64,}
impl AutosaveCadence { pub fn new() -> Self { Self { elapsed_since_success: 0, every: AUTOSAVE_EVERY_DEFAULT, } }
/// Charge actual elapsed simulation ticks. Crossing is sticky until a /// successful save, so render frames neither lose nor repeat a write. pub fn advance(&mut self, elapsed: u64) -> bool { self.elapsed_since_success = self.elapsed_since_success.saturating_add(elapsed); self.due() }
pub fn due(&self) -> bool { self.elapsed_since_success >= self.every }
pub fn saved_successfully(&mut self) { self.elapsed_since_success = 0; }
pub fn reset_after_load_or_rollback(&mut self) { self.elapsed_since_success = 0; }}
impl Default for AutosaveCadence { fn default() -> Self { Self::new() }}
#[cfg(test)]#[derive(Clone, Debug, Default)]pub struct SpsProbe { pub events: u64,}
#[cfg(test)]impl SpsProbe { pub fn new() -> Self { Self::default() }
pub fn record_event(&mut self) { self.events = self.events.saturating_add(1); }}
#[cfg(test)]pub fn story_worthiness(events_per_session: u64) -> bool { events_per_session >= 1}
#[cfg(test)]mod tests { use super::*;
#[test] fn accumulator_reports_due_steps_without_state() { let mut scheduler = Loop::new(); assert_eq!(scheduler.due_ticks(99), 0); assert_eq!(scheduler.due_ticks(1), 1); assert_eq!(scheduler.due_ticks(1050), 10); assert_eq!(scheduler.due_ticks(50), 1); scheduler.reset(); assert_eq!(scheduler.due_ticks(99), 0); }
#[test] fn autosave_is_crossing_based_and_success_resets() { let mut cadence = AutosaveCadence::new(); assert!(!cadence.advance(599)); assert!(cadence.advance(8_640)); assert!(cadence.due()); assert!(cadence.advance(0)); cadence.saved_successfully(); assert!(!cadence.due()); assert!(cadence.advance(600)); cadence.reset_after_load_or_rollback(); assert!(!cadence.due()); }
#[test] fn sps_threshold() { let mut probe = SpsProbe::new(); assert!(!story_worthiness(probe.events)); probe.record_event(); assert!(story_worthiness(probe.events)); }}