refactor(server): decompose observer pipeline and fix interaction boundary

Extract visibility geometry into a separate system behind a
PerceptionQuery trait, enabling D-017 perception mode swapping.
Two-stage pipeline: compute_visibility_geometry writes to
VisibilityGeometry resource, compute_observer_snapshot reads it.

Remove KnowledgeGraph from compute_nearby_interactions (simulation
phase boundary violation). Verb availability stays in simulation;
POI-based priority adjustment moves to observer via
apply_poi_verb_priority helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 23:32:41 +01:00
co-authored by Claude Opus 4.6
parent e6bd577a5a
commit 18d8253bfe
9 changed files with 380 additions and 262 deletions
+103
View File
@@ -0,0 +1,103 @@
//! 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, 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)| VisibleTile {
x,
y,
z,
visibility: sector,
})
.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))
}
}