Fix all Clippy warnings across the server codebase (2411 insertions, 1341 deletions). Raise type-complexity-threshold to 750 and too-many-arguments to 12 in .clippy.toml for idiomatic Bevy ECS system signatures. The server now passes `cargo clippy -- --deny warnings` cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
604 lines
21 KiB
Rust
604 lines
21 KiB
Rust
//! Gauntlet room and entity constants — single source of truth.
|
|
//!
|
|
//! All Gauntlet tests and room builders reference these constants instead
|
|
//! of magic numbers. Room geometry, observer positions, entity placements,
|
|
//! and StableId ranges are defined here.
|
|
//!
|
|
//! Canonical spawn order determines StableId assignment. Do NOT reorder
|
|
//! existing entries — append new rooms/entities at the end (additive-only).
|
|
//!
|
|
//! Specification source: gestalt-round3.md Section 7.
|
|
|
|
use crate::bridge::types::FacingDirection;
|
|
use crate::perception::vision_cone::Facing;
|
|
use crate::simulation::movement::TilePosition;
|
|
|
|
/// A Gauntlet room definition.
|
|
#[derive(Debug, Clone)]
|
|
pub struct GauntletRoom {
|
|
/// Room identifier string (e.g., "central_hub", "occlusion_corridor").
|
|
pub name: &'static str,
|
|
/// Top-left corner of the room (includes walls).
|
|
pub origin: TilePosition,
|
|
/// (width, height) in sim tiles (includes walls).
|
|
pub size: (i32, i32),
|
|
/// Player spawn position within this room (absolute coordinates).
|
|
pub spawn: TilePosition,
|
|
/// Golden file observer position (absolute coordinates).
|
|
pub observer: TilePosition,
|
|
/// Direction the observer faces for golden file snapshots.
|
|
pub observer_facing: Facing,
|
|
/// Reset plate location (if any). Only present in rooms with
|
|
/// corridor entrances (not the hub).
|
|
pub reset_plate: Option<TilePosition>,
|
|
}
|
|
|
|
/// A Gauntlet entity definition.
|
|
#[derive(Debug, Clone)]
|
|
pub struct GauntletEntity {
|
|
/// Entity name (e.g., "npc_guard_visible", "crate_01").
|
|
pub name: &'static str,
|
|
/// Which room this entity belongs to.
|
|
pub room: &'static str,
|
|
/// Absolute position in the Gauntlet map.
|
|
pub position: TilePosition,
|
|
/// Entity kind for classification.
|
|
pub kind: EntityKind,
|
|
/// StableId assigned to this entity.
|
|
pub stable_id: u64,
|
|
}
|
|
|
|
/// Entity classification for Gauntlet test entities.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum EntityKind {
|
|
Sign,
|
|
Npc,
|
|
Object,
|
|
}
|
|
|
|
// ============================================================
|
|
// Room constants — canonical spawn order
|
|
// ============================================================
|
|
|
|
pub const HUB: GauntletRoom = GauntletRoom {
|
|
name: "central_hub",
|
|
origin: TilePosition { x: 38, y: 46, z: 0 },
|
|
size: (24, 24),
|
|
spawn: TilePosition { x: 50, y: 58, z: 0 },
|
|
observer: TilePosition { x: 50, y: 58, z: 0 },
|
|
observer_facing: Facing(FacingDirection::North),
|
|
reset_plate: None,
|
|
};
|
|
|
|
pub const FOG_THEATER: GauntletRoom = GauntletRoom {
|
|
name: "fog_theater",
|
|
origin: TilePosition { x: 28, y: 2, z: 0 },
|
|
size: (44, 32),
|
|
spawn: TilePosition { x: 56, y: 18, z: 0 },
|
|
observer: TilePosition { x: 56, y: 18, z: 0 },
|
|
observer_facing: Facing(FacingDirection::South),
|
|
reset_plate: Some(TilePosition { x: 50, y: 34, z: 0 }),
|
|
};
|
|
|
|
pub const OCCLUSION_CORRIDOR: GauntletRoom = GauntletRoom {
|
|
name: "occlusion_corridor",
|
|
origin: TilePosition { x: 74, y: 48, z: 0 },
|
|
size: (42, 22),
|
|
spawn: TilePosition { x: 84, y: 58, z: 0 },
|
|
observer: TilePosition { x: 84, y: 58, z: 0 },
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition { x: 73, y: 58, z: 0 }),
|
|
};
|
|
|
|
pub const INVENTORY_WAREHOUSE: GauntletRoom = GauntletRoom {
|
|
name: "inventory_warehouse",
|
|
origin: TilePosition { x: 2, y: 40, z: 0 },
|
|
size: (30, 28),
|
|
spawn: TilePosition { x: 17, y: 54, z: 0 },
|
|
observer: TilePosition { x: 17, y: 54, z: 0 },
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition { x: 33, y: 58, z: 0 }),
|
|
};
|
|
|
|
pub const INTERACTION_GALLERY: GauntletRoom = GauntletRoom {
|
|
name: "interaction_gallery",
|
|
origin: TilePosition { x: 2, y: 82, z: 0 },
|
|
size: (24, 20),
|
|
spawn: TilePosition { x: 14, y: 92, z: 0 },
|
|
observer: TilePosition { x: 14, y: 92, z: 0 },
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition { x: 14, y: 82, z: 0 }),
|
|
};
|
|
|
|
pub const PAUSE_CHAMBER: GauntletRoom = GauntletRoom {
|
|
name: "pause_chamber",
|
|
origin: TilePosition { x: 42, y: 78, z: 0 },
|
|
size: (16, 16),
|
|
spawn: TilePosition { x: 50, y: 86, z: 0 },
|
|
observer: TilePosition { x: 50, y: 86, z: 0 },
|
|
observer_facing: Facing(FacingDirection::North),
|
|
reset_plate: Some(TilePosition { x: 50, y: 77, z: 0 }),
|
|
};
|
|
|
|
pub const DIALOGUE_ROOM: GauntletRoom = GauntletRoom {
|
|
name: "dialogue_room",
|
|
origin: TilePosition {
|
|
x: 36,
|
|
y: 104,
|
|
z: 0,
|
|
},
|
|
size: (28, 20),
|
|
spawn: TilePosition {
|
|
x: 50,
|
|
y: 114,
|
|
z: 0,
|
|
},
|
|
observer: TilePosition {
|
|
x: 50,
|
|
y: 114,
|
|
z: 0,
|
|
},
|
|
observer_facing: Facing(FacingDirection::North),
|
|
reset_plate: Some(TilePosition {
|
|
x: 50,
|
|
y: 103,
|
|
z: 0,
|
|
}),
|
|
};
|
|
|
|
pub const CROWD_PLAZA: GauntletRoom = GauntletRoom {
|
|
name: "crowd_plaza",
|
|
origin: TilePosition { x: 80, y: 78, z: 0 },
|
|
size: (32, 32),
|
|
spawn: TilePosition { x: 96, y: 94, z: 0 },
|
|
observer: TilePosition { x: 96, y: 94, z: 0 },
|
|
observer_facing: Facing(FacingDirection::West),
|
|
reset_plate: Some(TilePosition { x: 80, y: 86, z: 0 }),
|
|
};
|
|
|
|
/// Sprint Gauntlet — Room 8 (28x20)
|
|
/// Tests D-055 sprint suppression. Player sprints past a visible NPC;
|
|
/// interaction buffer must be empty during sprint, anomaly monologue fires
|
|
/// retroactively after sprint ends.
|
|
pub const SPRINT_GAUNTLET: GauntletRoom = GauntletRoom {
|
|
name: "sprint_gauntlet",
|
|
origin: TilePosition { x: 0, y: 2, z: 0 },
|
|
size: (28, 20),
|
|
spawn: TilePosition { x: 4, y: 10, z: 0 },
|
|
observer: TilePosition { x: 4, y: 10, z: 0 },
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition { x: 14, y: 22, z: 0 }),
|
|
};
|
|
|
|
/// Eavesdrop Alcove — Room 9 (24x16)
|
|
/// Tests eavesdrop positioning and ListeningFocus (D-071). Player in Careful
|
|
/// stance at a corner within audible range of two NPCs in conversation.
|
|
pub const EAVESDROP_ALCOVE: GauntletRoom = GauntletRoom {
|
|
name: "eavesdrop_alcove",
|
|
origin: TilePosition { x: 74, y: 26, z: 0 },
|
|
size: (24, 16),
|
|
spawn: TilePosition { x: 78, y: 36, z: 0 },
|
|
observer: TilePosition { x: 78, y: 36, z: 0 },
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition { x: 86, y: 42, z: 0 }),
|
|
};
|
|
|
|
/// Confrontation Stage — Room 10 (32x24)
|
|
/// Tests D-070 confrontation vulnerability. Peripheral NPC movement during
|
|
/// confrontation is suppressed; post-confrontation delayed monologue fires.
|
|
pub const CONFRONTATION_STAGE: GauntletRoom = GauntletRoom {
|
|
name: "confrontation_stage",
|
|
origin: TilePosition { x: 84, y: 2, z: 0 },
|
|
size: (32, 24),
|
|
spawn: TilePosition { x: 94, y: 20, z: 0 },
|
|
observer: TilePosition { x: 94, y: 20, z: 0 },
|
|
observer_facing: Facing(FacingDirection::North),
|
|
reset_plate: Some(TilePosition {
|
|
x: 100,
|
|
y: 26,
|
|
z: 0,
|
|
}),
|
|
};
|
|
|
|
/// Sound Lab — Room 12 (34x20)
|
|
/// Tests D-018 (three-range sound model). Sound-emitting NPCs at calibrated
|
|
/// distances from the observer: Close (2 tiles), Medium (6 tiles), Long (12 tiles).
|
|
pub const SOUND_LAB: GauntletRoom = GauntletRoom {
|
|
name: "sound_lab",
|
|
origin: TilePosition { x: 0, y: 104, z: 0 },
|
|
size: (34, 20),
|
|
spawn: TilePosition {
|
|
x: 10,
|
|
y: 114,
|
|
z: 0,
|
|
},
|
|
observer: TilePosition {
|
|
x: 10,
|
|
y: 114,
|
|
z: 0,
|
|
},
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition {
|
|
x: 16,
|
|
y: 103,
|
|
z: 0,
|
|
}),
|
|
};
|
|
|
|
/// Decay Observatory — Room 13 (24x14)
|
|
/// Tests D-041 knowledge graph decay. NPC observed in LOS then LOS broken;
|
|
/// confidence degrades Direct → KnowsDetails → KnowsOf → Suspects over ticks.
|
|
pub const DECAY_OBSERVATORY: GauntletRoom = GauntletRoom {
|
|
name: "decay_observatory",
|
|
origin: TilePosition { x: 0, y: 24, z: 0 },
|
|
size: (24, 14),
|
|
spawn: TilePosition { x: 10, y: 30, z: 0 },
|
|
observer: TilePosition { x: 10, y: 30, z: 0 },
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition { x: 15, y: 23, z: 0 }),
|
|
};
|
|
|
|
/// Shift Change — Room 14 (16x24)
|
|
/// Tests D-031 day-phase transitions. Two NPCs with DailyRoutine components
|
|
/// receive PathRequests when game clock crosses a phase boundary.
|
|
pub const SHIFT_CHANGE: GauntletRoom = GauntletRoom {
|
|
name: "shift_change",
|
|
origin: TilePosition { x: 64, y: 78, z: 0 },
|
|
size: (16, 24),
|
|
spawn: TilePosition { x: 72, y: 90, z: 0 },
|
|
observer: TilePosition { x: 72, y: 90, z: 0 },
|
|
observer_facing: Facing(FacingDirection::North),
|
|
reset_plate: Some(TilePosition { x: 72, y: 78, z: 0 }),
|
|
};
|
|
|
|
/// Zone Gate — Room 15 (16x22)
|
|
/// Tests D-077 zone assignment and zone-crossing detection (#512).
|
|
///
|
|
/// Room is split at x=72 (absolute) into two zones:
|
|
/// Terminal side (x=64-71): ZONE_GATE_TERMINAL_ZONE_ID (zone_id=100)
|
|
/// Corridor side (x=72-79): ZONE_GATE_CORRIDOR_ZONE_ID (zone_id=101)
|
|
///
|
|
/// A door entity sits on the boundary at (72, 112, 0). Moving from the
|
|
/// Terminal side to the Corridor side (or vice versa) fires ZoneCrossEvent.
|
|
///
|
|
/// Observer starts on the Terminal side at (70, 112, 0), facing East.
|
|
pub const ZONE_GATE: GauntletRoom = GauntletRoom {
|
|
name: "zone_gate",
|
|
origin: TilePosition {
|
|
x: 64,
|
|
y: 102,
|
|
z: 0,
|
|
},
|
|
size: (16, 22),
|
|
spawn: TilePosition {
|
|
x: 70,
|
|
y: 112,
|
|
z: 0,
|
|
},
|
|
observer: TilePosition {
|
|
x: 70,
|
|
y: 112,
|
|
z: 0,
|
|
},
|
|
observer_facing: Facing(FacingDirection::East),
|
|
reset_plate: Some(TilePosition {
|
|
x: 72,
|
|
y: 102,
|
|
z: 0,
|
|
}),
|
|
};
|
|
|
|
/// Zone ID for the Terminal (left/west) half of the Zone Gate room.
|
|
pub const ZONE_GATE_TERMINAL_ZONE_ID: u16 = 100;
|
|
|
|
/// Zone ID for the Corridor (right/east) half of the Zone Gate room.
|
|
pub const ZONE_GATE_CORRIDOR_ZONE_ID: u16 = 101;
|
|
|
|
/// All rooms in canonical spawn order.
|
|
/// THIS ORDER DETERMINES STABLEID ASSIGNMENT.
|
|
/// Do not reorder existing entries. Append new rooms at the end.
|
|
pub const ROOMS: &[GauntletRoom] = &[
|
|
HUB,
|
|
FOG_THEATER,
|
|
OCCLUSION_CORRIDOR,
|
|
INVENTORY_WAREHOUSE,
|
|
INTERACTION_GALLERY,
|
|
PAUSE_CHAMBER,
|
|
DIALOGUE_ROOM,
|
|
CROWD_PLAZA,
|
|
SPRINT_GAUNTLET,
|
|
EAVESDROP_ALCOVE,
|
|
CONFRONTATION_STAGE,
|
|
SOUND_LAB,
|
|
DECAY_OBSERVATORY,
|
|
SHIFT_CHANGE,
|
|
ZONE_GATE,
|
|
];
|
|
|
|
/// Look up which room a position falls in.
|
|
/// Returns the first room whose bounding box contains the position.
|
|
///
|
|
/// Assumptions:
|
|
/// - All rooms are at z=0 (single-floor Gauntlet). Positions on other
|
|
/// z-levels will never match.
|
|
/// - Room bounding boxes must not overlap (verified by `rooms_do_not_overlap`
|
|
/// test). Corridors are NOT rooms and intentionally fall outside all
|
|
/// bounding boxes — `room_at` returns None for corridor positions.
|
|
pub fn room_at(pos: &TilePosition) -> Option<&'static GauntletRoom> {
|
|
ROOMS.iter().find(|r| {
|
|
pos.x >= r.origin.x
|
|
&& pos.x < r.origin.x + r.size.0
|
|
&& pos.y >= r.origin.y
|
|
&& pos.y < r.origin.y + r.size.1
|
|
&& pos.z == r.origin.z
|
|
})
|
|
}
|
|
|
|
// ============================================================
|
|
// StableId range constants
|
|
// ============================================================
|
|
|
|
/// Player entity always gets StableId 0.
|
|
pub const PLAYER_STABLE_ID: u64 = 0;
|
|
|
|
/// StableId ranges per room (start, end inclusive).
|
|
pub const HUB_STABLE_IDS: (u64, u64) = (1, 4);
|
|
pub const FOG_THEATER_STABLE_IDS: (u64, u64) = (5, 8);
|
|
pub const OCCLUSION_STABLE_IDS: (u64, u64) = (9, 12);
|
|
pub const INVENTORY_STABLE_IDS: (u64, u64) = (13, 23);
|
|
pub const INTERACTION_GALLERY_STABLE_IDS: (u64, u64) = (24, 28);
|
|
pub const PAUSE_CHAMBER_STABLE_IDS: (u64, u64) = (29, 29);
|
|
pub const DIALOGUE_ROOM_STABLE_IDS: (u64, u64) = (30, 33);
|
|
pub const CROWD_PLAZA_STABLE_IDS: (u64, u64) = (34, 48);
|
|
pub const RESET_PLATE_STABLE_IDS: (u64, u64) = (49, 55);
|
|
// Sprint 11 rooms — appended after RESET_PLATE_STABLE_IDS per additive-only rule.
|
|
pub const SPRINT_GAUNTLET_STABLE_IDS: (u64, u64) = (56, 57);
|
|
pub const EAVESDROP_ALCOVE_STABLE_IDS: (u64, u64) = (58, 60);
|
|
pub const CONFRONTATION_STAGE_STABLE_IDS: (u64, u64) = (61, 62);
|
|
/// Reset plates for Sprint 11 rooms (sprint_gauntlet, eavesdrop_alcove, confrontation_stage).
|
|
pub const SPRINT11_RESET_PLATE_STABLE_IDS: (u64, u64) = (63, 65);
|
|
// Sprint 13 rooms — appended after SPRINT11_RESET_PLATE_STABLE_IDS per additive-only rule.
|
|
pub const SOUND_LAB_STABLE_IDS: (u64, u64) = (66, 68);
|
|
pub const DECAY_OBSERVATORY_STABLE_IDS: (u64, u64) = (69, 69);
|
|
pub const SHIFT_CHANGE_STABLE_IDS: (u64, u64) = (70, 71);
|
|
/// Reset plates for Sprint 13 rooms (sound_lab, decay_observatory, shift_change).
|
|
pub const SPRINT13_RESET_PLATE_STABLE_IDS: (u64, u64) = (72, 74);
|
|
// Sprint 22 rooms — appended after SPRINT13_RESET_PLATE_STABLE_IDS per additive-only rule.
|
|
/// Zone Gate room: door entity at the zone boundary.
|
|
pub const ZONE_GATE_STABLE_IDS: (u64, u64) = (75, 75);
|
|
/// Reset plate for Sprint 22 rooms (zone_gate).
|
|
pub const SPRINT22_RESET_PLATE_STABLE_IDS: (u64, u64) = (76, 76);
|
|
|
|
/// Number of actively-spawned entities in the current Gauntlet build.
|
|
/// Derived from StableId ranges of all rooms + player + reset plates.
|
|
pub const EXPECTED_ENTITY_COUNT: usize = 1 // player (StableId 0)
|
|
+ (HUB_STABLE_IDS.1 - HUB_STABLE_IDS.0 + 1) as usize
|
|
+ (FOG_THEATER_STABLE_IDS.1 - FOG_THEATER_STABLE_IDS.0 + 1) as usize
|
|
+ (OCCLUSION_STABLE_IDS.1 - OCCLUSION_STABLE_IDS.0 + 1) as usize
|
|
+ (INVENTORY_STABLE_IDS.1 - INVENTORY_STABLE_IDS.0 + 1) as usize
|
|
+ (INTERACTION_GALLERY_STABLE_IDS.1 - INTERACTION_GALLERY_STABLE_IDS.0 + 1) as usize
|
|
+ (PAUSE_CHAMBER_STABLE_IDS.1 - PAUSE_CHAMBER_STABLE_IDS.0 + 1) as usize
|
|
+ (DIALOGUE_ROOM_STABLE_IDS.1 - DIALOGUE_ROOM_STABLE_IDS.0 + 1) as usize
|
|
+ (CROWD_PLAZA_STABLE_IDS.1 - CROWD_PLAZA_STABLE_IDS.0 + 1) as usize
|
|
+ (RESET_PLATE_STABLE_IDS.1 - RESET_PLATE_STABLE_IDS.0 + 1) as usize
|
|
+ (SPRINT_GAUNTLET_STABLE_IDS.1 - SPRINT_GAUNTLET_STABLE_IDS.0 + 1) as usize
|
|
+ (EAVESDROP_ALCOVE_STABLE_IDS.1 - EAVESDROP_ALCOVE_STABLE_IDS.0 + 1) as usize
|
|
+ (CONFRONTATION_STAGE_STABLE_IDS.1 - CONFRONTATION_STAGE_STABLE_IDS.0 + 1) as usize
|
|
+ (SPRINT11_RESET_PLATE_STABLE_IDS.1 - SPRINT11_RESET_PLATE_STABLE_IDS.0 + 1) as usize
|
|
+ (SOUND_LAB_STABLE_IDS.1 - SOUND_LAB_STABLE_IDS.0 + 1) as usize
|
|
+ (DECAY_OBSERVATORY_STABLE_IDS.1 - DECAY_OBSERVATORY_STABLE_IDS.0 + 1) as usize
|
|
+ (SHIFT_CHANGE_STABLE_IDS.1 - SHIFT_CHANGE_STABLE_IDS.0 + 1) as usize
|
|
+ (SPRINT13_RESET_PLATE_STABLE_IDS.1 - SPRINT13_RESET_PLATE_STABLE_IDS.0 + 1) as usize
|
|
+ (ZONE_GATE_STABLE_IDS.1 - ZONE_GATE_STABLE_IDS.0 + 1) as usize
|
|
+ (SPRINT22_RESET_PLATE_STABLE_IDS.1 - SPRINT22_RESET_PLATE_STABLE_IDS.0 + 1) as usize;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn room_at_finds_hub() {
|
|
let pos = TilePosition { x: 50, y: 58, z: 0 };
|
|
let room = room_at(&pos).expect("Hub center should be in a room");
|
|
assert_eq!(room.name, "central_hub");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_occlusion_corridor() {
|
|
let pos = TilePosition { x: 84, y: 58, z: 0 };
|
|
let room = room_at(&pos).expect("Occlusion observer should be in a room");
|
|
assert_eq!(room.name, "occlusion_corridor");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_inventory_warehouse() {
|
|
let pos = TilePosition { x: 17, y: 54, z: 0 };
|
|
let room = room_at(&pos).expect("Inventory observer should be in a room");
|
|
assert_eq!(room.name, "inventory_warehouse");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_pause_chamber() {
|
|
let pos = TilePosition { x: 50, y: 86, z: 0 };
|
|
let room = room_at(&pos).expect("Pause observer should be in a room");
|
|
assert_eq!(room.name, "pause_chamber");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_fog_theater() {
|
|
let pos = TilePosition { x: 56, y: 18, z: 0 };
|
|
let room = room_at(&pos).expect("Fog Theater observer should be in a room");
|
|
assert_eq!(room.name, "fog_theater");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_interaction_gallery() {
|
|
let pos = TilePosition { x: 14, y: 92, z: 0 };
|
|
let room = room_at(&pos).expect("Interaction Gallery observer should be in a room");
|
|
assert_eq!(room.name, "interaction_gallery");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_dialogue_room() {
|
|
let pos = TilePosition {
|
|
x: 50,
|
|
y: 114,
|
|
z: 0,
|
|
};
|
|
let room = room_at(&pos).expect("Dialogue Room observer should be in a room");
|
|
assert_eq!(room.name, "dialogue_room");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_crowd_plaza() {
|
|
let pos = TilePosition { x: 96, y: 94, z: 0 };
|
|
let room = room_at(&pos).expect("Crowd Plaza observer should be in a room");
|
|
assert_eq!(room.name, "crowd_plaza");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_sprint_gauntlet() {
|
|
let pos = TilePosition { x: 4, y: 10, z: 0 };
|
|
let room = room_at(&pos).expect("Sprint Gauntlet observer should be in a room");
|
|
assert_eq!(room.name, "sprint_gauntlet");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_eavesdrop_alcove() {
|
|
let pos = TilePosition { x: 78, y: 36, z: 0 };
|
|
let room = room_at(&pos).expect("Eavesdrop Alcove observer should be in a room");
|
|
assert_eq!(room.name, "eavesdrop_alcove");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_finds_confrontation_stage() {
|
|
let pos = TilePosition { x: 94, y: 20, z: 0 };
|
|
let room = room_at(&pos).expect("Confrontation Stage observer should be in a room");
|
|
assert_eq!(room.name, "confrontation_stage");
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_returns_none_for_corridor() {
|
|
// Point inside corridor-E (between Hub and Occlusion)
|
|
let pos = TilePosition { x: 66, y: 57, z: 0 };
|
|
assert!(
|
|
room_at(&pos).is_none(),
|
|
"Corridor should not be in any room"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn room_at_returns_none_for_outside_map() {
|
|
let pos = TilePosition {
|
|
x: 200,
|
|
y: 200,
|
|
z: 0,
|
|
};
|
|
assert!(room_at(&pos).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn all_rooms_in_correct_order() {
|
|
assert_eq!(ROOMS.len(), 15);
|
|
assert_eq!(ROOMS[0].name, "central_hub");
|
|
assert_eq!(ROOMS[1].name, "fog_theater");
|
|
assert_eq!(ROOMS[2].name, "occlusion_corridor");
|
|
assert_eq!(ROOMS[3].name, "inventory_warehouse");
|
|
assert_eq!(ROOMS[4].name, "interaction_gallery");
|
|
assert_eq!(ROOMS[5].name, "pause_chamber");
|
|
assert_eq!(ROOMS[6].name, "dialogue_room");
|
|
assert_eq!(ROOMS[7].name, "crowd_plaza");
|
|
assert_eq!(ROOMS[8].name, "sprint_gauntlet");
|
|
assert_eq!(ROOMS[9].name, "eavesdrop_alcove");
|
|
assert_eq!(ROOMS[10].name, "confrontation_stage");
|
|
assert_eq!(ROOMS[11].name, "sound_lab");
|
|
assert_eq!(ROOMS[12].name, "decay_observatory");
|
|
assert_eq!(ROOMS[13].name, "shift_change");
|
|
assert_eq!(ROOMS[14].name, "zone_gate");
|
|
}
|
|
|
|
#[test]
|
|
fn rooms_do_not_overlap() {
|
|
for (i, a) in ROOMS.iter().enumerate() {
|
|
for (j, b) in ROOMS.iter().enumerate() {
|
|
if i >= j {
|
|
continue;
|
|
}
|
|
let overlap_x =
|
|
a.origin.x < b.origin.x + b.size.0 && a.origin.x + a.size.0 > b.origin.x;
|
|
let overlap_y =
|
|
a.origin.y < b.origin.y + b.size.1 && a.origin.y + a.size.1 > b.origin.y;
|
|
assert!(
|
|
!(overlap_x && overlap_y),
|
|
"Rooms {} and {} overlap",
|
|
a.name,
|
|
b.name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn observer_inside_room() {
|
|
for room in ROOMS {
|
|
let obs = &room.observer;
|
|
assert!(
|
|
obs.x >= room.origin.x && obs.x < room.origin.x + room.size.0,
|
|
"Observer x={} outside room {} (origin.x={}, width={})",
|
|
obs.x,
|
|
room.name,
|
|
room.origin.x,
|
|
room.size.0
|
|
);
|
|
assert!(
|
|
obs.y >= room.origin.y && obs.y < room.origin.y + room.size.1,
|
|
"Observer y={} outside room {} (origin.y={}, height={})",
|
|
obs.y,
|
|
room.name,
|
|
room.origin.y,
|
|
room.size.1
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stable_id_ranges_non_overlapping() {
|
|
let ranges = [
|
|
HUB_STABLE_IDS,
|
|
FOG_THEATER_STABLE_IDS,
|
|
OCCLUSION_STABLE_IDS,
|
|
INVENTORY_STABLE_IDS,
|
|
INTERACTION_GALLERY_STABLE_IDS,
|
|
PAUSE_CHAMBER_STABLE_IDS,
|
|
DIALOGUE_ROOM_STABLE_IDS,
|
|
CROWD_PLAZA_STABLE_IDS,
|
|
RESET_PLATE_STABLE_IDS,
|
|
SPRINT_GAUNTLET_STABLE_IDS,
|
|
EAVESDROP_ALCOVE_STABLE_IDS,
|
|
CONFRONTATION_STAGE_STABLE_IDS,
|
|
SPRINT11_RESET_PLATE_STABLE_IDS,
|
|
SOUND_LAB_STABLE_IDS,
|
|
DECAY_OBSERVATORY_STABLE_IDS,
|
|
SHIFT_CHANGE_STABLE_IDS,
|
|
SPRINT13_RESET_PLATE_STABLE_IDS,
|
|
ZONE_GATE_STABLE_IDS,
|
|
SPRINT22_RESET_PLATE_STABLE_IDS,
|
|
];
|
|
for (i, a) in ranges.iter().enumerate() {
|
|
for (j, b) in ranges.iter().enumerate() {
|
|
if i >= j {
|
|
continue;
|
|
}
|
|
assert!(
|
|
a.1 < b.0 || b.1 < a.0,
|
|
"StableId ranges {} and {} overlap: {:?} vs {:?}",
|
|
i,
|
|
j,
|
|
a,
|
|
b
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|