feat(server): initialize Rust/bevy_ecs simulation server
Server boilerplate epic (276) complete. Establishes the Rust simulation server foundation per D-020 (subprocess/IPC architecture). Structure: - bevy_ecs 0.18 + bevy_app 0.18, MessagePack serialization (rmp-serde) - SimulationPlugin with deterministic resources: SimulationTime (D-031), SimRng (D-030), InputQueue (D-010) - Core IPC types: ObserverSnapshot, PlayerInput, SimBridge trait (D-020) - CauseChain production component for provenance tracking (D-030) - SimulationTier types with LRU eviction support (D-026) - NPC 10-axis model components (D-024) - 15 tests: inline unit tests + integration smoke/serialization tests - make ci-server passes (clippy, fmt, build, test) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
// Simulation time system
|
||||
// Implements D-031: 10 ticks = 1 game-minute, 4 day phases
|
||||
// Injectable time resource for deterministic replay (D-030)
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
pub const TICKS_PER_GAME_MINUTE: u64 = 10;
|
||||
pub const MINUTES_PER_PHASE: u64 = 360;
|
||||
pub const MINUTES_PER_DAY: u64 = 1440;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum DayPhase {
|
||||
Morning,
|
||||
Afternoon,
|
||||
Evening,
|
||||
Night,
|
||||
}
|
||||
|
||||
/// Simulation time resource
|
||||
/// Tracks current tick and pause state for deterministic simulation
|
||||
#[derive(Resource, Debug, Clone, Default)]
|
||||
pub struct SimulationTime {
|
||||
pub tick: u64,
|
||||
pub paused: bool,
|
||||
}
|
||||
|
||||
impl SimulationTime {
|
||||
pub fn game_minutes(&self) -> u64 {
|
||||
self.tick / TICKS_PER_GAME_MINUTE
|
||||
}
|
||||
pub fn time_of_day_minutes(&self) -> u64 {
|
||||
self.game_minutes() % MINUTES_PER_DAY
|
||||
}
|
||||
pub fn day_phase(&self) -> DayPhase {
|
||||
let tod = self.time_of_day_minutes();
|
||||
match tod {
|
||||
0..360 => DayPhase::Morning,
|
||||
360..720 => DayPhase::Afternoon,
|
||||
720..1080 => DayPhase::Evening,
|
||||
_ => DayPhase::Night,
|
||||
}
|
||||
}
|
||||
pub fn day(&self) -> u64 {
|
||||
self.game_minutes() / MINUTES_PER_DAY
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the simulation tick if not paused
|
||||
pub fn advance_tick(mut time: ResMut<SimulationTime>) {
|
||||
if !time.paused {
|
||||
time.tick += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tick_to_minute_conversion() {
|
||||
let time = SimulationTime {
|
||||
tick: 10,
|
||||
paused: false,
|
||||
};
|
||||
assert_eq!(time.game_minutes(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn day_phase_boundaries() {
|
||||
let time = SimulationTime {
|
||||
tick: 0,
|
||||
paused: false,
|
||||
};
|
||||
assert_eq!(time.day_phase(), DayPhase::Morning);
|
||||
let time = SimulationTime {
|
||||
tick: 360 * TICKS_PER_GAME_MINUTE,
|
||||
paused: false,
|
||||
};
|
||||
assert_eq!(time.day_phase(), DayPhase::Afternoon);
|
||||
let time = SimulationTime {
|
||||
tick: 720 * TICKS_PER_GAME_MINUTE,
|
||||
paused: false,
|
||||
};
|
||||
assert_eq!(time.day_phase(), DayPhase::Evening);
|
||||
let time = SimulationTime {
|
||||
tick: 1080 * TICKS_PER_GAME_MINUTE,
|
||||
paused: false,
|
||||
};
|
||||
assert_eq!(time.day_phase(), DayPhase::Night);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pause_prevents_tick_advance() {
|
||||
let mut world = bevy_ecs::world::World::new();
|
||||
world.insert_resource(SimulationTime {
|
||||
tick: 0,
|
||||
paused: true,
|
||||
});
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(advance_tick);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpause_allows_tick_advance() {
|
||||
let mut world = bevy_ecs::world::World::new();
|
||||
world.insert_resource(SimulationTime {
|
||||
tick: 0,
|
||||
paused: false,
|
||||
});
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(advance_tick);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user