refactor(server): address PR #16 review feedback

Hoshe + Tyre review items:
- Use StableId consistently for wire entity_id (H4) across observer,
  observation, interpretation, and interaction systems
- Make NearbyInteractionBuffer.interactions private with take() (H1/H20)
- Add system ordering constraint for compute_nearby_interactions (H5)
- Panic on missing PlayerCharacter in input processing (H2)
- Remove redundant paused field from GameTime (Tyre8)
- Remove #[serde(default)] from nearby_interactions (H3)
- Change NearbyInteraction.distance from f32 to u32 (H8)
- Add sort stability for equal verb priorities (H6)
- Scope constants to pub(crate) (H7)
- Add debug_assert for last_observed_tick ordering (H10)
- Strengthen unregistered entity handling to debug_assert + error (H11)
- Document fractional tick accumulation (Tyre9)
- Extract collect_remembered_entities helper (Tyre2/H17)
- Add half_rate_no_drift_over_10000_frames test (H14)
- Add mid-range and deterministic sort tests (H15)
- Add fixture version assertion (H16)
- Regenerate msgpack fixtures for wire format changes

146 unit + 19 integration tests pass, zero clippy warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 20:14:35 +01:00
co-authored by Claude Opus 4.6
parent 458be0f621
commit 9943684f2c
19 changed files with 232 additions and 136 deletions
+27 -27
View File
@@ -110,39 +110,39 @@ pub fn generate_observation_events(
continue;
}
let entity = Entity::from_bits(visible.entity_id);
// Convert wire StableId back to bevy Entity via registry
let stable_id = StableId(visible.entity_id);
let Some(entity) = registry.to_entity(&stable_id) else {
continue;
};
// Check if this is a new entity (not in observer's knowledge graph)
if let Some(stable_id) = registry.to_stable(entity) {
if !observer_kg.knows_entity(&stable_id) {
// Reconstruct tile position from render coords
let tile_pos = TilePosition::from_render_coords(visible.x, visible.y, visible.z);
event_queue.push(ObservationEvent {
tick: time.tick,
trigger: ObservationTrigger::NewEntity {
entity: stable_id,
location: tile_pos,
},
observer: observer_entity,
});
}
if !observer_kg.knows_entity(&stable_id) {
// Reconstruct tile position from render coords
let tile_pos = TilePosition::from_render_coords(visible.x, visible.y, visible.z);
event_queue.push(ObservationEvent {
tick: time.tick,
trigger: ObservationTrigger::NewEntity {
entity: stable_id,
location: tile_pos,
},
observer: observer_entity,
});
}
// Check routine deviation: visible NPC not at expected location
if let Ok((actual_pos, routine)) = npc_query.get(entity) {
if let Some(expected_pos) = routine.expected_location(current_phase) {
if *actual_pos != expected_pos {
if let Some(stable_id) = registry.to_stable(entity) {
event_queue.push(ObservationEvent {
tick: time.tick,
trigger: ObservationTrigger::RoutineDeviation {
npc: stable_id,
expected: expected_pos,
actual: *actual_pos,
},
observer: observer_entity,
});
}
event_queue.push(ObservationEvent {
tick: time.tick,
trigger: ObservationTrigger::RoutineDeviation {
npc: stable_id,
expected: expected_pos,
actual: *actual_pos,
},
observer: observer_entity,
});
}
}
}
@@ -157,8 +157,8 @@ pub fn generate_observation_events(
continue;
};
// Skip if currently visible
if visible_npc_bits.contains(&entity.to_bits()) {
// Skip if currently visible (visible_npc_bits contains wire StableId values)
if visible_npc_bits.contains(&stable_id.0) {
continue;
}