From dd1dd0feec8823a45d8d288d60bba63b25fd853a Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 19 Feb 2026 18:22:48 +0100 Subject: [PATCH] =?UTF-8?q?fix(simulation):=20address=20PR=20#46=20review?= =?UTF-8?q?=20=E2=80=94=20zone=20tests,=20doc=20accuracy,=20TBD=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 3 zone enrichment tests to observer pipeline (tile inside zone, tile outside zone, absent ZoneMap resource). Fix misleading Decay Observatory doc comment that claimed Direct start when test starts at KnowsDetails. Add production population TBD note on ZoneMap. Co-Authored-By: Claude Opus 4.6 --- server/src/perception/observer/tests.rs | 113 ++++++++++++++++++ server/src/simulation/zone.rs | 3 + .../src/test_world/rooms/decay_observatory.rs | 7 +- 3 files changed, 120 insertions(+), 3 deletions(-) diff --git a/server/src/perception/observer/tests.rs b/server/src/perception/observer/tests.rs index f2b1d3024..d564eac2f 100644 --- a/server/src/perception/observer/tests.rs +++ b/server/src/perception/observer/tests.rs @@ -2466,3 +2466,116 @@ fn blocked_entities_sorted_ascending() { assert!(snapshot.blocked_entities.contains(&npc_b_sid.0)); assert!(snapshot.blocked_entities.contains(&npc_c_sid.0)); } + +// ----------------------------------------------------------------------- +// Zone enrichment tests (#523, D-077) +// ----------------------------------------------------------------------- + +#[test] +fn zone_map_enriches_visible_tiles_with_zone_id() { + // Tile inside a zone should get zone_id = Some(zone_id) + use crate::simulation::zone::ZoneMap; + + let mut world = setup_world(32, 32); + let mut zone_map = ZoneMap::default(); + // Zone 42 covers (14..18, 14..18) — includes the observer's tile at (16,16) + zone_map.set_rect(14, 14, 4, 4, 0, 42); + world.insert_resource(zone_map); + + 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::(); + let snapshot = buffer.snapshot.as_ref().unwrap(); + + // Observer's tile (16, 16) is inside zone 42 + let observer_tile = snapshot + .visible_tiles + .iter() + .find(|t| t.x == 16 && t.y == 16 && t.z == 0) + .expect("observer tile should be visible"); + assert_eq!( + observer_tile.zone_id, + Some(42), + "tile inside zone should have zone_id" + ); +} + +#[test] +fn zone_map_tiles_outside_zone_get_none() { + // Tile outside any zone should get zone_id = None + use crate::simulation::zone::ZoneMap; + + let mut world = setup_world(32, 32); + let mut zone_map = ZoneMap::default(); + // Zone only covers (0..2, 0..2) — far from observer at (16,16) + zone_map.set_rect(0, 0, 2, 2, 0, 7); + world.insert_resource(zone_map); + + 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::(); + let snapshot = buffer.snapshot.as_ref().unwrap(); + + // Observer's tile (16, 16) is outside any zone + let observer_tile = snapshot + .visible_tiles + .iter() + .find(|t| t.x == 16 && t.y == 16 && t.z == 0) + .expect("observer tile should be visible"); + assert_eq!( + observer_tile.zone_id, None, + "tile outside any zone should have zone_id = None" + ); +} + +#[test] +fn no_zone_map_resource_tiles_have_no_zone_id() { + // When ZoneMap resource is absent, all tiles should have zone_id = None + let mut world = setup_world(32, 32); + // Do NOT insert ZoneMap resource + + 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::(); + let snapshot = buffer.snapshot.as_ref().unwrap(); + + assert!( + !snapshot.visible_tiles.is_empty(), + "should have visible tiles" + ); + // All tiles should have zone_id = None when no ZoneMap exists + for tile in &snapshot.visible_tiles { + assert_eq!( + tile.zone_id, None, + "tile ({},{}) should have zone_id = None without ZoneMap resource", + tile.x, tile.y + ); + } +} diff --git a/server/src/simulation/zone.rs b/server/src/simulation/zone.rs index baccf096a..ebefd79ef 100644 --- a/server/src/simulation/zone.rs +++ b/server/src/simulation/zone.rs @@ -18,6 +18,9 @@ use bevy_ecs::prelude::*; /// /// Zone IDs are opaque u16 values — the client maintains its own /// `zone_id → zone_name / temperature_tint / ambient_layer` mapping. +/// +/// Production population path is TBD — currently populated only by +/// Gauntlet room builders via `set_rect` / `set`. #[derive(Resource, Debug, Default)] pub struct ZoneMap { zones: BTreeMap<(i32, i32, i32), u16>, diff --git a/server/src/test_world/rooms/decay_observatory.rs b/server/src/test_world/rooms/decay_observatory.rs index 7daef4412..152a88e50 100644 --- a/server/src/test_world/rooms/decay_observatory.rs +++ b/server/src/test_world/rooms/decay_observatory.rs @@ -1,8 +1,9 @@ //! Decay Observatory — Room 13 (24x14) //! -//! Tests D-041 (knowledge graph decay). Observer starts with Direct confidence -//! on an NPC in LOS, then LOS is broken. Tick advances verify confidence -//! degrades: Direct → KnowsDetails → KnowsOf → Suspects → Stale (D-041). +//! Tests D-041 (knowledge graph decay). Observer sees an NPC (Direct), then +//! LOS is broken (drops to KnowsDetails). Tick advances verify confidence +//! degrades: KnowsDetails → KnowsOf → Suspects (floor). Separate test +//! confirms Stale state triggers when age exceeds stale_after threshold. //! //! Decay runs once per game-minute (every 10 ticks, D-031). Confidence floor //! is Suspects — decay never removes an entity from the knowledge graph.