fix(server): address PR #23 review — 4 critical bugs, 3 warnings, 11 suggestions

Critical fixes:
- Add PendingRecognitionWire serialization roundtrip test
- Check Option return from delay.cancel() before logging
- InputQueue capacity limit (1000) with drop-oldest and warning
- TODO in observation.rs references ticket #450

Warning fixes:
- Hot-reload guards against invalid/empty content root
- Location header mismatch warning in line pool indexing
- walk_yaml() depth limit (100) against symlink loops
- Consecutive reload failure counter (warns after 5+)

Test additions:
- Negative prerequisite filtering test for monologue lines
- Integration test for pending_recognitions in observer snapshot
- Eavesdrop threshold ordering assertion (Careful < default)

Documentation:
- Playtesting expectation comments on delay constants
- is_pending() scalability note for future NPC cognitive delay
- ID format regex validation in line pool spec
- BTreeMap vs sort() ordering clarification in loader
- Multiplayer TODO in relationships.rs references D-010
- ContentSlug ticket #452 filed for entity slug resolution

394 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 01:03:51 +01:00
co-authored by Claude Opus 4.6
parent 8a334b47f3
commit d4fdbf426e
11 changed files with 379 additions and 17 deletions
+98
View File
@@ -1817,3 +1817,101 @@ fn sprint_anomaly_multiple_contradicted_npcs_only_first_queued() {
let queue = query.single(&world).unwrap();
assert!(queue.has_pending(), "one anomaly should be queued");
}
// -----------------------------------------------------------------------
// Pending recognitions in observer snapshot (#423, D-060)
// -----------------------------------------------------------------------
#[test]
fn pending_recognitions_appear_in_snapshot() {
// H11: When a player entity has a CognitiveDelay component with pending
// recognitions, compute_observer_snapshot should include them in
// pending_recognitions for the client to render as grey blobs.
use crate::perception::cognitive_delay::{
CognitiveDelay, PendingRecognition, RecognitionTrigger,
};
let mut world = setup_world(32, 32);
let mut registry = EntityRegistry::new(0);
// Target entity that is being "recognized"
let target = world.spawn_empty().id();
let target_sid = registry.register(target);
// Player with CognitiveDelay containing a pending recognition
let mut cd = CognitiveDelay::default();
cd.push(PendingRecognition {
target,
stable_id: target_sid,
position: TilePosition::new(16, 14, 0),
delay_until_tick: 110, // will complete at tick 110
trigger: RecognitionTrigger::Normal,
});
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing::default(),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
cd,
))
.id();
registry.register(player);
world.insert_resource(registry);
world.insert_resource({
let mut t = SimulationTime::default();
t.tick = 106; // 4 ticks remaining until recognition
t
});
run_observer_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().expect("snapshot should exist");
assert_eq!(
snapshot.pending_recognitions.len(),
1,
"should have one pending recognition in snapshot"
);
let pending = &snapshot.pending_recognitions[0];
assert_eq!(pending.entity_id, target_sid.0);
assert_eq!(pending.remaining_ticks, 4, "110 - 106 = 4 remaining");
assert_eq!(
pending.total_delay_ticks,
crate::perception::cognitive_delay::NORMAL_DELAY_TICKS,
"total delay should match Normal trigger"
);
// Position should be render coords of (16, 14, 0)
let (expected_x, expected_y, expected_z) = TilePosition::new(16, 14, 0).to_render_coords();
assert_eq!(pending.x, expected_x);
assert_eq!(pending.y, expected_y);
assert_eq!(pending.z, expected_z);
}
#[test]
fn no_cognitive_delay_component_means_empty_pending_recognitions() {
// H11 complement: player WITHOUT CognitiveDelay should produce
// an empty pending_recognitions vec (backward compatibility).
let mut world = setup_world(32, 32);
world.spawn((
PlayerCharacter,
TilePosition::new(16, 16, 0),
Facing::default(),
KnowledgeGraph::new(),
NearbyInteractionBuffer::default(),
MonologueBuffer::default(),
));
run_observer_pipeline(&mut world);
let buffer = world.resource::<SnapshotBuffer>();
let snapshot = buffer.snapshot.as_ref().unwrap();
assert!(
snapshot.pending_recognitions.is_empty(),
"no CognitiveDelay component should produce empty pending_recognitions"
);
}