feat(simulation): #523 add zone_id to ObserverSnapshot (D-077, protocol v11)
Server-tracked zone_id on VisibleTile for D-073 zone crossfade and D-059 deep fog temperature tint. ZoneMap resource backed by BTreeMap, observer enrichment in snapshot assembly. Backwards-compatible: v10 payloads deserialize with zone_id: None. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ pub mod sound;
|
||||
pub mod stance;
|
||||
pub mod tier;
|
||||
pub mod time;
|
||||
pub mod zone;
|
||||
|
||||
/// Core simulation plugin
|
||||
/// Manages simulation time, RNG, input processing, and tier transitions
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
//! Zone map — spatial zone assignment for tiles (D-077, D-073).
|
||||
//!
|
||||
//! Maps tile coordinates to zone identifiers. Used by:
|
||||
//! - 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)
|
||||
//!
|
||||
//! BTreeMap per D-010 principle 4 (deterministic iteration).
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
/// Server-authoritative zone assignment for tiles.
|
||||
///
|
||||
/// Each tile position maps to a zone ID. Tiles outside any defined zone
|
||||
/// (corridors, transition spaces) have no entry and return None.
|
||||
///
|
||||
/// Zone IDs are opaque u16 values — the client maintains its own
|
||||
/// `zone_id → zone_name / temperature_tint / ambient_layer` mapping.
|
||||
#[derive(Resource, Debug, Default)]
|
||||
pub struct ZoneMap {
|
||||
zones: BTreeMap<(i32, i32, i32), u16>,
|
||||
}
|
||||
|
||||
impl ZoneMap {
|
||||
/// Look up the zone for a tile position.
|
||||
pub fn zone_at(&self, x: i32, y: i32, z: i32) -> Option<u16> {
|
||||
self.zones.get(&(x, y, z)).copied()
|
||||
}
|
||||
|
||||
/// Assign a zone to a rectangular region of tiles.
|
||||
/// Used by map builders to define zone boundaries.
|
||||
pub fn set_rect(&mut self, ox: i32, oy: i32, w: i32, h: i32, z: i32, zone_id: u16) {
|
||||
for y in oy..(oy + h) {
|
||||
for x in ox..(ox + w) {
|
||||
self.zones.insert((x, y, z), zone_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign a zone to a single tile.
|
||||
pub fn set(&mut self, x: i32, y: i32, z: i32, zone_id: u16) {
|
||||
self.zones.insert((x, y, z), zone_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn zone_at_returns_none_for_unset_tile() {
|
||||
let map = ZoneMap::default();
|
||||
assert_eq!(map.zone_at(10, 20, 0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_rect_populates_zone() {
|
||||
let mut map = ZoneMap::default();
|
||||
map.set_rect(5, 10, 3, 2, 0, 42);
|
||||
|
||||
// Inside rect
|
||||
assert_eq!(map.zone_at(5, 10, 0), Some(42));
|
||||
assert_eq!(map.zone_at(7, 11, 0), Some(42));
|
||||
|
||||
// Outside rect
|
||||
assert_eq!(map.zone_at(4, 10, 0), None);
|
||||
assert_eq!(map.zone_at(8, 10, 0), None);
|
||||
assert_eq!(map.zone_at(5, 12, 0), None);
|
||||
|
||||
// Wrong z-level
|
||||
assert_eq!(map.zone_at(5, 10, 1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_single_tile() {
|
||||
let mut map = ZoneMap::default();
|
||||
map.set(3, 7, 0, 99);
|
||||
assert_eq!(map.zone_at(3, 7, 0), Some(99));
|
||||
assert_eq!(map.zone_at(3, 8, 0), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user