test(server): add Entity::to_bits roundtrip and TickRate switch tests
- Entity::to_bits() roundtrip test guards against bevy version changes silently breaking wire IDs (Hoshe #1) - PROTOCOL_VERSION constant used in test helpers instead of hardcoded 4 - TickRate switch mid-accumulation test verifies Half→Full→Paused→Half transitions preserve accumulator state correctly (Tyre N3) - POI mid-range test now asserts priority=1 (Hoshe #4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -199,6 +199,37 @@ mod tests {
|
||||
assert_eq!(time.day(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_rate_switch_mid_accumulation() {
|
||||
// Half->Full with 0.5 remainder: Full should tick immediately (0.5 + 1.0 >= 1.0)
|
||||
let mut world = bevy_ecs::world::World::new();
|
||||
world.insert_resource(SimulationTime { tick_rate: TickRate::Half, ..Default::default() });
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(advance_tick);
|
||||
|
||||
// Frame 1: Half rate, accumulate 0.5, no tick
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 0);
|
||||
|
||||
// Switch to Full mid-accumulation (0.5 remainder)
|
||||
world.resource_mut::<SimulationTime>().tick_rate = TickRate::Full;
|
||||
|
||||
// Frame 2: Full rate adds 1.0 to 0.5 remainder → tick fires
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 1);
|
||||
|
||||
// Switch to Paused: no advance regardless of accumulator
|
||||
world.resource_mut::<SimulationTime>().tick_rate = TickRate::Paused;
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 1);
|
||||
|
||||
// Switch back to Half: accumulator still has 0.5 from overshoot
|
||||
world.resource_mut::<SimulationTime>().tick_rate = TickRate::Half;
|
||||
schedule.run(&mut world);
|
||||
// 0.5 (leftover) + 0.5 (Half) = 1.0 → tick fires
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_rate_no_drift_over_10000_frames() {
|
||||
let mut world = bevy_ecs::world::World::new();
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::fs;
|
||||
/// Helper to create a minimal v2 snapshot for tests
|
||||
fn test_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
|
||||
ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick,
|
||||
game_time: GameTime {
|
||||
day: 0,
|
||||
@@ -231,6 +231,35 @@ fn snapshot_v2_fields_roundtrip() {
|
||||
assert_eq!(decoded.entities[0].visibility, VisibilitySector::Forward);
|
||||
}
|
||||
|
||||
/// Entity::to_bits() must roundtrip through from_bits() — guards against
|
||||
/// bevy version changes silently breaking wire IDs (Hoshe #12).
|
||||
#[test]
|
||||
fn entity_to_bits_roundtrip() {
|
||||
use bevy_ecs::entity::Entity;
|
||||
// Create entities via a World so we get valid index+generation pairs
|
||||
let mut world = bevy_ecs::world::World::new();
|
||||
let e1 = world.spawn_empty().id();
|
||||
let e2 = world.spawn_empty().id();
|
||||
let e3 = world.spawn_empty().id();
|
||||
// Despawn and respawn to get a higher generation
|
||||
world.despawn(e2);
|
||||
let e4 = world.spawn_empty().id();
|
||||
|
||||
for entity in [e1, e2, e3, e4] {
|
||||
let bits = entity.to_bits();
|
||||
let restored = Entity::from_bits(bits);
|
||||
assert_eq!(entity, restored, "Entity::to_bits() roundtrip failed for {:?}", entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// PROTOCOL_VERSION constant matches snapshot version field
|
||||
#[test]
|
||||
fn protocol_version_constant_matches_snapshot() {
|
||||
let snapshot = test_snapshot(0, vec![]);
|
||||
assert_eq!(snapshot.version, PROTOCOL_VERSION);
|
||||
assert_eq!(PROTOCOL_VERSION, 4, "bump this assertion when protocol version changes");
|
||||
}
|
||||
|
||||
/// All FacingDirection variants round-trip
|
||||
#[test]
|
||||
fn all_facing_direction_variants_roundtrip() {
|
||||
|
||||
Reference in New Issue
Block a user