#![forbid(unsafe_code)] //! Deterministic world identity, queue, lifecycle, and snapshots. use std::collections::VecDeque; /// Object handle with generation. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ObjectHandle { /// Generation. pub generation: u32, /// Slot. pub slot: u32, } /// Original mission object id. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct OriginalObjectId(pub u32); /// Owner id. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct OwnerId(pub u16); /// Tick. #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Tick(pub u64); /// State hash. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct StateHash(pub [u8; 32]); /// World phase. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum WorldPhase { /// Idle. Idle, /// Calculating. Calculating, /// Applying deferred operations. ApplyingDeferred, /// Publishing snapshot. PublishingSnapshot, } /// Object draft. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ObjectDraft { /// Original id. pub original_id: Option, } /// Distinct object identity metadata. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct IdentityMetadata { /// Original mission object id. pub original_id: Option, /// Mirrored original id. pub mirror_id: Option, /// Local owner id. pub owner_id: Option, } /// World command. #[derive(Clone, Debug, Eq, PartialEq)] pub struct WorldCommand { /// Sequence. pub sequence: u64, /// Target. pub target: Option, } /// World event. #[derive(Clone, Debug, Eq, PartialEq)] pub struct WorldEvent { /// Sequence. pub sequence: u64, /// Target object, if any. pub target: Option, } /// Input snapshot. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct InputSnapshot; /// World snapshot. #[derive(Clone, Debug, Eq, PartialEq)] pub struct WorldSnapshot { /// Tick. pub tick: Tick, /// Live object handles. pub objects: Vec, /// Commands processed during this step. pub events: Vec, /// State hash. pub hash: StateHash, } /// World configuration. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct WorldConfig; /// Fixed-step clock state. #[derive(Clone, Debug, Eq, PartialEq)] pub struct FixedStepClock { accumulated_millis: u64, tick: Tick, paused: bool, platform_event_collections: u64, } /// Fixed-step configuration. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FixedStepConfig { /// Milliseconds per simulation tick. pub step_millis: u32, } impl Default for FixedStepConfig { fn default() -> Self { Self { step_millis: 16 } } } /// Shutdown ordering report. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ShutdownReport { /// Object handles released before managers. pub released_objects: Vec, /// Whether managers were released after objects. pub managers_released: bool, } #[derive(Clone, Debug)] struct Slot { generation: u32, live: bool, registered: bool, original_id: Option, owner_id: Option, mirror_id: Option, registration_sequence: Option, } /// World. #[derive(Clone, Debug)] pub struct World { slots: Vec, queue: VecDeque, deferred_delete: Vec, phase: WorldPhase, tick: Tick, next_sequence: u64, next_registration_sequence: u64, } /// World error. #[derive(Debug, Eq, PartialEq)] pub enum WorldError { /// Invalid handle. InvalidHandle, /// Stale handle. StaleHandle, /// Object already deleted. Deleted, /// Duplicate original object id. DuplicateOriginalObjectId(OriginalObjectId), /// Invalid fixed-step configuration. InvalidFixedStep, } impl std::fmt::Display for WorldError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{self:?}") } } impl std::error::Error for WorldError {} /// Creates a world. #[must_use] pub fn new(_config: WorldConfig) -> World { World { slots: Vec::new(), queue: VecDeque::new(), deferred_delete: Vec::new(), phase: WorldPhase::Idle, tick: Tick(0), next_sequence: 0, next_registration_sequence: 0, } } /// Constructs an object without registering it. /// /// # Errors /// /// Returns [`WorldError::InvalidHandle`] if the slot index cannot be /// represented by an [`ObjectHandle`]. pub fn construct_object(world: &mut World, draft: ObjectDraft) -> Result { let slot = u32::try_from(world.slots.len()).map_err(|_| WorldError::InvalidHandle)?; let handle = ObjectHandle { generation: 1, slot, }; world.slots.push(Slot { generation: 1, live: true, registered: false, original_id: draft.original_id, owner_id: None, mirror_id: None, registration_sequence: None, }); Ok(handle) } /// Registers a constructed object. /// /// # Errors /// /// Returns [`WorldError`] if the handle is stale, deleted, or out of range. pub fn register_object(world: &mut World, handle: ObjectHandle) -> Result<(), WorldError> { let original_id = checked_slot(world, handle)?.original_id; if let Some(original_id) = original_id { let duplicate = world.slots.iter().enumerate().any(|(idx, slot)| { u32::try_from(idx).is_ok_and(|slot_index| slot_index != handle.slot) && slot.live && slot.registered && slot.original_id == Some(original_id) }); if duplicate { return Err(WorldError::DuplicateOriginalObjectId(original_id)); } } let sequence = world.next_registration_sequence; world.next_registration_sequence = world.next_registration_sequence.saturating_add(1); let slot = checked_slot_mut(world, handle)?; slot.registered = true; slot.registration_sequence = Some(sequence); Ok(()) } /// Attaches local ownership metadata to an object. /// /// # Errors /// /// Returns [`WorldError`] if the handle is stale, deleted, or out of range. pub fn set_owner( world: &mut World, handle: ObjectHandle, owner_id: Option, ) -> Result<(), WorldError> { checked_slot_mut(world, handle)?.owner_id = owner_id; Ok(()) } /// Attaches mirror metadata to an object without changing its original id. /// /// # Errors /// /// Returns [`WorldError`] if the handle is stale, deleted, or out of range. pub fn set_mirror_original( world: &mut World, handle: ObjectHandle, mirror_id: Option, ) -> Result<(), WorldError> { checked_slot_mut(world, handle)?.mirror_id = mirror_id; Ok(()) } /// Returns registration sequence for a live object. /// /// # Errors /// /// Returns [`WorldError`] if the handle is stale, deleted, or out of range. pub fn registration_sequence( world: &World, handle: ObjectHandle, ) -> Result, WorldError> { Ok(checked_slot(world, handle)?.registration_sequence) } /// Returns object identity metadata. /// /// # Errors /// /// Returns [`WorldError`] if the handle is stale, deleted, or out of range. pub fn identity_metadata( world: &World, handle: ObjectHandle, ) -> Result { let slot = checked_slot(world, handle)?; Ok(IdentityMetadata { original_id: slot.original_id, mirror_id: slot.mirror_id, owner_id: slot.owner_id, }) } /// Requests deletion. /// /// # Errors /// /// Returns [`WorldError`] if the handle is stale, deleted, or out of range. pub fn request_delete(world: &mut World, handle: ObjectHandle) -> Result<(), WorldError> { checked_slot(world, handle)?; if world.phase == WorldPhase::Calculating { if !world.deferred_delete.contains(&handle) { world.deferred_delete.push(handle); } Ok(()) } else { delete_now(world, handle) } } /// Enqueues a command. /// /// # Errors /// /// Returns [`WorldError`] when a targeted command references an invalid /// handle. pub fn enqueue(world: &mut World, mut command: WorldCommand) -> Result<(), WorldError> { if let Some(handle) = command.target { checked_slot(world, handle)?; } command.sequence = world.next_sequence; world.next_sequence = world.next_sequence.saturating_add(1); world.queue.push_back(command); Ok(()) } /// Advances one deterministic step. /// /// # Errors /// /// Returns [`WorldError`] if a queued command references a stale, deleted, or /// out-of-range handle. pub fn step(world: &mut World, input: &InputSnapshot) -> Result { step_with_handler(world, input, |_, _| Ok(())) } /// Advances one deterministic step with a command callback. /// /// The callback runs while the world is in the calculating phase, which allows /// tests and adapters to exercise deferred deletion semantics without exposing /// mutable slot internals. /// /// # Errors /// /// Returns [`WorldError`] if a queued command references a stale, deleted, or /// out-of-range handle, or if the callback reports a world error. pub fn step_with_handler( world: &mut World, _input: &InputSnapshot, mut handler: F, ) -> Result where F: FnMut(&mut World, &WorldCommand) -> Result<(), WorldError>, { world.phase = WorldPhase::Calculating; let mut events = Vec::new(); while let Some(command) = world.queue.pop_front() { if let Some(handle) = command.target { if world.deferred_delete.contains(&handle) { continue; } checked_slot(world, handle)?; } handler(world, &command)?; events.push(WorldEvent { sequence: command.sequence, target: command.target, }); } world.phase = WorldPhase::ApplyingDeferred; let deletes = std::mem::take(&mut world.deferred_delete); for handle in deletes { let _ = delete_now(world, handle); } world.tick.0 = world.tick.0.saturating_add(1); world.phase = WorldPhase::PublishingSnapshot; let snapshot = WorldSnapshot { tick: world.tick, objects: live_registered(world), events, hash: canonical_state_hash(world), }; world.phase = WorldPhase::Idle; Ok(snapshot) } /// Computes canonical state hash. #[must_use] pub fn canonical_state_hash(world: &World) -> StateHash { let mut state = 0xcbf2_9ce4_8422_2325_u64; hash_u64(&mut state, world.tick.0); for (idx, slot) in world.slots.iter().enumerate() { hash_u64(&mut state, idx as u64); hash_u64(&mut state, u64::from(slot.generation)); hash_u64(&mut state, u64::from(u8::from(slot.live))); hash_u64(&mut state, u64::from(u8::from(slot.registered))); hash_u64(&mut state, slot.original_id.map_or(0, |id| u64::from(id.0))); hash_u64(&mut state, slot.mirror_id.map_or(0, |id| u64::from(id.0))); hash_u64(&mut state, slot.owner_id.map_or(0, |id| u64::from(id.0))); hash_u64(&mut state, slot.registration_sequence.unwrap_or(u64::MAX)); } let mut out = [0; 32]; out[..8].copy_from_slice(&state.to_le_bytes()); out[8..16].copy_from_slice(&state.rotate_left(13).to_le_bytes()); out[16..24].copy_from_slice(&state.rotate_left(29).to_le_bytes()); out[24..32].copy_from_slice(&state.rotate_left(47).to_le_bytes()); StateHash(out) } /// Creates a fixed-step clock. /// /// # Errors /// /// Returns [`WorldError::InvalidFixedStep`] when the configured step is zero. pub fn fixed_step_clock(config: FixedStepConfig) -> Result { if config.step_millis == 0 { return Err(WorldError::InvalidFixedStep); } Ok(FixedStepClock { accumulated_millis: 0, tick: Tick(0), paused: false, platform_event_collections: 0, }) } /// Records platform event collection independently of game time. pub fn collect_platform_events(clock: &mut FixedStepClock) { clock.platform_event_collections = clock.platform_event_collections.saturating_add(1); } /// Sets pause state. pub fn set_paused(clock: &mut FixedStepClock, paused: bool) { clock.paused = paused; } /// Advances fixed-step game time. /// /// Returns the number of simulation ticks that should be executed. /// /// # Errors /// /// Returns [`WorldError::InvalidFixedStep`] when the configured step is zero. pub fn advance_fixed_step( clock: &mut FixedStepClock, config: FixedStepConfig, elapsed_millis: u64, ) -> Result { if config.step_millis == 0 { return Err(WorldError::InvalidFixedStep); } if clock.paused { return Ok(0); } clock.accumulated_millis = clock.accumulated_millis.saturating_add(elapsed_millis); let step = u64::from(config.step_millis); let mut ticks = 0_u32; while clock.accumulated_millis >= step { clock.accumulated_millis -= step; clock.tick.0 = clock.tick.0.saturating_add(1); ticks = ticks.saturating_add(1); } Ok(ticks) } /// Returns fixed-step clock tick. #[must_use] pub fn fixed_step_tick(clock: &FixedStepClock) -> Tick { clock.tick } /// Returns platform event collection count. #[must_use] pub fn platform_event_collections(clock: &FixedStepClock) -> u64 { clock.platform_event_collections } /// Runs end-frame callbacks in stable sequence order. #[must_use] pub fn end_frame_callback_order(mut callbacks: Vec) -> Vec { callbacks.sort_by_key(|event| event.sequence); callbacks.into_iter().map(|event| event.sequence).collect() } /// Releases live objects before managers. #[must_use] pub fn shutdown(mut world: World) -> ShutdownReport { let released_objects = live_registered(&world); for slot in &mut world.slots { slot.live = false; slot.registered = false; slot.generation = slot.generation.saturating_add(1); } ShutdownReport { released_objects, managers_released: true, } } fn hash_u64(state: &mut u64, value: u64) { for byte in value.to_le_bytes() { *state ^= u64::from(byte); *state = state.wrapping_mul(0x0000_0100_0000_01b3); } } fn checked_slot(world: &World, handle: ObjectHandle) -> Result<&Slot, WorldError> { let slot = world .slots .get(handle.slot as usize) .ok_or(WorldError::InvalidHandle)?; if slot.generation != handle.generation { return Err(WorldError::StaleHandle); } if !slot.live { return Err(WorldError::Deleted); } Ok(slot) } fn checked_slot_mut(world: &mut World, handle: ObjectHandle) -> Result<&mut Slot, WorldError> { let slot = world .slots .get_mut(handle.slot as usize) .ok_or(WorldError::InvalidHandle)?; if slot.generation != handle.generation { return Err(WorldError::StaleHandle); } if !slot.live { return Err(WorldError::Deleted); } Ok(slot) } fn delete_now(world: &mut World, handle: ObjectHandle) -> Result<(), WorldError> { let slot = checked_slot_mut(world, handle)?; slot.live = false; slot.generation = slot.generation.saturating_add(1); Ok(()) } fn live_registered(world: &World) -> Vec { world .slots .iter() .enumerate() .filter_map(|(idx, slot)| { let slot_index = u32::try_from(idx).ok()?; (slot.live && slot.registered).then_some(ObjectHandle { generation: slot.generation, slot: slot_index, }) }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn construct_register_and_hash_are_stable() { let mut world = new(WorldConfig); let handle = construct_object(&mut world, ObjectDraft { original_id: None }).expect("obj"); let before = step(&mut world, &InputSnapshot).expect("step"); assert!(before.objects.is_empty()); register_object(&mut world, handle).expect("register"); let after = step(&mut world, &InputSnapshot).expect("step"); assert_eq!(after.objects, vec![handle]); } #[test] fn registration_sequence_stale_and_duplicate_original_contracts() { let mut world = new(WorldConfig); let first = construct_object( &mut world, ObjectDraft { original_id: Some(OriginalObjectId(7)), }, ) .expect("first"); let second = construct_object( &mut world, ObjectDraft { original_id: Some(OriginalObjectId(8)), }, ) .expect("second"); register_object(&mut world, first).expect("register first"); register_object(&mut world, second).expect("register second"); assert_eq!(registration_sequence(&world, first), Ok(Some(0))); assert_eq!(registration_sequence(&world, second), Ok(Some(1))); request_delete(&mut world, first).expect("delete"); assert_eq!( register_object(&mut world, first), Err(WorldError::StaleHandle) ); let recycled = ObjectHandle { generation: first.generation, slot: first.slot, }; assert_eq!( register_object(&mut world, recycled), Err(WorldError::StaleHandle) ); let duplicate = construct_object( &mut world, ObjectDraft { original_id: Some(OriginalObjectId(8)), }, ) .expect("duplicate"); assert_eq!( register_object(&mut world, duplicate), Err(WorldError::DuplicateOriginalObjectId(OriginalObjectId(8))) ); } #[test] fn identity_metadata_keeps_original_mirror_and_owner_distinct() { let mut world = new(WorldConfig); let handle = construct_object( &mut world, ObjectDraft { original_id: Some(OriginalObjectId(10)), }, ) .expect("object"); set_mirror_original(&mut world, handle, Some(OriginalObjectId(20))).expect("mirror"); set_owner(&mut world, handle, Some(OwnerId(3))).expect("owner"); assert_eq!( identity_metadata(&world, handle), Ok(IdentityMetadata { original_id: Some(OriginalObjectId(10)), mirror_id: Some(OriginalObjectId(20)), owner_id: Some(OwnerId(3)) }) ); } #[test] fn command_fifo_and_deferred_delete_during_calculation() { let mut world = new(WorldConfig); let first = construct_object(&mut world, ObjectDraft { original_id: None }).expect("first"); let second = construct_object(&mut world, ObjectDraft { original_id: None }).expect("second"); register_object(&mut world, first).expect("register first"); register_object(&mut world, second).expect("register second"); enqueue( &mut world, WorldCommand { sequence: 99, target: Some(first), }, ) .expect("enqueue first"); enqueue( &mut world, WorldCommand { sequence: 99, target: Some(second), }, ) .expect("enqueue second"); enqueue( &mut world, WorldCommand { sequence: 99, target: Some(first), }, ) .expect("enqueue first again"); let snapshot = step_with_handler(&mut world, &InputSnapshot, |world, command| { if command.target == Some(first) { request_delete(world, first)?; request_delete(world, first)?; } Ok(()) }) .expect("step"); assert_eq!( snapshot.events, vec![ WorldEvent { sequence: 0, target: Some(first) }, WorldEvent { sequence: 1, target: Some(second) } ] ); assert_eq!( request_delete(&mut world, first), Err(WorldError::StaleHandle) ); assert_eq!( step(&mut world, &InputSnapshot).expect("step").objects, vec![second] ); } #[test] fn snapshot_hash_determinism_and_immutability() { let mut left = new(WorldConfig); let mut right = new(WorldConfig); for world in [&mut left, &mut right] { let handle = construct_object( world, ObjectDraft { original_id: Some(OriginalObjectId(1)), }, ) .expect("object"); register_object(world, handle).expect("register"); } let snapshot = step(&mut left, &InputSnapshot).expect("snapshot"); let clone = snapshot.clone(); let extra = construct_object(&mut left, ObjectDraft { original_id: None }).expect("extra"); register_object(&mut left, extra).expect("register extra"); assert_eq!(snapshot, clone); assert_eq!( clone.hash, step(&mut right, &InputSnapshot).expect("right").hash ); } #[test] fn fixed_step_pause_and_long_determinism_are_stable() { let config = FixedStepConfig { step_millis: 20 }; let mut clock = fixed_step_clock(config).expect("clock"); collect_platform_events(&mut clock); set_paused(&mut clock, true); assert_eq!(advance_fixed_step(&mut clock, config, 100), Ok(0)); collect_platform_events(&mut clock); assert_eq!(fixed_step_tick(&clock), Tick(0)); assert_eq!(platform_event_collections(&clock), 2); set_paused(&mut clock, false); assert_eq!(advance_fixed_step(&mut clock, config, 45), Ok(2)); assert_eq!(fixed_step_tick(&clock), Tick(2)); let mut first = new(WorldConfig); let mut second = new(WorldConfig); let mut first_hashes = Vec::new(); let mut second_hashes = Vec::new(); for _ in 0..10_000 { first_hashes.push(step(&mut first, &InputSnapshot).expect("first").hash); second_hashes.push(step(&mut second, &InputSnapshot).expect("second").hash); } assert_eq!(first_hashes, second_hashes); } #[test] fn render_disabled_does_not_change_hash_end_callbacks_and_shutdown_order() { let callbacks = vec![ WorldEvent { sequence: 3, target: None, }, WorldEvent { sequence: 1, target: None, }, WorldEvent { sequence: 2, target: None, }, ]; assert_eq!(end_frame_callback_order(callbacks), vec![1, 2, 3]); let mut rendered = new(WorldConfig); let mut headless = rendered.clone(); assert_eq!( step(&mut rendered, &InputSnapshot).expect("rendered").hash, step(&mut headless, &InputSnapshot).expect("headless").hash ); let handle = construct_object(&mut rendered, ObjectDraft { original_id: None }).expect("object"); register_object(&mut rendered, handle).expect("register"); assert_eq!( shutdown(rendered), ShutdownReport { released_objects: vec![handle], managers_released: true } ); } #[test] fn generated_command_delete_sequences_preserve_registry_invariants() { for seed in 0_u32..64 { let mut world = new(WorldConfig); let mut handles = Vec::new(); for index in 0..8 { let handle = construct_object( &mut world, ObjectDraft { original_id: Some(OriginalObjectId(seed * 100 + index)), }, ) .expect("object"); register_object(&mut world, handle).expect("register"); handles.push(handle); } for (index, handle) in handles.iter().copied().enumerate() { if (seed as usize + index) % 3 == 0 { request_delete(&mut world, handle).expect("delete"); } else { enqueue( &mut world, WorldCommand { sequence: 0, target: Some(handle), }, ) .expect("enqueue"); } } let snapshot = step(&mut world, &InputSnapshot).expect("step"); for handle in snapshot.objects { assert!(registration_sequence(&world, handle) .expect("sequence") .is_some()); } } } }