feat(simulation): sprint 9 gauntlet — test infrastructure and first 3 rooms
Add Gauntlet test world with 3 rooms (Inventory Warehouse, Occlusion Corridor, Pause Chamber) + Central Hub, room constants module, room reset trigger mechanism, Layer 3 subprocess integration test, golden file comparison engine and test suite, and content runtime validation. Tickets: #482, #484, #485, #487, #488, #489, #490 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
//! Central Hub — Room 0 (24x24)
|
||||
//!
|
||||
//! Connector room with 4 directional sign markers. No test entities.
|
||||
//! Hub spawn point: (12, 12) relative = (50, 58) absolute.
|
||||
//!
|
||||
//! Entities (StableId 1-4):
|
||||
//! sign_north (50, 48) — "Fog Theater"
|
||||
//! sign_east (60, 58) — "Occlusion Corridor"
|
||||
//! sign_south (50, 68) — "Pause Chamber"
|
||||
//! sign_west (40, 58) — "Inventory Warehouse"
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
use crate::simulation::interaction::Interactable;
|
||||
use crate::simulation::movement::TilePosition;
|
||||
|
||||
/// Room origin (top-left corner including walls).
|
||||
const ORIGIN_X: i32 = 38;
|
||||
const ORIGIN_Y: i32 = 46;
|
||||
|
||||
/// Sign positions as relative offsets from room origin.
|
||||
const SIGNS: &[(&str, i32, i32)] = &[
|
||||
("sign_north", 12, 2), // North marker
|
||||
("sign_east", 22, 12), // East marker
|
||||
("sign_south", 12, 22), // South marker
|
||||
("sign_west", 2, 12), // West marker
|
||||
];
|
||||
|
||||
/// Spawn hub sign entities in canonical order (StableId 1-4).
|
||||
pub fn spawn_entities(app: &mut App, registry: &mut EntityRegistry) {
|
||||
for &(_name, rx, ry) in SIGNS {
|
||||
let pos = TilePosition::new(ORIGIN_X + rx, ORIGIN_Y + ry, 0);
|
||||
let entity = app.world_mut().spawn((Interactable, pos)).id();
|
||||
let sid = registry.register(entity);
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//! Inventory Warehouse — Room 3 (30x28)
|
||||
//!
|
||||
//! Tests D-065 (9-slot inventory), pickup/drop verbs, CarriedBy component.
|
||||
//!
|
||||
//! Layout: Open warehouse floor with 10 item crates in a grid pattern
|
||||
//! and 1 NPC near the back wall for interaction testing while carrying items.
|
||||
//!
|
||||
//! Observer position: (15, 14) relative = (17, 54) absolute, facing East.
|
||||
//!
|
||||
//! Entities (StableId 13-23):
|
||||
//! crate_01..crate_10 — Floor items (Container ObjectType)
|
||||
//! npc_warehouse — Interaction target
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
use crate::bridge::types::ObjectType;
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
use crate::npc::{Contentment, Npc, ToleranceThreshold, Want, WantKind};
|
||||
use crate::simulation::interaction::Interactable;
|
||||
use crate::simulation::inventory::ItemName;
|
||||
use crate::simulation::movement::TilePosition;
|
||||
use crate::simulation::path_follow::MovementSpeed;
|
||||
|
||||
/// Room origin (top-left corner including walls).
|
||||
const ORIGIN_X: i32 = 2;
|
||||
const ORIGIN_Y: i32 = 40;
|
||||
|
||||
/// Item crate definitions: (name, relative_x, relative_y, item_name).
|
||||
/// 10 crates for testing the 9-slot inventory limit.
|
||||
const CRATES: &[(&str, i32, i32, &str)] = &[
|
||||
("crate_01", 4, 4, "Keycard"),
|
||||
("crate_02", 8, 4, "Manifest"),
|
||||
("crate_03", 12, 4, "Datapad"),
|
||||
("crate_04", 16, 4, "Toolkit"),
|
||||
("crate_05", 4, 10, "Badge"),
|
||||
("crate_06", 8, 10, "Medkit"),
|
||||
("crate_07", 12, 10, "Ration"),
|
||||
("crate_08", 16, 10, "Cable"),
|
||||
("crate_09", 4, 16, "Seal"),
|
||||
("crate_10", 8, 16, "Chip"),
|
||||
];
|
||||
|
||||
/// Spawn Inventory Warehouse entities in canonical order (StableId 13-23).
|
||||
pub fn spawn_entities(app: &mut App, registry: &mut EntityRegistry) {
|
||||
// Crates (StableId 13-22)
|
||||
for &(_name, rx, ry, item_name) in CRATES {
|
||||
let pos = TilePosition::new(ORIGIN_X + rx, ORIGIN_Y + ry, 0);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Interactable,
|
||||
ObjectType::Container,
|
||||
ItemName(item_name.to_string()),
|
||||
pos,
|
||||
))
|
||||
.id();
|
||||
let sid = registry.register(entity);
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
|
||||
// Warehouse NPC (StableId 23)
|
||||
let npc_pos = TilePosition::new(ORIGIN_X + 22, ORIGIN_Y + 14, 0);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Npc,
|
||||
Interactable,
|
||||
npc_pos,
|
||||
Want {
|
||||
primary: WantKind::Wealth,
|
||||
intensity: 5,
|
||||
description: "Warehouse supervisor wants efficiency".to_string(),
|
||||
},
|
||||
Contentment { level: 10 },
|
||||
ToleranceThreshold {
|
||||
current_stress: 15,
|
||||
threshold: 65,
|
||||
},
|
||||
MovementSpeed::default(),
|
||||
))
|
||||
.id();
|
||||
let sid = registry.register(entity);
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Gauntlet room builders.
|
||||
//!
|
||||
//! Each room module exports a `spawn_entities()` function that creates
|
||||
//! entities in canonical order for deterministic StableId assignment.
|
||||
|
||||
pub mod hub;
|
||||
pub mod inventory_warehouse;
|
||||
pub mod occlusion_corridor;
|
||||
pub mod pause_chamber;
|
||||
@@ -0,0 +1,66 @@
|
||||
//! Occlusion Corridor — Room 2 (42x22)
|
||||
//!
|
||||
//! Tests D-035 (symmetric shadowcasting), D-017 (perception modes),
|
||||
//! D-015 (vision cone sectors).
|
||||
//!
|
||||
//! Layout: Long east-west corridor with perpendicular wall segments
|
||||
//! creating visibility pockets. North alcove with hidden NPC,
|
||||
//! south alcove with partially visible NPC.
|
||||
//!
|
||||
//! Observer position: (10, 10) relative = (84, 58) absolute, facing East.
|
||||
//!
|
||||
//! Entities (StableId 9-12):
|
||||
//! npc_guard_visible (92, 58) — Clear LOS baseline
|
||||
//! npc_hidden_wall (92, 52) — Behind north wall segment
|
||||
//! npc_peripheral (80, 64) — South alcove, peripheral sector
|
||||
//! npc_far_end (110, 58) — Far end of corridor, tests range
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
use crate::npc::{Contentment, Npc, ToleranceThreshold, Want, WantKind};
|
||||
use crate::simulation::interaction::Interactable;
|
||||
use crate::simulation::movement::TilePosition;
|
||||
use crate::simulation::path_follow::MovementSpeed;
|
||||
|
||||
/// Room origin (top-left corner including walls).
|
||||
const ORIGIN_X: i32 = 74;
|
||||
const ORIGIN_Y: i32 = 48;
|
||||
|
||||
/// NPC definitions: (name, relative_x, relative_y, want_kind, want_intensity).
|
||||
const NPCS: &[(&str, i32, i32, WantKind, u8)] = &[
|
||||
("npc_guard_visible", 18, 10, WantKind::Safety, 5),
|
||||
("npc_hidden_wall", 18, 4, WantKind::Freedom, 3),
|
||||
("npc_peripheral", 6, 16, WantKind::Knowledge, 6),
|
||||
("npc_far_end", 36, 10, WantKind::Wealth, 4),
|
||||
];
|
||||
|
||||
/// Spawn Occlusion Corridor entities in canonical order (StableId 9-12).
|
||||
pub fn spawn_entities(app: &mut App, registry: &mut EntityRegistry) {
|
||||
for &(name, rx, ry, want_kind, intensity) in NPCS {
|
||||
let pos = TilePosition::new(ORIGIN_X + rx, ORIGIN_Y + ry, 0);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Npc,
|
||||
Interactable,
|
||||
pos,
|
||||
Want {
|
||||
primary: want_kind,
|
||||
intensity,
|
||||
description: format!("Occlusion Corridor test NPC: {}", name),
|
||||
},
|
||||
Contentment { level: 0 },
|
||||
ToleranceThreshold {
|
||||
current_stress: 0,
|
||||
threshold: 50,
|
||||
},
|
||||
MovementSpeed::default(),
|
||||
))
|
||||
.id();
|
||||
let sid = registry.register(entity);
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//! Pause Chamber — Room 5 (16x16)
|
||||
//!
|
||||
//! Tests D-031 (pause/unpause), Bug #3 regression (movement while paused).
|
||||
//!
|
||||
//! Layout: Minimal open room with a single NPC. No visual complexity —
|
||||
//! this room is pure state machine testing.
|
||||
//!
|
||||
//! Observer position: (8, 8) relative = (50, 86) absolute, facing North.
|
||||
//!
|
||||
//! Entities (StableId 29):
|
||||
//! npc_pause_target — Interaction target during pause (Talk should work)
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
use crate::npc::{Contentment, Npc, ToleranceThreshold, Want, WantKind};
|
||||
use crate::simulation::interaction::Interactable;
|
||||
use crate::simulation::movement::TilePosition;
|
||||
use crate::simulation::path_follow::MovementSpeed;
|
||||
|
||||
/// Room origin (top-left corner including walls).
|
||||
const ORIGIN_X: i32 = 42;
|
||||
const ORIGIN_Y: i32 = 78;
|
||||
|
||||
/// Spawn Pause Chamber entities in canonical order (StableId 29).
|
||||
pub fn spawn_entities(app: &mut App, registry: &mut EntityRegistry) {
|
||||
let pos = TilePosition::new(ORIGIN_X + 6, ORIGIN_Y + 6, 0);
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Npc,
|
||||
Interactable,
|
||||
pos,
|
||||
Want {
|
||||
primary: WantKind::Safety,
|
||||
intensity: 3,
|
||||
description: "Wants a quiet posting".to_string(),
|
||||
},
|
||||
Contentment { level: 5 },
|
||||
ToleranceThreshold {
|
||||
current_stress: 10,
|
||||
threshold: 40,
|
||||
},
|
||||
MovementSpeed::default(),
|
||||
))
|
||||
.id();
|
||||
let sid = registry.register(entity);
|
||||
app.world_mut()
|
||||
.entity_mut(entity)
|
||||
.insert(StableEntityId(sid));
|
||||
}
|
||||
Reference in New Issue
Block a user