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:
@@ -9,10 +9,12 @@ pub mod conversation;
|
||||
pub mod dialogue;
|
||||
pub mod examine;
|
||||
pub mod follow;
|
||||
pub mod generator;
|
||||
pub mod input;
|
||||
pub mod interaction;
|
||||
pub mod inventory;
|
||||
pub mod listening;
|
||||
pub mod modification;
|
||||
pub mod monologue;
|
||||
pub mod movement;
|
||||
pub mod npc_knowledge_transfer;
|
||||
@@ -63,6 +65,9 @@ impl Plugin for SimulationPlugin {
|
||||
.init_resource::<crate::npc::relationships::RelationshipGraph>()
|
||||
.init_resource::<crate::knowledge::KnowledgeEventQueue>()
|
||||
.init_resource::<interaction::TerminalInteractedQueue>()
|
||||
// Zone crossing event queue (#512, D-077): detect when player moves between zones.
|
||||
.init_resource::<zone::ZoneCrossEventQueue>()
|
||||
.init_resource::<zone::PreviousPlayerZone>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
@@ -116,6 +121,12 @@ impl Plugin for SimulationPlugin {
|
||||
.before(crate::perception::observer::compute_observer_snapshot),
|
||||
time::advance_tick.after(path_follow::cleanup_path_blocked),
|
||||
),
|
||||
)
|
||||
// Zone crossing detection (#512, D-077) — separate call to stay within
|
||||
// Bevy's 20-element system tuple limit.
|
||||
.add_systems(
|
||||
Update,
|
||||
zone::detect_zone_crossings.after(movement::validate_movement),
|
||||
);
|
||||
|
||||
tracing::debug!("SimulationPlugin initialized");
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! - Observer snapshot: enriches VisibleTile with zone_id
|
||||
//! - Client AudioManager: zone crossfade triggers (D-073)
|
||||
//! - Client fog shader: deep fog temperature tint (D-059 layer 3)
|
||||
//! - Zone Gate Gauntlet room: crossing-event detection (QA #512)
|
||||
//!
|
||||
//! BTreeMap per D-010 principle 4 (deterministic iteration).
|
||||
|
||||
@@ -11,6 +12,8 @@ use std::collections::BTreeMap;
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
||||
|
||||
/// Server-authoritative zone assignment for tiles.
|
||||
///
|
||||
/// Each tile position maps to a zone ID. Tiles outside any defined zone
|
||||
@@ -48,6 +51,70 @@ impl ZoneMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Event emitted when the player crosses a zone boundary (D-077).
|
||||
///
|
||||
/// Queued once per tick when the player's tile zone_id differs from the
|
||||
/// previously recorded zone. `from` or `to` may be None for tiles outside
|
||||
/// any defined zone (corridors, unzoned transitions).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ZoneCrossEvent {
|
||||
/// Zone the player left (None = no zone on previous tile).
|
||||
pub from: Option<u16>,
|
||||
/// Zone the player entered (None = no zone on new tile).
|
||||
pub to: Option<u16>,
|
||||
}
|
||||
|
||||
/// Resource: queue of zone-crossing events (drained each tick by consumers).
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ZoneCrossEventQueue {
|
||||
pub events: Vec<ZoneCrossEvent>,
|
||||
}
|
||||
|
||||
impl ZoneCrossEventQueue {
|
||||
pub fn push(&mut self, event: ZoneCrossEvent) {
|
||||
self.events.push(event);
|
||||
}
|
||||
|
||||
pub fn drain(&mut self) -> Vec<ZoneCrossEvent> {
|
||||
std::mem::take(&mut self.events)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the player's zone from the previous tick.
|
||||
///
|
||||
/// Used by `detect_zone_crossings` to compare against the current tick's
|
||||
/// zone and emit a `ZoneCrossEvent` when they differ.
|
||||
#[derive(Resource, Debug, Default)]
|
||||
pub struct PreviousPlayerZone(pub Option<u16>);
|
||||
|
||||
/// System: detect zone crossings and queue a `ZoneCrossEvent`.
|
||||
///
|
||||
/// Runs after `validate_movement` so the player position is current.
|
||||
/// Compares the player's current tile zone (from `ZoneMap`) against
|
||||
/// `PreviousPlayerZone`. Queues a `ZoneCrossEvent` on change and updates
|
||||
/// the resource for the next tick.
|
||||
pub fn detect_zone_crossings(
|
||||
zone_map: Option<Res<ZoneMap>>,
|
||||
mut previous_zone: ResMut<PreviousPlayerZone>,
|
||||
player_query: Query<&TilePosition, With<PlayerCharacter>>,
|
||||
mut queue: ResMut<ZoneCrossEventQueue>,
|
||||
) {
|
||||
let Some(zone_map) = zone_map else {
|
||||
return;
|
||||
};
|
||||
let Ok(pos) = player_query.single() else {
|
||||
return;
|
||||
};
|
||||
let current_zone = zone_map.zone_at(pos.x, pos.y, pos.z);
|
||||
if current_zone != previous_zone.0 {
|
||||
queue.push(ZoneCrossEvent {
|
||||
from: previous_zone.0,
|
||||
to: current_zone,
|
||||
});
|
||||
previous_zone.0 = current_zone;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user