feat(perception): ground vision cone in human sensory physiology

- Visible half-angle 150° → 100° (200° arc), matching average human
  binocular field. Blind spot widens from 60° to 160° — genuine
  vulnerability behind the character.
- Forward half-angle stays at 60° (120° arc) — binocular overlap zone.
- Peripheral zone represents combined sensory awareness: visual
  periphery + subconscious sound/motion tracking, not just eyes.
- Per-character VisionConeConfig allows implants and perception modes
  (D-017) to widen beyond the unaugmented baseline.
- Fog shader: smooth back-edge gradient via smoothstep blend between
  Layer 2 (peripheral) and Layer 3 (deep fog), eliminating blocky
  stair-stepped tiles at the rear of the vision cone.
- D-015 updated with physiological basis and per-character config.
- D-017 updated with vision cone modification by perception modes.
- D-020 updated with protocol versioning policy: PROTOCOL_VERSION
  gates wire format, not gameplay parameters.
- DB backup after sprint 21 close.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 22:31:29 +01:00
co-authored by Claude Opus 4.6
parent dd0de2ff04
commit a69e69e663
5 changed files with 67 additions and 39 deletions
+30 -11
View File
@@ -1,9 +1,21 @@
//! Vision cone system (D-015)
//!
//! Modulates raw shadowcast output with direction-dependent sectors:
//! - Forward: full LOS range, full detail (~120 degree arc)
//! - Peripheral: reduced range, dimmer (~90 degrees each side)
//! - Behind: blind (excluded from output)
//! Modulates raw shadowcast output with direction-dependent sectors
//! grounded in human sensory physiology:
//!
//! - Forward: 120° arc (60° half-angle) — binocular overlap zone where
//! both eyes converge. Stereoscopic depth, high acuity. Full LOS range.
//! - Peripheral: extends to 200° arc (100° half-angle) — combined sensory
//! awareness envelope. Beyond pure visual acuity (~120°), this includes
//! subconscious tracking of sound cues, movement in the corner of the
//! eye, and ambient sensory input. Reduced range, dimmer rendering.
//! The fog shader gradient (D-059) is aligned to this outer edge.
//! - Behind: 160° blind arc — no sensory awareness without explicit cues.
//! You can be snuck up on. Monologue system (D-016) bridges this gap
//! when the character's other senses fire.
//!
//! Reference: binocular field ~200° horizontal, overlap ~120°,
//! far peripheral limit ~100-105° per eye (Wikipedia, NCBI NBK220).
//!
//! Y-down convention: North = (0, -1)
@@ -22,16 +34,23 @@ impl Default for Facing {
}
}
/// Vision cone configuration per D-015
/// Vision cone configuration per D-015.
///
/// Angles grounded in human visual field physiology:
/// - forward_half_angle (60°): binocular overlap — where both eyes converge,
/// providing stereoscopic depth and high acuity.
/// - visible_half_angle (100°): average human binocular field limit — the
/// outermost boundary of peripheral vision. The fog shader's transparency
/// gradient is aligned to this edge.
pub struct VisionConeConfig {
/// Maximum vision range for forward sector (in tiles)
pub forward_range: i32,
/// Maximum vision range for peripheral sector (shorter than forward)
pub peripheral_range: i32,
/// Half-angle of forward cone in radians (~60 degrees = 120 degree arc)
/// Half-angle of forward cone in radians (60° = 120° arc, binocular overlap)
pub forward_half_angle: f32,
/// Half-angle of total visible cone in radians (~150 degrees = 300 degree arc)
/// Tiles beyond this are in the blind spot
/// Half-angle of total visible cone in radians (100° = 200° arc, peripheral limit)
/// Tiles beyond this are in the blind spot (~160° behind)
pub visible_half_angle: f32,
}
@@ -40,8 +59,8 @@ impl Default for VisionConeConfig {
Self {
forward_range: 20,
peripheral_range: 12,
forward_half_angle: std::f32::consts::FRAC_PI_3, // 60 degrees = 120 degree arc
visible_half_angle: 5.0 * std::f32::consts::FRAC_PI_6, // 150 degrees = 300 degree arc
forward_half_angle: std::f32::consts::FRAC_PI_3, // 60° = 120° arc (binocular overlap)
visible_half_angle: 100.0_f32.to_radians(), // 100° = 200° arc (peripheral limit)
}
}
}
@@ -258,7 +277,7 @@ mod tests {
assert!(!cone.is_empty());
// Tile directly south (same x, far behind) should be in blind spot
// The blind spot is the 60 degrees directly behind
// The blind spot is ~160 degrees behind (beyond 100° peripheral limit)
let has_direct_south_far = cone.iter().any(|&(x, y, _)| x == 5 && y >= 10);
assert!(
!has_direct_south_far,