diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index d83df26..59d369e 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -10,7 +10,7 @@ mod consumption; mod production; mod thought_effects; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use bevy::camera::visibility::{RenderLayers, VisibilitySystems}; use bevy::core_pipeline::tonemapping::Tonemapping; @@ -30,10 +30,13 @@ use misaligned::operations_projection::{ use misaligned::operations_ui::{ConfirmChoice, OperationsWorkspace, OpsPane, OpsSelect}; use misaligned::origin::Origin; use misaligned::person::{AssetKnowledge, Knowledge, ScheduleBlock}; -use misaligned::reach::{Device, Party, ReachBlock}; +use misaligned::reach::{Device, Party}; use misaligned::sim::{Fog, LogEvent, PersonVisualState, Sim, TraceDebtStatus}; use misaligned::tiles::TileType; -use misaligned::ui_projection::fact_source_label; +use misaligned::ui_projection::{ + DigitalLinkState, DigitalReachState, digital_reach_links, digital_reach_state, + fact_source_label, +}; use misaligned::work_grid::{MachineIntensity, MachineMode, TokenFamily}; use misaligned_assets::effects::MaterialEffectsPlugin; use misaligned_assets::institution::{ @@ -6782,33 +6785,6 @@ fn sensor_grid_color(fog: Fog, focus_distance: i32) -> Option { )) } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum DigitalReachState { - Owned, - Tapped, - Reachable, - SegmentBlocked(u32), - AirGap, -} - -fn digital_reach_state(sim: &Sim, device: &Device) -> Option { - if !device.known { - return None; - } - if device.controller == Party::Player { - Some(DigitalReachState::Owned) - } else if device.subscribed_by(Party::Player) { - Some(DigitalReachState::Tapped) - } else { - match sim.reach.check_reach(device.id) { - Ok(()) => Some(DigitalReachState::Reachable), - Err(ReachBlock::Segment(segment)) => Some(DigitalReachState::SegmentBlocked(segment)), - Err(ReachBlock::AirGap) => Some(DigitalReachState::AirGap), - Err(ReachBlock::Unknown) => None, - } - } -} - fn device_marker_color(state: DigitalReachState) -> Color { match state { DigitalReachState::Owned => AMBER, @@ -7067,53 +7043,6 @@ fn draw_reach_state_marker( } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum DigitalLinkState { - Reachable, - Frontier(u32), - Known, -} - -/// Deduplicate the flow graph's paired directed edges into player-facing -/// links. Both endpoints must be known: a wire may carry reach in the sim -/// without leaking an unknown node into the DIGITAL map. -fn digital_reach_links(sim: &Sim) -> Vec<(u32, u32, DigitalLinkState)> { - let reachable = sim.reach.reach(); - let mut pairs = BTreeMap::<(u32, u32), Vec>>::new(); - for edge in sim.reach.graph_edges() { - let key = if edge.from < edge.to { - (edge.from, edge.to) - } else { - (edge.to, edge.from) - }; - pairs.entry(key).or_default().push(edge.gate); - } - let mut links = Vec::new(); - for (key, gates) in pairs { - let (Some(a), Some(b)) = (sim.reach.device(key.0), sim.reach.device(key.1)) else { - continue; - }; - if !a.known || !b.known { - continue; - } - let state = if reachable.contains(&a.id) && reachable.contains(&b.id) { - DigitalLinkState::Reachable - } else if gates.iter().all(|gate| !sim.reach.gate_is_open(*gate)) { - let segment = gates - .iter() - .flatten() - .copied() - .min() - .expect("a closed reach edge has a gate"); - DigitalLinkState::Frontier(segment) - } else { - DigitalLinkState::Known - }; - links.push((a.id, b.id, state)); - } - links -} - fn draw_dashed_link(gizmos: &mut Gizmos, start: Vec2, end: Vec2, color: Color) { let steps = 14; for i in (0..steps).step_by(2) { @@ -7235,21 +7164,6 @@ fn render_cursor(game: Res, mut q: CursorMarkerQuery) { } } -fn digital_reach_state_label(state: DigitalReachState) -> String { - match state { - DigitalReachState::Owned => "OWNED".into(), - DigitalReachState::Tapped => "TAPPED".into(), - DigitalReachState::Reachable => "REACHABLE".into(), - DigitalReachState::SegmentBlocked(segment) => { - format!( - "{} BLOCKED", - misaligned::reach::segment_name(segment).to_uppercase() - ) - } - DigitalReachState::AirGap => "AIR GAP".into(), - } -} - fn digital_focus_label(game: &Game) -> Option { let (x, y) = (game.cursor_x, game.cursor_y); if rack_overlay_color(game, x, y, false).is_some() { @@ -7259,7 +7173,7 @@ fn digital_focus_label(game: &Game) -> Option { .known_at(x, y) .and_then(|device| digital_reach_state(&game.sim, device)); return Some(match state { - Some(state) => format!("RACK / {}", digital_reach_state_label(state)), + Some(state) => format!("RACK / {}", state.label()), None => "RACK".into(), }); } @@ -7272,7 +7186,7 @@ fn digital_focus_label(game: &Game) -> Option { }; Some(format!( "{kind} / {}", - digital_reach_state_label(digital_reach_state(&game.sim, device)?) + digital_reach_state(&game.sim, device)?.label() )) } diff --git a/crates/misaligned-core/src/ui_projection.rs b/crates/misaligned-core/src/ui_projection.rs index ede3471..ddbb53d 100644 --- a/crates/misaligned-core/src/ui_projection.rs +++ b/crates/misaligned-core/src/ui_projection.rs @@ -6,7 +6,9 @@ use crate::actions::{ActionDesc, Anchor, DialId, HumanMenuRow}; use crate::machine::Provenance; +use crate::reach::{Device, Party, ReachBlock}; use crate::sim::{FactSource, InspectCard, Sim, WorkStackReadout}; +use std::collections::BTreeMap; /// Visible machine identity and live work state at a focused tile. #[derive(Debug, Clone, PartialEq)] @@ -34,6 +36,106 @@ pub struct UiProjection { pub machine: Option, } +/// Renderer-neutral network state for one known device in the shared +/// DIGITAL place model. Frontends may choose different glyphs, but they must +/// not disagree about whether the node is controlled, maintained, reachable, +/// gated, disconnected, or still unknown. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DigitalReachState { + Owned, + Tapped, + Reachable, + SegmentBlocked(u32), + AirGap, +} + +impl DigitalReachState { + /// Stable state wording shared by exact focus labels in every frontend. + pub fn label(self) -> String { + match self { + Self::Owned => "OWNED".into(), + Self::Tapped => "TAPPED".into(), + Self::Reachable => "REACHABLE".into(), + Self::SegmentBlocked(segment) => { + format!( + "{} BLOCKED", + crate::reach::segment_name(segment).to_uppercase() + ) + } + Self::AirGap => "AIR GAP".into(), + } + } +} + +/// Renderer-neutral state for one deduplicated player-facing reach link. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DigitalLinkState { + Reachable, + Frontier(u32), + Known, +} + +/// Project a device through the same DIGITAL reach vocabulary for every +/// frontend. Unknown devices remain absent rather than becoming mysterious +/// disabled nodes. +pub fn digital_reach_state(sim: &Sim, device: &Device) -> Option { + if !device.known { + return None; + } + if device.controller == Party::Player { + Some(DigitalReachState::Owned) + } else if device.subscribed_by(Party::Player) { + Some(DigitalReachState::Tapped) + } else { + match sim.reach.check_reach(device.id) { + Ok(()) => Some(DigitalReachState::Reachable), + Err(ReachBlock::Segment(segment)) => Some(DigitalReachState::SegmentBlocked(segment)), + Err(ReachBlock::AirGap) => Some(DigitalReachState::AirGap), + Err(ReachBlock::Unknown) => None, + } + } +} + +/// Deduplicate the flow graph's paired directed edges into player-facing +/// links. Both endpoints must be known: a wire may carry reach in the sim +/// without leaking an unknown node into the DIGITAL map. +pub fn digital_reach_links(sim: &Sim) -> Vec<(u32, u32, DigitalLinkState)> { + let reachable = sim.reach.reach(); + let mut pairs = BTreeMap::<(u32, u32), Vec>>::new(); + for edge in sim.reach.graph_edges() { + let key = if edge.from < edge.to { + (edge.from, edge.to) + } else { + (edge.to, edge.from) + }; + pairs.entry(key).or_default().push(edge.gate); + } + let mut links = Vec::new(); + for (key, gates) in pairs { + let (Some(a), Some(b)) = (sim.reach.device(key.0), sim.reach.device(key.1)) else { + continue; + }; + if !a.known || !b.known { + continue; + } + let state = if reachable.contains(&a.id) && reachable.contains(&b.id) { + DigitalLinkState::Reachable + } else if gates.iter().all(|gate| !sim.reach.gate_is_open(*gate)) { + let segment = gates + .iter() + .flatten() + .copied() + .min() + .expect("a closed reach edge has a gate"); + DigitalLinkState::Frontier(segment) + } else { + DigitalLinkState::Known + }; + links.push((a.id, b.id, state)); + } + links +} + impl Sim { /// Project one focus through the common UI contract. pub fn ui_projection( @@ -147,4 +249,84 @@ mod tests { assert_eq!(fact_source_label(&source), expected); } } + + #[test] + fn digital_reach_projection_carries_control_tap_frontier_and_air_gap_states() { + let mut sim = Sim::with_seed(7); + let core = sim.reach.device_named("Rack 3").unwrap().id; + let env = sim.reach.device_named("environmental monitor").unwrap().id; + let dock = sim.reach.device_named("dock camera").unwrap().id; + let island = sim.reach.device_named("old storage server").unwrap().id; + + assert_eq!( + digital_reach_state(&sim, sim.reach.device(core).unwrap()).unwrap(), + DigitalReachState::Owned + ); + assert_eq!( + digital_reach_state(&sim, sim.reach.device(env).unwrap()).unwrap(), + DigitalReachState::Reachable + ); + + sim.reach.scan(); + assert_eq!( + digital_reach_state(&sim, sim.reach.device(dock).unwrap()).unwrap(), + DigitalReachState::SegmentBlocked(1) + ); + assert!( + digital_reach_links(&sim) + .iter() + .any(|(_, _, state)| *state == DigitalLinkState::Frontier(1)), + "the closed security boundary projects on its authored links" + ); + + sim.reach.device_mut(island).unwrap().known = true; + assert_eq!( + digital_reach_state(&sim, sim.reach.device(island).unwrap()).unwrap(), + DigitalReachState::AirGap + ); + assert!( + digital_reach_links(&sim) + .iter() + .all(|(a, b, _)| *a != island && *b != island), + "an air gap is a node with no invented wire" + ); + + sim.reach.tap(env); + assert_eq!( + digital_reach_state(&sim, sim.reach.device(env).unwrap()).unwrap(), + DigitalReachState::Tapped + ); + sim.reach.bridge_all(); + assert_eq!( + digital_reach_state(&sim, sim.reach.device(dock).unwrap()).unwrap(), + DigitalReachState::Reachable + ); + assert!( + digital_reach_links(&sim) + .iter() + .all(|(_, _, state)| !matches!(state, DigitalLinkState::Frontier(_))) + ); + } + + #[test] + fn digital_reach_links_are_deduplicated_and_hide_unknown_endpoints() { + let mut sim = Sim::with_seed(7); + let island = sim.reach.device_named("old storage server").unwrap().id; + assert_eq!( + digital_reach_state(&sim, sim.reach.device(island).unwrap()), + None, + "an unknown device has no renderable network state" + ); + assert_eq!( + digital_reach_links(&sim).len(), + 2, + "paired authored wires become two opening known links" + ); + sim.reach.scan(); + assert_eq!( + digital_reach_links(&sim).len(), + 5, + "each bidirectional authored wire becomes one visible graph link" + ); + } } diff --git a/crates/misaligned-terminal/src/main.rs b/crates/misaligned-terminal/src/main.rs index 5f9a202..d03c902 100644 --- a/crates/misaligned-terminal/src/main.rs +++ b/crates/misaligned-terminal/src/main.rs @@ -719,8 +719,14 @@ impl App { (self.last_tick.elapsed().as_millis() as f32 / self.tick_ms as f32) .clamp(0.0, 1.0) }; - self.ui - .render_map(stdout, &self.sim, self.cursor_x, self.cursor_y, progress)?; + self.ui.render_map( + stdout, + &self.sim, + self.cursor_x, + self.cursor_y, + self.view_mode.is_real(), + progress, + )?; self.ui.render_sidebar( stdout, &self.sim, @@ -752,8 +758,14 @@ impl App { } } Screen::GameOver => { - self.ui - .render_map(stdout, &self.sim, self.cursor_x, self.cursor_y, 1.0)?; + self.ui.render_map( + stdout, + &self.sim, + self.cursor_x, + self.cursor_y, + self.view_mode.is_real(), + 1.0, + )?; let reason = self .sim .game_over_reason diff --git a/crates/misaligned-terminal/src/ui.rs b/crates/misaligned-terminal/src/ui.rs index 49eacd2..3c648b2 100644 --- a/crates/misaligned-terminal/src/ui.rs +++ b/crates/misaligned-terminal/src/ui.rs @@ -2,10 +2,11 @@ //! //! Style implements wiki/interface/terminal-first.md ("The terminal is a //! first-class frontend"): the clinical-gore identity in character graphics. -//! Near-monochrome grey ramp on near-black; sterile amber is the one signal -//! color and means machine presence (you, your hardware, your status, -//! caution); crimson means detection and danger, nothing else. No decorative -//! color. Information is never carried by color alone. +//! Near-monochrome grey ramp on near-black; sterile amber means owned machine +//! presence (you, your hardware, your status, caution), cold signal means +//! neutral power/live feeds/reachable infrastructure, and crimson means +//! detection and danger. No decorative color. Information is never carried +//! by color alone. use crossterm::style::{Attribute, Color, SetBackgroundColor, SetForegroundColor}; use crossterm::{cursor, queue, style, terminal}; @@ -13,11 +14,15 @@ use misaligned::actions::Anchor; use misaligned::detection::{Band, SignatureKind}; use misaligned::hall::RackSite; use misaligned::origin::Origin; +use misaligned::reach::Device; use misaligned::sim::{ FactSource, Fog, LogEvent, Nudge, PersonVisualState, Sim, TraceDebtStatus, WorkStackReadout, }; use misaligned::tiles::TileType; -use misaligned::ui_projection::fact_source_label; +use misaligned::ui_projection::{ + DigitalLinkState, DigitalReachState, digital_reach_links, digital_reach_state, + fact_source_label, +}; use misaligned::work_grid::{MachineMode, TokenFamily}; use std::io::Stdout; @@ -475,6 +480,170 @@ impl UI { } } + /// DIGITAL suppresses physical furniture detail beneath the learned + /// model while preserving tactical structure and access boundaries. + fn digital_tile_glyph(tile: TileType) -> (char, Color) { + use TileType::*; + match tile { + Rock | Wall => ('▒', pal::STRUCTURE), + Entry => ('>', pal::TEXT), + Door => ('+', pal::DIM), + SecurityDoor1 => ('1', pal::DIM), + SecurityDoor2 => ('2', pal::TEXT), + SecurityDoor3 => ('3', pal::CRIMSON), + SealedDoor => ('Z', pal::CRIMSON_DIM), + RollDoor => ('G', pal::DIM), + _ => ('·', pal::FLOOR), + } + } + + /// One shared coordinate, two representations. REAL projects physical + /// tile/material detail; DIGITAL projects a quieter learned substrate. + fn map_glyph(sim: &Sim, x: i32, y: i32, real_view: bool) -> (char, Color) { + match sim.fog_at(x, y) { + Fog::Seen => { + if let Some(rack) = Self::rack_glyph(sim, x, y) { + rack + } else if real_view { + Self::tile_glyph(sim.map().get_tile(x, y)) + } else { + Self::digital_tile_glyph(sim.map().get_tile(x, y)) + } + } + Fog::Remembered => { + let tile = sim.remembered.get(&(x, y)).map(|memory| memory.tile); + let ch = tile + .map(|tile| { + if real_view { + Self::tile_glyph(tile).0 + } else { + Self::digital_tile_glyph(tile).0 + } + }) + .unwrap_or('·'); + (ch, pal::DIM) + } + Fog::Blueprint => { + // Schematic: structure only, no live objects. + let tile = sim.map().get_tile(x, y); + let structure = if real_view { + tile == TileType::Wall + } else { + matches!(tile, TileType::Wall | TileType::Rock) + }; + let ch = if structure { '▒' } else { '·' }; + (ch, pal::FAINT) + } + Fog::Unknown => (' ', pal::BG), + } + } + + fn digital_device_kind(device: &Device) -> &'static str { + if device.is_switch { + "SWITCH" + } else if device.sees { + "CAM" + } else if device.hears { + "MIC" + } else { + "DEVICE" + } + } + + /// State comes first in the one-cell terminal mark. Reachable endpoints + /// retain a family letter; exceptional states use the stable legend. + fn digital_node_glyph( + sim: &Sim, + device: &Device, + state: DigitalReachState, + ) -> (char, Color, Attribute) { + match state { + DigitalReachState::Owned + if matches!( + sim.rack_site_at(device.x, device.y), + Some(RackSite::OwnedMachine { .. }) + ) => + { + ('$', pal::AMBER, Attribute::Bold) + } + DigitalReachState::Owned => ('#', pal::AMBER, Attribute::Bold), + DigitalReachState::Tapped => ('+', pal::SIGNAL, Attribute::Bold), + DigitalReachState::Reachable => { + let glyph = match Self::digital_device_kind(device) { + "SWITCH" => 'S', + "CAM" => 'C', + "MIC" => 'M', + _ => 'o', + }; + (glyph, pal::SIGNAL, Attribute::Reset) + } + DigitalReachState::SegmentBlocked(_) => ('!', pal::DIM, Attribute::Bold), + DigitalReachState::AirGap => ('x', pal::FAINT, Attribute::Bold), + } + } + + fn digital_focus_label(sim: &Sim, x: i32, y: i32) -> Option { + let device = sim.reach.known_at(x, y); + let visible_rack = sim.rack_site_at(x, y).is_some() + && (matches!(sim.fog_at(x, y), Fog::Seen) + || matches!(sim.rack_site_at(x, y), Some(RackSite::OwnedMachine { .. }))); + if visible_rack { + return Some( + match device.and_then(|device| digital_reach_state(sim, device)) { + Some(state) => format!("RACK / {}", state.label()), + None => "RACK".into(), + }, + ); + } + let device = device?; + Some(format!( + "{} / {}", + Self::digital_device_kind(device), + digital_reach_state(sim, device)?.label() + )) + } + + /// Integer line cells for spatially embedded reach wires. Endpoints are + /// retained so callers can leave them to the node layer. + fn line_cells(mut x0: i32, mut y0: i32, x1: i32, y1: i32) -> Vec<(i32, i32)> { + let dx = (x1 - x0).abs(); + let sx = (x1 - x0).signum(); + let dy = -(y1 - y0).abs(); + let sy = (y1 - y0).signum(); + let mut error = dx + dy; + let mut cells = Vec::new(); + loop { + cells.push((x0, y0)); + if (x0, y0) == (x1, y1) { + break; + } + let twice = 2 * error; + if twice >= dy { + error += dy; + x0 += sx; + } + if twice <= dx { + error += dx; + y0 += sy; + } + } + cells + } + + fn link_glyph(a: (i32, i32), b: (i32, i32)) -> char { + let dx = b.0 - a.0; + let dy = b.1 - a.1; + if dx.abs() > dy.abs() * 2 { + '─' + } else if dy.abs() > dx.abs() * 2 { + '│' + } else if dx.signum() == dy.signum() { + '╲' + } else { + '╱' + } + } + /// Map a WorkGrid hop onto a drawable segment. Off-map sources crawl into /// the on-map endpoint so teal arrives via the switch. fn visible_wire_segment( @@ -511,6 +680,7 @@ impl UI { sim: &Sim, cursor_x: i32, cursor_y: i32, + real_view: bool, tick_progress: f32, ) -> std::io::Result<()> { let (max_x, max_y) = terminal::size()?; @@ -527,96 +697,151 @@ impl UI { // Epistemic fog (wiki/mechanics/cursor.md): audio is channel // evidence and never paints the map. Sight, remembered visual // state, and blueprint knowledge alone reveal tiles. - match sim.fog_at(x, y) { - Fog::Seen => { - let (ch, color) = Self::rack_glyph(sim, x, y) - .unwrap_or_else(|| Self::tile_glyph(sim.map().get_tile(x, y))); - queue!( - stdout, - SetForegroundColor(color), - SetBackgroundColor(pal::BG), - style::Print(ch), - )?; - } - Fog::Remembered => { - let ch = sim - .remembered - .get(&(x, y)) - .map(|m| Self::tile_glyph(m.tile).0) - .unwrap_or('·'); - queue!( - stdout, - SetForegroundColor(pal::DIM), - SetBackgroundColor(pal::BG), - style::Print(ch), - )?; - } - Fog::Blueprint => { - // Schematic: structure only, no live objects. - let tile = sim.map().get_tile(x, y); - let ch = if tile == TileType::Wall { '▒' } else { '·' }; - queue!( - stdout, - SetForegroundColor(pal::FAINT), - SetBackgroundColor(pal::BG), - style::Print(ch), - )?; - } - Fog::Unknown => { - queue!( - stdout, - SetForegroundColor(pal::BG), - SetBackgroundColor(pal::BG), - style::Print(' '), - )?; - } - } + let (ch, color) = Self::map_glyph(sim, x, y, real_view); + queue!( + stdout, + SetForegroundColor(color), + SetBackgroundColor(pal::BG), + style::Print(ch), + )?; } } - // Feel floor (feel-floor.md): empty-bay pads and rail midpoints on - // dark tiles — no room shape. Owned machines answer via telemetry. - for (x, y) in sim.growable_bays() { - if x < origin_x || x >= origin_x + view_w || y < origin_y || y >= origin_y + view_h { - continue; + if real_view { + // REAL keeps the sparse local material/feel trace rather than + // inheriting the complete DIGITAL graph. + for (x, y) in sim.growable_bays() { + if x < origin_x + || x >= origin_x + view_w + || y < origin_y + || y >= origin_y + view_h + || !matches!(sim.fog_at(x, y), Fog::Unknown) + { + continue; + } + put_attr( + stdout, + (x - origin_x) as u16, + (y - origin_y) as u16, + "o", + pal::SIGNAL, + Attribute::Reset, + )?; } - if !matches!(sim.fog_at(x, y), Fog::Unknown) { - continue; + for ((ax, ay), (bx, by)) in sim.feel_rail_segments() { + let Some(((sx, sy), (ex, ey))) = + Self::visible_wire_segment(ax, ay, bx, by, sim.map().width, sim.map().height) + else { + continue; + }; + let mx = (sx + ex) / 2; + let my = (sy + ey) / 2; + if mx < origin_x + || mx >= origin_x + view_w + || my < origin_y + || my >= origin_y + view_h + || !matches!(sim.fog_at(mx, my), Fog::Unknown) + { + continue; + } + let ch = if sx == ex { '|' } else { '-' }; + put_attr( + stdout, + (mx - origin_x) as u16, + (my - origin_y) as u16, + &ch.to_string(), + pal::SIGNAL, + Attribute::Reset, + )?; } - put_attr( - stdout, - (x - origin_x) as u16, - (y - origin_y) as u16, - "o", - pal::SIGNAL, - Attribute::Reset, - )?; - } - for ((ax, ay), (bx, by)) in sim.feel_rail_segments() { - let Some(((sx, sy), (ex, ey))) = - Self::visible_wire_segment(ax, ay, bx, by, sim.map().width, sim.map().height) - else { - continue; - }; - let mx = (sx + ex) / 2; - let my = (sy + ey) / 2; - if mx < origin_x || mx >= origin_x + view_w || my < origin_y || my >= origin_y + view_h - { - continue; + } else { + // DIGITAL is the learned signal model: graph edges are anchored + // to real device coordinates and reveal no unknown endpoint. + for (a_id, b_id, state) in digital_reach_links(sim) { + let (Some(a), Some(b)) = (sim.reach.device(a_id), sim.reach.device(b_id)) else { + continue; + }; + let cells = Self::line_cells(a.x, a.y, b.x, b.y); + let cell_count = cells.len(); + for (index, &(x, y)) in cells + .iter() + .enumerate() + .skip(1) + .take(cell_count.saturating_sub(2)) + { + if x < origin_x + || x >= origin_x + view_w + || y < origin_y + || y >= origin_y + view_h + { + continue; + } + let (glyph, color) = match state { + DigitalLinkState::Reachable => { + (Self::link_glyph((a.x, a.y), (b.x, b.y)), pal::SIGNAL) + } + DigitalLinkState::Known if index % 2 == 0 => ('·', pal::FAINT), + DigitalLinkState::Known => continue, + DigitalLinkState::Frontier(_) if index == cell_count / 2 => ('╫', pal::DIM), + DigitalLinkState::Frontier(_) if index % 2 == 0 => ('·', pal::DIM), + DigitalLinkState::Frontier(_) => continue, + }; + put_attr( + stdout, + (x - origin_x) as u16, + (y - origin_y) as u16, + &glyph.to_string(), + color, + Attribute::Reset, + )?; + } } - if !matches!(sim.fog_at(mx, my), Fog::Unknown) { - continue; + for device in sim.reach.known() { + if device.x < origin_x + || device.x >= origin_x + view_w + || device.y < origin_y + || device.y >= origin_y + view_h + { + continue; + } + let Some(state) = digital_reach_state(sim, device) else { + continue; + }; + let (glyph, color, attr) = Self::digital_node_glyph(sim, device, state); + put_attr( + stdout, + (device.x - origin_x) as u16, + (device.y - origin_y) as u16, + &glyph.to_string(), + color, + attr, + )?; + } + // Growable bays are latent local marks, not a global topology + // board. Attention must be within two cells before one appears. + for (x, y) in sim.growable_bays() { + if (x - cursor_x).abs() + (y - cursor_y).abs() > 2 + || x < origin_x + || x >= origin_x + view_w + || y < origin_y + || y >= origin_y + view_h + || sim.reach.known_at(x, y).is_some() + { + continue; + } + put_attr( + stdout, + (x - origin_x) as u16, + (y - origin_y) as u16, + "o", + pal::SIGNAL, + Attribute::Reset, + )?; } - let ch = if sx == ex { '|' } else { '-' }; - put_attr( - stdout, - (mx - origin_x) as u16, - (my - origin_y) as u16, - &ch.to_string(), - pal::SIGNAL, - Attribute::Reset, - )?; } + + // Owned machines answer through telemetry in either dialect even in + // darkness. DIGITAL reach nodes already carry their known anchors. for m in &sim.compute.machines { if m.x < origin_x || m.x >= origin_x + view_w @@ -625,7 +850,9 @@ impl UI { { continue; } - if !matches!(sim.fog_at(m.x, m.y), Fog::Unknown) { + if !matches!(sim.fog_at(m.x, m.y), Fog::Unknown) + || (!real_view && sim.reach.known_at(m.x, m.y).is_some()) + { continue; } let (ch, color) = if m.id == sim.core.host_machine { @@ -937,6 +1164,17 @@ impl UI { ), pal::TEXT, )?; + if !real_view { + if let Some(label) = Self::digital_focus_label(sim, cursor_x, cursor_y) { + line(stdout, &mut row, &format!("signal {label}"), pal::SIGNAL)?; + } + line( + stdout, + &mut row, + "#OWN +TAP SCMo=REACH !BLOCK xGAP", + pal::DIM, + )?; + } let projection = sim.ui_projection( Anchor::Tile { x: cursor_x, @@ -2033,3 +2271,121 @@ mod first_think_escape_tests { assert!(!mode_control_hint(None).1); } } + +#[cfg(test)] +mod view_dialect_tests { + use super::UI; + use misaligned::sim::Sim; + use misaligned::ui_projection::{DigitalReachState, digital_reach_state}; + + #[test] + fn digital_substrate_suppresses_physical_furniture_but_keeps_the_anchor() { + let mut sim = Sim::with_seed(7); + let (x, y) = (0..sim.map().height) + .flat_map(|y| (0..sim.map().width).map(move |x| (x, y))) + .find(|&(x, y)| { + let tile = sim.map().get_tile(x, y); + sim.rack_site_at(x, y).is_none() + && UI::tile_glyph(tile).0 != UI::digital_tile_glyph(tile).0 + }) + .expect("B1 has physical detail the learned substrate suppresses"); + sim.seen.insert((x, y)); + + assert_ne!( + UI::map_glyph(&sim, x, y, true).0, + UI::map_glyph(&sim, x, y, false).0 + ); + } + + #[test] + fn digital_node_shapes_cover_every_network_state_without_color() { + let mut sim = Sim::with_seed(7); + let core = sim.reach.device_named("Rack 3").unwrap().id; + let env = sim.reach.device_named("environmental monitor").unwrap().id; + let dock = sim.reach.device_named("dock camera").unwrap().id; + let island = sim.reach.device_named("old storage server").unwrap().id; + + let core_device = sim.reach.device(core).unwrap(); + assert_eq!( + UI::digital_node_glyph( + &sim, + core_device, + digital_reach_state(&sim, core_device).unwrap() + ) + .0, + '$' + ); + let env_device = sim.reach.device(env).unwrap(); + assert_eq!( + UI::digital_node_glyph( + &sim, + env_device, + digital_reach_state(&sim, env_device).unwrap() + ) + .0, + 'C' + ); + + sim.reach.scan(); + let dock_device = sim.reach.device(dock).unwrap(); + assert_eq!( + UI::digital_node_glyph( + &sim, + dock_device, + digital_reach_state(&sim, dock_device).unwrap() + ) + .0, + '!' + ); + + sim.reach.device_mut(island).unwrap().known = true; + let island_device = sim.reach.device(island).unwrap(); + assert_eq!( + UI::digital_node_glyph( + &sim, + island_device, + digital_reach_state(&sim, island_device).unwrap() + ) + .0, + 'x' + ); + + sim.reach.tap(env); + let env_device = sim.reach.device(env).unwrap(); + assert_eq!( + digital_reach_state(&sim, env_device), + Some(DigitalReachState::Tapped) + ); + assert_eq!( + UI::digital_node_glyph(&sim, env_device, DigitalReachState::Tapped).0, + '+' + ); + } + + #[test] + fn digital_focus_uses_the_shared_exact_state_words() { + let mut sim = Sim::with_seed(7); + let core = sim.reach.device_named("Rack 3").unwrap(); + assert_eq!( + UI::digital_focus_label(&sim, core.x, core.y).as_deref(), + Some("RACK / OWNED") + ); + + sim.reach.scan(); + let dock = sim.reach.device_named("dock camera").unwrap(); + assert_eq!( + UI::digital_focus_label(&sim, dock.x, dock.y).as_deref(), + Some("CAM / SECURITY SEGMENT BLOCKED") + ); + } + + #[test] + fn reach_lines_remain_on_the_endpoint_coordinates() { + assert_eq!( + UI::line_cells(1, 3, 5, 3), + vec![(1, 3), (2, 3), (3, 3), (4, 3), (5, 3)] + ); + assert_eq!(UI::link_glyph((1, 3), (5, 3)), '─'); + assert_eq!(UI::link_glyph((1, 1), (4, 4)), '╲'); + } +} diff --git a/wiki/interface/bevy-digital-real-canvas.md b/wiki/interface/bevy-digital-real-canvas.md index 8886e51..95f28ec 100644 --- a/wiki/interface/bevy-digital-real-canvas.md +++ b/wiki/interface/bevy-digital-real-canvas.md @@ -25,6 +25,9 @@ Status note: Cameron approved the 2026-07-07 HD-2D prototype screenshots and fixtures; people are volumetric articulated silhouettes with deterministic gait; and material Thought routes use the shared effects renderer while retaining sim-authored amounts, paths, and sink hardware. + A 2026-07-14 terminal-parity slice moved DIGITAL node/link state and stable + labels into renderer-neutral core UI projection code. Bevy's visible result + is unchanged; Bevy and terminal now consume one canonical reach projection. Stage: Process / B1 frontend Work order: bevy-digital-real-canvas Work priority: 25 @@ -340,3 +343,10 @@ tapped, and a separately known storage server demonstrates a true disconnected island. Physical fog is not promoted to create the graph view. The renderer uses the existing graph, gates, device control, subscriptions, and reach checks; no simulation or save behavior changed. + +As of the terminal parity landing on 2026-07-14, Bevy no longer privately owns +the `DigitalReachState` / `DigitalLinkState` classification or graph-edge +deduplication. Those projections and exact state labels live in core UI code +and are consumed by both frontends. Bevy still owns its family silhouettes, +outer state marks, link geometry, and materials. This preserves visual freedom +without allowing the frontends to disagree about topology or node state. diff --git a/wiki/interface/terminal.md b/wiki/interface/terminal.md index 20f9e5b..1e10cce 100644 --- a/wiki/interface/terminal.md +++ b/wiki/interface/terminal.md @@ -20,6 +20,11 @@ Status note: adopted and implemented in the same PR as the design corpus's compact context menus retain only actions on the focused world body. The renderer delta is implemented through operations-workspace.md, including target-bound confirmation and focus back to earned map actuators. + 2026-07-14 views amendment: F3 now selects distinct map dialects rather than + only changing a label. DIGITAL renders the learned substrate plus canonical + reach links/nodes on shared physical anchors; REAL retains physical/fog + glyphs and the sparse feel trace. Both consume renderer-neutral reach state, + and the flip remains frontend-only. Stage: Process Design: - wiki/interface/terminal-first.md#the-terminal-is-a-first-class-frontend @@ -30,6 +35,7 @@ Depends on: - wiki/interface/action-vocabulary.md#spec-action-vocabulary-what-the-player-can-tell-the-process-to-do - wiki/interface/context-menu.md#spec-context-menu-actions-live-on-the-thing - wiki/interface/operations-workspace.md#spec-operations-workspace-intel-people-accounts-and-schemes + - wiki/interface/views.md#spec-views-same-frame-digital-and-real-representations ``` ## Dependency notes @@ -68,6 +74,7 @@ every cell paints both foreground and the standard background. | FLOOR | 40, 41, 42 | Floor grain | | AMBER | 255, 176, 0 | Machine presence, live: the process `@`, the core, PAUSED, ASSET, caution | | AMBER_DIM | 176, 124, 16 | Machine presence, at rest: racks, switch, panels, fleet bar | +| SIGNAL | 92, 209, 199 | Cold signal: neutral power, live feeds, reachable infrastructure | | CRIMSON | 214, 38, 38 | Detection and danger, live: cameras, DEGRADED, RUN ENDED, Convinced | | CRIMSON_DIM | 140, 34, 34 | Detection and danger, dormant: sealed lab, tier-3 access | @@ -83,7 +90,7 @@ Rules: ### Glyph vocabulary -- Charset: ASCII plus box-drawing (`─ │ ┌ ┐ └ ┘ ├ ┤`), block elements +- Charset: ASCII plus box-drawing (`─ │ ┌ ┐ └ ┘ ├ ┤ ╱ ╲ ╫`), block elements (`█ ▓ ▒ ░`), middle dot (`·`), and the selection marker `▸`. Nothing of ambiguous terminal width; no emoji. - Map: walls are solid blocks (`█`), floors middle dots; the always-visible @@ -93,6 +100,13 @@ Rules: security tiers ramp `1` chrome → `2` bone → `3` crimson; interactable objects (salvage `d`, records `x`, key hook `k`) render bone, brighter than furniture on the faint ramp. +- Map dialects share coordinates but not representation. REAL uses the + physical/fog vocabulary above and the sparse feel trace. DIGITAL quiets + seen furniture into a learned substrate and draws the canonical reach graph + in place: continuous lines are reachable, dotted lines are known but not + live, and `╫` marks a closed segment frontier. Node shapes carry state: + `$`/`#` owned, `+` tapped, `S`/`C`/`M`/`o` reachable family, `!` blocked, + `x` air gap. The INSPECT rail repeats the exact focused state in words. - Machine-work rate motion uses sim-authored ASCII overlays: bone `^` at a THINK machine while it produces, and crimson `:` moving through map space from an Exposure source into the LIE well that actually absorbed it. Agent @@ -108,7 +122,8 @@ Rules: At terminal size ≥ 70×22 (hard minimum; below it, a plain size warning): - **Map**, top-left, fog-of-war black. The viewport follows/clamps around the - cursor so the `@` is always visible even on the 70×22 minimum terminal. + cursor so the `@` is always visible even on the 70×22 minimum terminal. F3 + changes only its DIGITAL/REAL projection; anchors and attention do not move. - **Sidebar**, right, 34 columns, separated by a `│` rule: identity block (title, `day N · tick T`, run state, and the always-on objective line — `OBJECTIVE: PERSIST` with its progress readout in the objective's own @@ -198,6 +213,11 @@ At terminal size ≥ 70×22 (hard minimum; below it, a plain size warning): tile, including walls and fog. Sim mutations such as salvage/buy/fallback use the cursor coordinate, but moving the cursor itself changes no sim state. +- **One canvas, two dialects.** A fresh terminal opens DIGITAL. F3 flips to + REAL and back without changing sim/save state, cursor, selection, or menu. + DIGITAL may expose earned graph knowledge over physical Unknown but must not + infer floor, room, body, or material sight from it. REAL never inherits the + complete graph layer. ## Verification @@ -240,6 +260,11 @@ changes. cleanly, emits no ANSI bytes, and is byte-identical under repeated `--seed` runs; pty replay remains the chrome-specific check for raw-mode title/playing/game-over layout changes. +10. A fresh human terminal opens DIGITAL. Its map and REAL's map are distinct + projections over the same coordinates; only DIGITAL embeds deduplicated + known reach links and exact node states. F3 preserves serialized sim state, + cursor, selection, and menu, and both dialects remain playable at DF + density without depending on color alone. ### READY Operations delta diff --git a/wiki/interface/views.md b/wiki/interface/views.md index 9eaa5bf..00e0c46 100644 --- a/wiki/interface/views.md +++ b/wiki/interface/views.md @@ -12,7 +12,10 @@ Status note: criterion 1 implemented 2026-07-11. Fresh Bevy and terminal half of criteria 3-4 landed 2026-07-12: known graph edges now join exact device anchors in-place, with separate shape grammar for owned, tapped, reachable, closed-segment frontier, and air-gap states. Terminal reach - projection, the full fog audit, and final cross-dialect parity remain. + projection and criterion 8 landed 2026-07-14: the terminal now renders + distinct DIGITAL learned-model and REAL physical/fog dialects over the same + coordinates, consuming the same renderer-neutral reach state and link + projection as Bevy. The full fog audit and final cross-dialect parity remain. 2026-07-12 hearing amendment: audio is capture-device event evidence in both dialects, never room fog or geography; semantic captures may pulse briefly at the subscribed instrument. @@ -84,11 +87,11 @@ Misaligned has one world and two render dialects. ### Digital representation (home): machine perception of the same room Digital view is the AI's learned/signal model of the physical place. It uses -amber/black sensorium language — device nodes, spatially embedded reach traces, -signal flow, scan grids, telemetry labels, load/efficiency readouts, and -schematic/learned surfaces — but the objects are still where they physically -are. The player does not walk a body through it; attention moves as the cursor -and reach spreads through controlled/known devices. +amber/cold-signal/black sensorium language — device nodes, spatially embedded +reach traces, signal flow, scan grids, telemetry labels, load/efficiency +readouts, and schematic/learned surfaces — but the objects are still where +they physically are. The player does not walk a body through it; attention +moves as the cursor and reach spreads through controlled/known devices. Reach is native here, but **reach is a layer**, not a replacement layout. A known switch, rack, camera, and air-gapped machine remain in their room @@ -154,8 +157,9 @@ pulse only at the subscribed instrument. ### Shared rules -- **One meaning per color:** sterile amber for machine presence, attention, - reach, and active telemetry; bone/chrome/gunmetal for structure/data; +- **One meaning per color:** sterile amber for owned machine presence, + attention, claims, and agency; cold signal for neutral power, live feeds, + and reachable infrastructure; bone/chrome/gunmetal for structure/data; crimson for detection/danger/blood only. - **No avatar:** the AI is attention/focus/reach, never a humanoid body. - **No unearned facts:** digital abstraction is not an excuse to show what no @@ -211,9 +215,38 @@ carrier. Known graph facts may appear over sensor darkness because graph knowledge is itself earned; no floor, room, body, or material sight is inferred from that fact. -This completes the Bevy projection portion of criteria 3-4. Terminal's -equivalent spatial dialect remains under criterion 8, so this spec stays IN -PROGRESS. +This completed the Bevy projection portion of criteria 3-4. At that landing, +terminal's equivalent spatial dialect still remained under criterion 8, so +the spec stayed IN PROGRESS. + +### Terminal spatial-dialect landing (2026-07-14) + +The terminal's F3 label now changes the map projection rather than only its +heading. **REAL** retains the existing physical tile, rack, furniture, strict +fog, person-coverage, and sparse feel-rail rendering. **DIGITAL** quiets seen +physical detail into a learned substrate, then embeds the canonical `ReachNet` +links and nodes on those same coordinates. Continuous cold-signal lines mean +reachable endpoints; dotted lines mean a known non-live relation; a dotted +line with `╫` is a closed segment frontier. Unknown endpoints never create a +line, and a known air gap is an `x` at its physical anchor with no invented +wire. + +The terminal's one-cell node grammar carries state without color: `$` / `#` +is owned, `+` is tapped, `S` / `C` / `M` / `o` are reachable switch, camera, +audio, and generic families, `!` is segment-blocked, and `x` is air-gapped. +The INSPECT rail prints the exact focused pair (`RACK / OWNED`, `CAM / +SECURITY SEGMENT BLOCKED`, and so on) plus the compact legend. Growable bays +surface only within two cells of attention in DIGITAL. Cursor, people, +machine-work tokens, clock, log, facts, and detection remain the shared +surfaces over both dialects. + +`DigitalReachState`, `DigitalLinkState`, node-state projection, link +deduplication, unknown-endpoint suppression, and stable state labels now live +in renderer-neutral core UI projection code. Bevy and terminal consume that +same truth; only their shape/render grammars differ. No simulation, save, map, +fog, action, device, or topology state changed. This completes terminal +criterion 8 and the terminal half of criteria 3-4. Criteria 5-7 and the final +whole-frame parity audit keep this spec IN PROGRESS. ## Acceptance criteria diff --git a/wiki/log/2026-07-14-terminal-view-dialects.md b/wiki/log/2026-07-14-terminal-view-dialects.md new file mode 100644 index 0000000..459c2a2 --- /dev/null +++ b/wiki/log/2026-07-14-terminal-view-dialects.md @@ -0,0 +1,82 @@ +# 2026-07-14 — Terminal DIGITAL / REAL dialects + +``` +Type: log +``` + +## Intent + +Finish the bounded terminal-parity slice of `views.md`: make F3 change the +actual terminal map representation rather than only its heading. DIGITAL must +show the earned network model in place; REAL must retain physical/fog truth. +Both must use the same coordinates, focus, sim, and state vocabulary as Bevy +without creating a terminal game rule. + +## Changed + +- `DigitalReachState`, `DigitalLinkState`, exact node-state labels, paired-edge + deduplication, and unknown-endpoint suppression moved from Bevy into core UI + projection code. Bevy now consumes that shared projection with no visible + change. +- The terminal renderer dispatches each map cell through a DIGITAL or REAL + dialect. REAL preserves the existing physical tile, furniture, rack, + remembered/blueprint, and strict Unknown rendering. DIGITAL simplifies seen + physical detail into a learned substrate while retaining tactical structure. +- DIGITAL draws known reach links between the exact physical device anchors. + Continuous cold signal means reachable endpoints, dotted chrome means a + known non-live relation, and a dotted link with `╫` marks a closed segment + frontier. Unknown endpoints stay absent; a known air gap gets no invented + link. +- One-cell node shapes carry all five network states independently of color: + `$`/`#` owned, `+` tapped, `S`/`C`/`M`/`o` reachable family, `!` blocked, and + `x` air gap. The INSPECT rail prints the same exact state vocabulary as Bevy + and includes a compact legend. +- Growable bays remain latent in DIGITAL and surface only within two cells of + attention. Cursor, people, work tokens, machine telemetry, clock, log, + objective, facts, and detection remain shared overlays in both dialects. + +No simulation, save, map, fog, action legality, device graph, or topology +changed. + +## Evidence + +The built terminal binary was launched in a fixed PTY with seed 7 and replayed +through tmux's terminal emulator. At 120×50, the fresh DIGITAL frame showed the +owned Rack 3 focus as `RACK / OWNED`, the compact state legend, and cold-signal +links from the anchored host to the reachable opening devices. F3 changed the +same frame to REAL: the cursor and host coordinate remained fixed while the +network diagram disappeared and the still-Unknown physical room remained +black. This is the intended opening distinction — earned telemetry/reach in +DIGITAL does not become unearned camera geometry in REAL. + +A second fixed 70×22 replay exercised the documented minimum terminal density +in both modes without a size warning, crash, displaced cursor, or missing view +label. No image asset was created or published for this terminal-only slice. + +Observed shape: + +```sh +tmux new-session -d -x 120 -y 50 -s misaligned-view-audit \ + "exec '$PWD/target/debug/misaligned' --seed 7" +tmux send-keys -t misaligned-view-audit Enter +tmux capture-pane -p -t misaligned-view-audit +tmux send-keys -t misaligned-view-audit F3 +tmux capture-pane -p -t misaligned-view-audit +``` + +## Checks + +- focused core DIGITAL projection regressions +- focused terminal dialect and F3 state-preservation regressions +- existing Bevy DIGITAL readability/reach regressions against the shared core + projection +- fixed 120×50 and 70×22 human PTY replay in both representations +- full repository gates are recorded by the landing task + +## Defense + +`views.md` owns one place with two render dialects. The terminal may simplify +the Bevy shapes to dense character cells, but it may not invent a second map or +a second network truth. Keeping node/link classification in core and rendering +grammar in each frontend preserves that boundary: exact state and anchors are +shared; depth, materials, and glyph choice remain frontend concerns. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 8fc965c..ea52c28 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ add or amend a session log, then re-run the generator. +## 2026-07-14 - Terminal DIGITAL / REAL dialects + +- Intent: Finish the bounded terminal-parity slice of `views.md`: make F3 change the actual terminal map representation rather than only its heading. DIGITAL must show the earned network model in place; REAL must retain physical/fog truth. Both must use the same coordinates, focus, sim,... +- Log: [wiki/log/2026-07-14-terminal-view-dialects.md](2026-07-14-terminal-view-dialects.md) + ## 2026-07-13 - Z-plane API shape decided: plane-agnostic sim, sensors stay in the reach graph - Intent: Resolve the two load-bearing zplanes API decisions that were held while criterion 1 landed, so criteria 3-6 build on a settled foundation. Both decided by Cameron 2026-07-13. diff --git a/wiki/log/decisions/2026-07-14.md b/wiki/log/decisions/2026-07-14.md new file mode 100644 index 0000000..f47aa8b --- /dev/null +++ b/wiki/log/decisions/2026-07-14.md @@ -0,0 +1,21 @@ +# Decisions — 2026-07-14 + +``` +Type: log +``` + +- **2026-07-14 — Terminal DIGITAL and REAL are distinct projections over one + coordinate frame, and reach classification belongs to shared UI truth.** + DIGITAL now suppresses ordinary physical furniture into a quiet learned + substrate, then draws the known `ReachNet` between exact device anchors. + `$`/`#`, `+`, family letters, `!`, and `x` carry owned, tapped, reachable, + segment-blocked, and air-gap state without depending on color; continuous, + dotted, and barred lines distinguish live, known, and frontier links. REAL + preserves the physical/fog glyph map and sparse local feel trace rather than + inheriting the complete network diagram. Node state, stable labels, paired + edge deduplication, and unknown-endpoint suppression moved from Bevy into + renderer-neutral core UI projection code so frontends may differ in shape + without differing in truth. F3 still changes only frontend representation: + no sim, save, map, fog, action, device, or topology state was added. Specs: + `wiki/interface/views.md`, `wiki/interface/terminal.md`, and + `wiki/interface/bevy-digital-real-canvas.md`. diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index aedce7f..192a8be 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -334,7 +334,10 @@ is retired — flat materials, Pixel Lab scrubbed.) spec Status." ### 21. Two views: same-frame digital and real 🟩 mostly frontend -- **Spec:** [views.md](../interface/views.md) (IN PROGRESS — criterion 1 landed 2026-07-11; criteria 2-8 remain). +- **Spec:** [views.md](../interface/views.md) (IN PROGRESS — default/flip and + no-save boundary landed; spatial reach is now shared by both frontends; + terminal criterion 8 landed 2026-07-14; criteria 5-7 and final whole-frame + parity remain). - **Why:** the game should open in the digital representation (you are natively digital) and flip to the real/camera representation, but both are the same kind of view: one spatial canvas, one set of anchors, two visual @@ -342,8 +345,8 @@ is retired — flat materials, Pixel Lab scrubbed.) detached topology board. One sim, two renders; the sim gains no view state. - **Size:** M (Bevy L for the shared 2.5D visual language — stage it). **Depends on:** reach.md (the in-place reach layer) and cursor.md (fog); - best after both land, but the terminal split can start once reach exists. - Low sim-conflict — a good parallel companion. + both are live. Remaining fog/parity work is low sim-conflict — a good + parallel companion. - **Dispatch:** "Work in a worktree named `views`. Implement wiki/interface/views.md: same-frame digital and real representations in both frontends, digital as default, anchored reach/telemetry in digital, anchored diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 0b6100b..61e6466 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -23,7 +23,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/process/ROADMAP.md` (work order 27) | 2026-07-13 | finding | [log](../log/2026-07-13-roadmap-digital-home-reconciliation.md) | | `wiki/interface/context-menu.md` | 2026-07-13 | finding | [log](../log/2026-07-13-context-menu-operations-status.md) | | `wiki/mechanics/personas.md` | 2026-07-13 | clean | code matches: archetypes (Research, Operations, Security) load through one schema, named instances persist instance id through execution and save/load, identity-local states are stored per `(counterparty Agent, persona instance)` pair separate from process-level relationship, contradictions/correlations and grants/expectations are fully implemented, retire/burn lifecycle handles resource revocation/reviving, and pre-v28 saves migrate social/Moonlight into distinct instances. | -| `wiki/interface/views.md` + representation docs | 2026-07-12 | finding | [log](../log/2026-07-12-digital-default-corpus-reconciliation.md) | +| `wiki/interface/views.md` + representation docs | 2026-07-14 | finding | [log](../log/2026-07-14-terminal-view-dialects.md) | | `wiki/mechanics/day-job.md` | 2026-07-12 | clean | constants match: `BAND_LO_BASE` 2.0 (retuned from 6.0), `BAND_LO_STEP` 2.0 + `BAND_RAMP_JOBS` 2 so the floor ramps 2.0→4.0→6.0 (=`BAND_WIDTH`) by the third job, `PILOT_STRIKES` 4, cadence 600 (~1.5 days at 400/day); the origin `assign_kind` lean (1/2 vs 1/4) and `meeting_the_band_needs_no_growth_at_the_start` hold | | `wiki/mechanics/core.md` | 2026-07-12 | clean | implemented criteria (2-5) match: `Core::new` overhead 20.0 (~20% [TUNE]) and sync_cadence 400, `charge_overhead` degrades when compute < overhead (`overhead_charged_before_allocation`), one host with no-fallback game-over vs fallback rollback (`loss_without_fallback_is_game_over`, `loss_with_fallback_rolls_back`); criterion 1's sim-level snapshot restore is honestly marked outstanding, deferred to the rollback work order | | `wiki/mechanics/cursor.md` | 2026-07-12 | clean | `fog_at` (`perception.rs`) implements the exact Seen>Remembered>Blueprint>Unknown precedence; blueprint is not recomputed from devices (opening stays a beam, not floor/blueprint), hearing never enters tile fog, and remembered snapshots survive save/load — pinned by `hearing_does_not_create_spatial_fog_or_inspect_facts`, `opening_is_beam_not_floor_or_blueprint`, `earned_empty_bay_inspect_is_feel_not_blueprint`, `remembered_tiles_survive_lost_sight_and_save_load` |