refactor(simulation): address PR #32 review — 14 items from Hoshe + Tyre
Hoshe (code quality): - Remove dead RoomMember component from reset.rs - Remove execute_reset (dual API trap); plan_reset is sole production path - .unwrap() → .expect() on reset_plate in setup_gauntlet boot path - Add 10s read timeout to TCP runtime test (prevents hangs) - Register player in EntityRegistry in runtime boot test - Document room_at z-range and corridor overlap assumptions - Derive entity count from EXPECTED_ENTITY_COUNT constant (was hardcoded 24) - Add reset plate (49-51) verification to stable_id_ranges_match_spec - Add debounce exact boundary test (tick 9 rejected, tick 10 accepted) Tyre (architecture): - Gate test_world rooms/constants/setup behind "gauntlet" feature (default-on); reset module stays always-compiled (production dependency via input system) - Document setup_gauntlet scheduler bypass for future tracking - Extract runtime TCP test to content_runtime.rs (separate failure modes) 507 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,11 @@
|
||||
//! and manual QA sessions. Loaded instead of content/ when the server runs
|
||||
//! the Gauntlet map.
|
||||
//!
|
||||
//! Module structure:
|
||||
//! - `reset` — always compiled (used by simulation::input in production)
|
||||
//! - `constants`, `rooms`, `setup_gauntlet` — gated behind "gauntlet" feature
|
||||
//! (default-on; strip with --no-default-features for release builds)
|
||||
//!
|
||||
//! Architecture (per workshop-outcomes.md Section 4):
|
||||
//! - Rooms are defined as Rust builder functions (hybrid YAML + Rust inject)
|
||||
//! - WalkabilityMap covers the full Gauntlet bounds (0-116 x 0-124)
|
||||
@@ -23,28 +28,45 @@
|
||||
//! Crowd Plaza: 34-48 (reserved, not yet built)
|
||||
//! Reset plates: 49-51 (Occlusion, Inventory, Pause)
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
pub mod constants;
|
||||
pub mod reset;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
pub mod rooms;
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::knowledge::KnowledgeGraph;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::knowledge::types::StableId;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::perception::cognitive_delay::CognitiveDelay;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::perception::vision_cone::Facing;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::interaction::{Interactable, NearbyInteractionBuffer};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::inventory::ItemName;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::listening::ListeningFocus;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::monologue::{MonologueBuffer, MonologueState, SprintAnomalyQueue};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::stance::{MovementProfile, PlayerMoveCooldown};
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use reset::{RoomResetTrigger, RoomSnapshots};
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
/// Map bounds for the full Gauntlet world.
|
||||
pub const MAP_WIDTH: i32 = 117;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
pub const MAP_HEIGHT: i32 = 125;
|
||||
|
||||
/// Set up the Gauntlet test world.
|
||||
@@ -52,6 +74,12 @@ pub const MAP_HEIGHT: i32 = 125;
|
||||
/// Creates the full WalkabilityMap (blocked by default), carves room
|
||||
/// interiors and corridors, spawns the player and all room entities
|
||||
/// in canonical StableId order.
|
||||
///
|
||||
/// NOTE: This bypasses the normal startup system scheduler — entities are
|
||||
/// spawned directly into the World rather than via startup systems. This
|
||||
/// is intentional for deterministic test setups but should be revisited
|
||||
/// if Gauntlet is ever served by the production startup pipeline.
|
||||
#[cfg(feature = "gauntlet")]
|
||||
pub fn setup_gauntlet(app: &mut App) {
|
||||
// Start with a fully blocked map, then carve rooms and corridors.
|
||||
let mut walkability = WalkabilityMap::new_blocked(MAP_WIDTH, MAP_HEIGHT, 1);
|
||||
@@ -137,9 +165,9 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
// Spawned at corridor entrances per workshop-outcomes.md Section 8.
|
||||
// Each plate triggers reset of its associated room.
|
||||
let reset_plates: &[(&str, TilePosition)] = &[
|
||||
("occlusion_corridor", constants::OCCLUSION_CORRIDOR.reset_plate.unwrap()),
|
||||
("inventory_warehouse", constants::INVENTORY_WAREHOUSE.reset_plate.unwrap()),
|
||||
("pause_chamber", constants::PAUSE_CHAMBER.reset_plate.unwrap()),
|
||||
("occlusion_corridor", constants::OCCLUSION_CORRIDOR.reset_plate.expect("occlusion_corridor should have a reset_plate")),
|
||||
("inventory_warehouse", constants::INVENTORY_WAREHOUSE.reset_plate.expect("inventory_warehouse should have a reset_plate")),
|
||||
("pause_chamber", constants::PAUSE_CHAMBER.reset_plate.expect("pause_chamber should have a reset_plate")),
|
||||
];
|
||||
for &(room_name, pos) in reset_plates {
|
||||
let entity = app
|
||||
@@ -191,6 +219,7 @@ pub fn setup_gauntlet(app: &mut App) {
|
||||
app.insert_resource(registry);
|
||||
}
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
/// Carve the walkable interior of a room.
|
||||
/// Room has 2-tile-thick walls; interior starts 2 tiles inside each boundary.
|
||||
fn carve_room_interior(wm: &mut WalkabilityMap, ox: i32, oy: i32, w: i32, h: i32) {
|
||||
@@ -201,6 +230,7 @@ fn carve_room_interior(wm: &mut WalkabilityMap, ox: i32, oy: i32, w: i32, h: i32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
/// Carve a corridor as a fully walkable rectangle.
|
||||
/// Corridors are 6 tiles wide with walls on both sides — we carve the
|
||||
/// interior 2 tiles in from each edge (leaving 2-tile walkable center).
|
||||
@@ -215,7 +245,7 @@ fn carve_corridor(wm: &mut WalkabilityMap, ox: i32, oy: i32, w: i32, h: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "gauntlet"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::knowledge::registry::EntityRegistry;
|
||||
@@ -230,10 +260,11 @@ mod tests {
|
||||
setup_gauntlet(&mut app);
|
||||
|
||||
let registry = app.world().resource::<EntityRegistry>();
|
||||
// Player (0) + Hub signs (1-4) + Occlusion (9-12) + Inventory (13-23) + Pause (29)
|
||||
// + Reset plates (49-51)
|
||||
// = 1 + 4 + 4 + 11 + 1 + 3 = 24 entities
|
||||
assert_eq!(registry.len(), 24);
|
||||
assert_eq!(
|
||||
registry.len(),
|
||||
constants::EXPECTED_ENTITY_COUNT,
|
||||
"entity count should match EXPECTED_ENTITY_COUNT derived from StableId ranges"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -322,5 +353,14 @@ mod tests {
|
||||
|
||||
// Pause Chamber at 29
|
||||
assert!(registry.to_entity(&StableId(29)).is_some(), "Pause Chamber at StableId 29");
|
||||
|
||||
// Reset plates at 49-51
|
||||
for id in constants::RESET_PLATE_STABLE_IDS.0..=constants::RESET_PLATE_STABLE_IDS.1 {
|
||||
assert!(
|
||||
registry.to_entity(&StableId(id)).is_some(),
|
||||
"Reset plate at StableId {}",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user