Files
settled-reach/server/src/perception/query.rs
T
jpmschweitzerandClaude Opus 4.6 29c5003403 fix(simulation): add TileKind to VisibleTile wire protocol (#412)
Server was sending visible_tiles without tile type data, so the client
could not distinguish floor from wall in live mode. Add TileKind enum
(Floor/Wall/Door/Object) to VisibleTile, populated from WalkabilityMap
in the NaturalVision perception query. Update test fixtures to include
the new field. Uses #[serde(default)] for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 02:07:19 +01:00

112 lines
3.5 KiB
Rust

//! Perception query trait (D-017).
//!
//! Abstraction for perception mode geometry computation. Each mode
//! (natural vision, thermal, EM, etc.) implements PerceptionQuery to
//! provide mode-specific FOV and visibility sector computation.
//! v0.1 implements only NaturalVision.
use std::collections::{HashMap, HashSet};
use bevy_ecs::prelude::*;
use crate::bridge::types::{FacingDirection, TileKind, VisibilitySector, VisibleTile};
use crate::perception::shadowcast::compute_fov;
use crate::perception::vision_cone::{apply_vision_cone, VisionConeConfig};
use crate::simulation::movement::{TilePosition, WalkabilityMap};
/// Cached FOV geometry for the current frame. Produced by
/// compute_visibility_geometry, consumed by compute_observer_snapshot.
/// D-017 perception modes swap the geometry producer while the consumer
/// remains unchanged.
#[derive(Resource, Default)]
pub struct VisibilityGeometry {
pub visible_tiles: Vec<VisibleTile>,
pub visible_positions: HashSet<(i32, i32)>,
pub sector_lookup: HashMap<(i32, i32), VisibilitySector>,
pub observer_z: i32,
}
/// Trait for perception mode geometry computation (D-017).
///
/// Each perception mode implements this to produce a VisibilityGeometry
/// from the observer's position and facing. v0.1 only implements
/// NaturalVision; D-017 adds Thermal, EM, etc.
pub trait PerceptionQuery: Send + Sync {
fn compute_geometry(
&self,
observer_pos: &TilePosition,
facing: FacingDirection,
walkability: &WalkabilityMap,
) -> VisibilityGeometry;
}
/// Natural vision — default perception mode.
/// Uses symmetric shadowcasting (D-011) + directional vision cone (D-015).
pub struct NaturalVision;
impl PerceptionQuery for NaturalVision {
fn compute_geometry(
&self,
observer_pos: &TilePosition,
facing: FacingDirection,
walkability: &WalkabilityMap,
) -> VisibilityGeometry {
let config = VisionConeConfig::default();
let z = observer_pos.z;
let fov = compute_fov(
|x, y| !walkability.can_move_to(&TilePosition::new(x, y, z)),
observer_pos.x,
observer_pos.y,
config.forward_range,
z,
);
let cone_tiles =
apply_vision_cone(&fov, observer_pos.x, observer_pos.y, facing, &config);
let visible_tiles = cone_tiles
.iter()
.map(|&(x, y, sector)| {
let tile_kind = if walkability.can_move_to(&TilePosition::new(x, y, z)) {
TileKind::Floor
} else {
TileKind::Wall
};
VisibleTile {
x,
y,
z,
visibility: sector,
tile_kind,
}
})
.collect();
let visible_positions = cone_tiles.iter().map(|&(x, y, _)| (x, y)).collect();
let sector_lookup = cone_tiles
.iter()
.map(|&(x, y, sector)| ((x, y), sector))
.collect();
VisibilityGeometry {
visible_tiles,
visible_positions,
sector_lookup,
observer_z: z,
}
}
}
/// Resource wrapping the active perception mode (D-017).
/// Defaults to NaturalVision. Swap this resource to change perception modes.
#[derive(Resource)]
pub struct ActivePerceptionMode(pub Box<dyn PerceptionQuery>);
impl Default for ActivePerceptionMode {
fn default() -> Self {
Self(Box::new(NaturalVision))
}
}