feat(simulation): add Zone Gate gauntlet room and zone crossing detection (#512)

New test_world room with two zones (Terminal/Corridor) separated by
a door. Adds ZoneCrossEventQueue resource and detect_zone_crossings
system to fire events when the player crosses zone boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 16:34:21 +01:00
co-authored by Claude Opus 4.6
parent 61eb40fcab
commit daae3dd6ab
6 changed files with 501 additions and 3 deletions
+56 -2
View File
@@ -255,6 +255,49 @@ pub const SHIFT_CHANGE: GauntletRoom = GauntletRoom {
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.
@@ -273,6 +316,7 @@ pub const ROOMS: &[GauntletRoom] = &[
SOUND_LAB,
DECAY_OBSERVATORY,
SHIFT_CHANGE,
ZONE_GATE,
];
/// Look up which room a position falls in.
@@ -323,6 +367,11 @@ 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.
@@ -343,7 +392,9 @@ pub const EXPECTED_ENTITY_COUNT: usize = 1 // player (StableId 0)
+ (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;
+ (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 {
@@ -452,7 +503,7 @@ mod tests {
#[test]
fn all_rooms_in_correct_order() {
assert_eq!(ROOMS.len(), 14);
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");
@@ -467,6 +518,7 @@ mod tests {
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]
@@ -533,6 +585,8 @@ mod tests {
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() {