From 1b0513f9b3d5458d723ea83d29eae35575c2b5ea Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 Jul 2026 21:57:18 -0700 Subject: [PATCH] Render Heard as a bone noise field after the Ears tap. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first audio tap left coverage as a flat dim floor with invisible mic-only people. Both frontends now paint standing room rings, tile stipple, event rings, and presence markers in bone — never amber, never room shape. Defense: wiki/mechanics/cursor.md (Heard noise field; AC4) and wiki/interface/bevy-digital-real-canvas.md require bone presence pulses and presence markers under hearing; this implements that contract. Co-authored-by: Cursor --- src/bin/bevy.rs | 315 +++++++++++++++++++-- src/bin/terminal/agent.rs | 31 +- src/bin/terminal/ui.rs | 58 ++-- wiki/interface/bevy-digital-real-canvas.md | 5 +- wiki/interface/bevy-visual-floor.md | 5 +- wiki/interface/flat-materials.md | 14 +- wiki/interface/views.md | 4 +- wiki/log/2026-07-09-heard-noise-field.md | 37 +++ wiki/log/DEVLOG.md | 13 +- wiki/log/decisions/2026-07-09.md | 8 + wiki/mechanics/cursor.md | 18 +- 11 files changed, 439 insertions(+), 69 deletions(-) create mode 100644 wiki/log/2026-07-09-heard-noise-field.md diff --git a/src/bin/bevy.rs b/src/bin/bevy.rs index 355efc0..30f7620 100644 --- a/src/bin/bevy.rs +++ b/src/bin/bevy.rs @@ -23,7 +23,7 @@ use bevy::window::{PrimaryWindow, WindowResolution}; use misaligned::actions::{Anchor, MenuRow, menu_rows}; use misaligned::detection::{Band, SignatureKind}; use misaligned::reach::Party; -use misaligned::sim::{FactSource, Fog, LogEvent, Sim, TraceDebtStatus}; +use misaligned::sim::{FactSource, Fog, HeardKind, LogEvent, Sim, TraceDebtStatus}; use misaligned::tiles::TileType; use misaligned::work_grid::{MachineMode, TokenFamily}; @@ -316,7 +316,7 @@ impl Default for RenderMode { } /// Dev screenshot harness (env `MISALIGNED_SHOT=flat|wide|close|dark| -/// zoomin|zoomout|intel|tokens`, path via `MISALIGNED_SHOT_PATH`): stages a scenario, +/// zoomin|zoomout|intel|tokens|ears`, path via `MISALIGNED_SHOT_PATH`): stages a scenario, /// waits for /// assets, runs the fog audit, saves one screenshot, exits. Not a player /// surface; exists so render passes can be reviewed from deterministic PNGs. @@ -824,12 +824,14 @@ fn main() { blink_machines_3d, render_real_links, render_work_routes, + render_noise_field_3d, render_cursor, render_tokens, ), ( render_people, render_people_3d, + render_noise_field_flat, scroll_sidebar, render_ui, menu_pointer, @@ -879,6 +881,39 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { game.drain(); return; } + // Ears beat: audio tap only — no camera splice — so the Heard + // noise field is the only live epistemic upgrade in frame. + if kind == "ears" { + game.sim.operations_bandwidth = 500.0; + game.sim.scan_network(); + if let Some(env) = game + .sim + .reach + .device_named("environmental monitor") + .map(|d| d.id) + { + game.sim.tap_device(env); + } + for _ in 0..4000 { + let heard_person = game + .sim + .people + .people + .iter() + .any(|p| game.sim.can_hear_person(p.id) && !game.sim.can_see_person(p.id)); + if heard_person || !game.sim.heard_events.is_empty() { + break; + } + game.sim.advance(); + } + game.drain(); + mode.material = true; + mode.zoom = 0.55; + if let Some(env) = game.sim.reach.device_named("environmental monitor") { + game.set_cursor(env.x, env.y); + } + return; + } let sim = &mut game.sim; sim.operations_bandwidth = 500.0; sim.scan_network(); @@ -1846,9 +1881,10 @@ fn material_3d(tile: TileType, fog: Fog, part: TilePart, live: bool) -> Standard m.base_color = family_color(tile); } Fog::Heard => { - // Dim warm presence: sound earned a floor, nothing more. + // Dim warm floor under the bone noise-field pulses + // (render_noise_field_3d) — presence, never shape. m.unlit = true; - m.base_color = scaled(FLOOR_SERVICE, 0.17); + m.base_color = scaled(FLOOR_SERVICE, 0.12); } Fog::Remembered => { m.unlit = true; @@ -2573,6 +2609,216 @@ fn visible_wire_segment( Some(((from_x, from_y), (ex, ey))) } } +/// How long a heard capture keeps ringing on the map (ticks). +const HEARD_EVENT_RING_TICKS: u64 = 240; + +/// Bone color for the hearing noise field — never amber (machine footprint). +fn heard_noise_color(alpha: f32) -> Color { + let s = BONE.to_srgba(); + Color::srgba(s.red * 0.92, s.green * 0.88, s.blue * 0.72, alpha) +} + +/// Rooms currently under Heard fog (room-grade coverage), for room-scale rings. +fn heard_rooms(sim: &Sim) -> Vec<(i32, i32, f32)> { + let mut out = Vec::new(); + for room in &sim.map.rooms { + let (cx, cy) = room.center(); + if !matches!(sim.fog_at(cx, cy), Fog::Heard) { + continue; + } + // Approximate room radius from extents so the ring fills the floor. + let rx = room.w.max(1) as f32 * 0.45; + let ry = room.h.max(1) as f32 * 0.45; + out.push((cx, cy, rx.max(ry).max(1.2))); + } + out +} + +/// Material-mode Heard noise field (cursor.md / bevy-digital-real-canvas.md): +/// room-scale bone rings, tile stipple, expanding rings from recent +/// `HeardEvent`s, and a soft disc for mic-only people. No room shape. +fn render_noise_field_3d( + game: Res, + mode: Res, + time: Res