feat(simulation): add Sound Lab, Decay Observatory, Shift Change gauntlet rooms (#505)
Three new gauntlet test rooms for Sprint 13, each targeting a specific system under test. StableIds are additive-only (66–74). Sound Lab (66-68): Three NPCs at calibrated tile distances (2, 6, 12 tiles) verify D-018 three-range sound model. Tests confirm Close ≤3, Medium ≤8, Long ≤20 tile thresholds via SoundEvent::audible_at(). Decay Observatory (69): Single NPC observed in LOS then broken. Tests confirm D-041 confidence decay: KnowsDetails → KnowsOf → Suspects (floor), and Stale state when stale_after threshold is exceeded. Shift Change (70-71): Two NPCs with DailyRoutine components. Test confirms D-031 phase-boundary check_phase_transition issues PathRequest only to NPCs not already at their target location. Sprint 13 reset plates (72-74) wired per existing pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
//! - Entities spawned in canonical order → StableId assignment is deterministic
|
||||
//! - Additive-only: existing rooms/entities never reordered
|
||||
//!
|
||||
//! StableId ranges (from gestalt-round3.md + Sprint 11):
|
||||
//! StableId ranges (from gestalt-round3.md + Sprint 11 + Sprint 13):
|
||||
//! Player: 0
|
||||
//! Hub signs: 1-4
|
||||
//! Fog Theater: 5-8
|
||||
@@ -31,6 +31,10 @@
|
||||
//! Eavesdrop Alcove: 58-60
|
||||
//! Confrontation Stage: 61-62
|
||||
//! Reset plates (Sprint 11 rooms): 63-65
|
||||
//! Sound Lab: 66-68
|
||||
//! Decay Observatory: 69
|
||||
//! Shift Change: 70-71
|
||||
//! Reset plates (Sprint 13 rooms): 72-74
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
pub mod constants;
|
||||
@@ -64,6 +68,8 @@ use crate::simulation::monologue::{MonologueBuffer, MonologueState, SprintAnomal
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::stance::{MovementProfile, PlayerMoveCooldown};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::zone::ZoneMap;
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use reset::{RoomResetTrigger, RoomSnapshots};
|
||||
@@ -101,6 +107,10 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
carve_room_interior(&mut walkability, 0, 2, 28, 20); // Sprint Gauntlet
|
||||
carve_room_interior(&mut walkability, 74, 26, 24, 16); // Eavesdrop Alcove
|
||||
carve_room_interior(&mut walkability, 84, 2, 32, 24); // Confrontation Stage
|
||||
// Sprint 13 rooms
|
||||
carve_room_interior(&mut walkability, 0, 104, 34, 20); // Sound Lab
|
||||
carve_room_interior(&mut walkability, 0, 24, 24, 14); // Decay Observatory
|
||||
carve_room_interior(&mut walkability, 64, 78, 16, 24); // Shift Change
|
||||
|
||||
// Carve corridors between hub and rooms
|
||||
carve_corridor(&mut walkability, 48, 34, 6, 12); // corridor-N: Hub ↔ Fog Theater
|
||||
@@ -130,6 +140,22 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
|
||||
app.insert_resource(walkability);
|
||||
|
||||
// Zone map: assign zone IDs per room bounding box (D-077, D-073).
|
||||
// Zone IDs are sequential per ROOMS order. Corridors remain unzoned (None).
|
||||
let mut zone_map = ZoneMap::default();
|
||||
for (i, room) in constants::ROOMS.iter().enumerate() {
|
||||
let zone_id = i as u16;
|
||||
zone_map.set_rect(
|
||||
room.origin.x,
|
||||
room.origin.y,
|
||||
room.size.0,
|
||||
room.size.1,
|
||||
room.origin.z,
|
||||
zone_id,
|
||||
);
|
||||
}
|
||||
app.insert_resource(zone_map);
|
||||
|
||||
// Entity spawning in canonical StableId order.
|
||||
// Player gets StableId 0, then entities by room in workshop order.
|
||||
let mut registry = EntityRegistry::new(0);
|
||||
@@ -256,6 +282,15 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
// --- Confrontation Stage (StableId 61-62) ---
|
||||
rooms::confrontation_stage::spawn_entities(app, &mut registry);
|
||||
|
||||
// --- Sound Lab (StableId 66-68) ---
|
||||
rooms::sound_lab::spawn_entities(app, &mut registry);
|
||||
|
||||
// --- Decay Observatory (StableId 69) ---
|
||||
rooms::decay_observatory::spawn_entities(app, &mut registry);
|
||||
|
||||
// --- Shift Change (StableId 70-71) ---
|
||||
rooms::shift_change::spawn_entities(app, &mut registry);
|
||||
|
||||
// --- Sprint 11 reset plates (StableId 63-65) ---
|
||||
let sprint11_reset_plates: &[(&str, TilePosition)] = &[
|
||||
(
|
||||
@@ -294,6 +329,44 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
|
||||
// --- Sprint 13 reset plates (StableId 72-74) ---
|
||||
let sprint13_reset_plates: &[(&str, TilePosition)] = &[
|
||||
(
|
||||
"sound_lab",
|
||||
constants::SOUND_LAB
|
||||
.reset_plate
|
||||
.expect("sound_lab should have a reset_plate"),
|
||||
),
|
||||
(
|
||||
"decay_observatory",
|
||||
constants::DECAY_OBSERVATORY
|
||||
.reset_plate
|
||||
.expect("decay_observatory should have a reset_plate"),
|
||||
),
|
||||
(
|
||||
"shift_change",
|
||||
constants::SHIFT_CHANGE
|
||||
.reset_plate
|
||||
.expect("shift_change should have a reset_plate"),
|
||||
),
|
||||
];
|
||||
for &(room_name, pos) in sprint13_reset_plates {
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Interactable,
|
||||
RoomResetTrigger {
|
||||
room_name: room_name.to_string(),
|
||||
},
|
||||
pos,
|
||||
))
|
||||
.id();
|
||||
let sid = registry.register(entity);
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
|
||||
// --- Populate RoomSnapshots for reset mechanism (#490) ---
|
||||
let mut snapshots = RoomSnapshots::default();
|
||||
|
||||
@@ -390,6 +463,35 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sound Lab entities (StableId 66-68): NPCs only, no floor items
|
||||
for id in constants::SOUND_LAB_STABLE_IDS.0..=constants::SOUND_LAB_STABLE_IDS.1 {
|
||||
if let Some(entity) = registry.to_entity(&StableId(id)) {
|
||||
if let Some(pos) = app.world().get::<TilePosition>(entity) {
|
||||
snapshots.record("sound_lab", entity, *pos, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decay Observatory entities (StableId 69): NPC only, no floor items
|
||||
for id in
|
||||
constants::DECAY_OBSERVATORY_STABLE_IDS.0..=constants::DECAY_OBSERVATORY_STABLE_IDS.1
|
||||
{
|
||||
if let Some(entity) = registry.to_entity(&StableId(id)) {
|
||||
if let Some(pos) = app.world().get::<TilePosition>(entity) {
|
||||
snapshots.record("decay_observatory", entity, *pos, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shift Change entities (StableId 70-71): NPCs only, no floor items
|
||||
for id in constants::SHIFT_CHANGE_STABLE_IDS.0..=constants::SHIFT_CHANGE_STABLE_IDS.1 {
|
||||
if let Some(entity) = registry.to_entity(&StableId(id)) {
|
||||
if let Some(pos) = app.world().get::<TilePosition>(entity) {
|
||||
snapshots.record("shift_change", entity, *pos, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.insert_resource(snapshots);
|
||||
app.insert_resource(registry);
|
||||
}
|
||||
@@ -624,5 +726,45 @@ mod tests {
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
// Sound Lab at 66-68
|
||||
for id in constants::SOUND_LAB_STABLE_IDS.0..=constants::SOUND_LAB_STABLE_IDS.1 {
|
||||
assert!(
|
||||
registry.to_entity(&StableId(id)).is_some(),
|
||||
"Sound Lab at StableId {}",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
// Decay Observatory at 69
|
||||
for id in constants::DECAY_OBSERVATORY_STABLE_IDS.0
|
||||
..=constants::DECAY_OBSERVATORY_STABLE_IDS.1
|
||||
{
|
||||
assert!(
|
||||
registry.to_entity(&StableId(id)).is_some(),
|
||||
"Decay Observatory at StableId {}",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
// Shift Change at 70-71
|
||||
for id in constants::SHIFT_CHANGE_STABLE_IDS.0..=constants::SHIFT_CHANGE_STABLE_IDS.1 {
|
||||
assert!(
|
||||
registry.to_entity(&StableId(id)).is_some(),
|
||||
"Shift Change at StableId {}",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
// Sprint 13 reset plates at 72-74
|
||||
for id in constants::SPRINT13_RESET_PLATE_STABLE_IDS.0
|
||||
..=constants::SPRINT13_RESET_PLATE_STABLE_IDS.1
|
||||
{
|
||||
assert!(
|
||||
registry.to_entity(&StableId(id)).is_some(),
|
||||
"Sprint 13 reset plate at StableId {}",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user