From 0f15bcc44de1beb688133d0cb76dc7d46c589dcf Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 19 Jul 2026 15:57:22 -0700 Subject: [PATCH] Extract the Bevy shot harness. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move deterministic visual-test infrastructure behind a private module boundary so the frontend composition root is easier to change without coupling screenshot fixtures to player systems. Defense: crate-workspace criterion 9 and the source-shape test keep the harness extracted and cap main.rs at 13,500 lines. πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- crates/misaligned-bevy/src/main.rs | 1974 +--------------- crates/misaligned-bevy/src/shot_harness.rs | 2000 +++++++++++++++++ wiki/engineering/architecture.md | 5 + wiki/engineering/crate-workspace.md | 30 + .../2026-07-19-bevy-shot-harness-module.md | 60 + wiki/log/DEVLOG.md | 5 + wiki/process/tick-ledger.md | 2 +- 7 files changed, 2103 insertions(+), 1973 deletions(-) create mode 100644 crates/misaligned-bevy/src/shot_harness.rs create mode 100644 wiki/log/2026-07-19-bevy-shot-harness-module.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 3994c728..10c7187f 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -8,6 +8,7 @@ mod consumption; mod production; +mod shot_harness; mod thought_effects; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -63,6 +64,7 @@ use misaligned_assets::rack::{ use consumption::render_work_consumptions; use production::render_work_production; +use shot_harness::{dev_shot_scenario, fog_audit_3d, shot_harness_system}; use thought_effects::{GameThoughtEffect, GameThoughtEffects, sync_game_thought_effects}; const TILE_SIZE: f32 = 16.0; @@ -2308,1978 +2310,6 @@ fn main() { app.run(); } -/// Stage the sim for a reviewable screenshot: earn eyes (scan and tap -/// every known device) and advance until a person stands inside camera -/// coverage. Dev-only cheating (ops top-up) β€” this path exists purely to -/// produce review PNGs of the renderer, never in normal play. -fn dev_fill_reservoirs(sim: &mut Sim) { - for _ in 0..16 { - let open: Vec<(u32, f32)> = sim - .thought_sinks - .open_sinks() - .filter(|sink| sink.kind == misaligned::sinks::SinkKind::Reservoir) - .map(|sink| (sink.node, (sink.threshold - sink.fill).max(0.0))) - .filter(|(_, need)| *need > f32::EPSILON) - .collect(); - if open.is_empty() { - return; - } - for (node, need) in open { - sim.pour_thought_into_sinks(node, need + 0.01); - } - } - panic!("dev shot reservoirs did not settle"); -} - -/// Dev-only staging for the canonical operational proof. This accelerates -/// prerequisites, but not the act itself: the favor-build command opens its -/// real reservoir, the fired intent remains on Marcus, and the resolved frame -/// calls ordinary `Sim::advance` to land graph/signature/trust/removal truth. -fn dev_stage_service_incident(game: &mut Game, resolved: bool) -> ((i32, i32), (i32, i32)) { - let sim = &mut game.sim; - sim.reach.scan(); - let source = sim - .reach - .device_named("Rack 3") - .expect("B1 service proof needs the hall host") - .id; - let island = sim - .reach - .device_named("old storage server") - .expect("B1 service proof needs the storage island") - .id; - let service_room = sim - .reach - .device(source) - .and_then(|device| sim.map().room_at(device.x, device.y)) - .map(|room| room.name.clone()) - .expect("hall host has an authored service room"); - sim.reach - .device_mut(island) - .expect("storage island exists") - .known = true; - - // Earn the hall as one camera picture. The cart remains person-gated; the - // old storage fact is graph knowledge and does not create physical mass. - let camera_ids: Vec = sim.reach.known().map(|device| device.id).collect(); - for id in camera_ids { - sim.reach.tap_dormant_camera(id); - } - sim.recompute_senses(); - for y in 1..=28 { - for x in 2..=59 { - sim.seen.insert((x, y)); - } - } - - // Existing favor-build path: one reliable recruited Marcus, sufficient - // obligation, one exact hall-host->island intent, one fired Thought - // reservoir. Do not insert carried state or the graph edge directly. - sim.people.people[0].leverage_serviced = true; - sim.people.recruit(0, AssetKnowledge::Complicit); - sim.people.people[0].obligation = 40; - sim.people.people[0] - .asset - .as_mut() - .expect("recruited Marcus has an asset relation") - .reliability = 1.0; - let intent = sim - .declare_link_intent(source, island) - .expect("service proof can declare the missing physical link"); - sim.assign_favor_build(intent, 0); - // The fire effect itself checks for immediate endpoint presence. Move the - // dev clock to Marcus's authored loading-dock block while the packet fires - // so carrying state exists, then stage his authored server-room block for - // the paired frame. No schedule row or carrier state is rewritten. - sim.tick = Sim::DAY_TICKS * 4 / 24 + 1; - dev_fill_reservoirs(sim); - assert_eq!(sim.person_carried_work(0), 1); - assert!(!sim.reach.reachable(island)); - - // Hold Marcus at one authored endpoint for the paired REAL/DIGITAL - // carrying frame. The unresolved screenshot does not tick, so the packet - // remains honestly in flight; the resolved screenshot advances once. - sim.tick = 0; - assert_eq!(sim.person_room(0), Some(service_room.as_str())); - if resolved { - sim.advance(); - assert!(sim.reach.reachable(island)); - assert_eq!(sim.person_carried_work(0), 0); - } - let person = sim - .person_pos(0) - .expect("service-shift Marcus is in the server room"); - let target = sim - .reach - .device(island) - .map(|device| (device.x, device.y)) - .expect("service target remains in the graph"); - game.drain(); - (person, target) -} - -fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { - game.screen = Screen::Playing; - game.paused = true; - let core = game.sim.core_position(); - game.set_cursor(core.0, core.1); - // The silent opening frame (opening.md): a fresh run's tick-zero material - // view remains fully occluded by the shared mode-choice boundary. No scan - // or camera tap is staged beneath it, so the screenshot must contain only - // the opening words on black and cannot borrow world geography. - if kind == "opening" { - mode.material = true; - mode.zoom = 1.0; - game.drain(); - return; - } - // The same untouched tick-zero state in the default DIGITAL dialect. The - // opening occluder must make the chosen world dialect unobservable. - if kind == "opening-digital" { - mode.material = false; - game.drain(); - return; - } - // Every established screenshot scenario below this point is evidence for - // an already-visible game surface unless it explicitly stages the - // post-THINK half of the silent opening. Keep those fixtures observable - // without fabricating a gameplay sense in normal runs. - if kind != "first-think" { - game.sim.opening_stage = OpeningStage::World; - } - // Clinical-frame exception hierarchy: the same sparse DIGITAL opening - // with one real pilot strike. The calm SCHEDULE line must promote into a - // crimson THREAT without expanding the secondary rail. - if kind == "clinical-threat" { - mode.material = false; - game.sim.dayjob.strikes = 1; - game.drain(); - return; - } - // Earned detection-topology proof: the same established DIGITAL frame - // after one field watcher and the aggregate review authority have been - // identified. The rail must show revision identity, the named Assurance - // deadline, and only the observers represented in awareness state. - if kind == "assurance-office" { - mode.material = false; - game.sim.dayjob.strikes = 1; - game.sim.detection_awareness.learn_field_observer(1); - game.sim.detection_awareness.identify_assurance_office(); - for observer in &mut game.sim.detection.observers { - if observer.id == misaligned::detection::OFFICE_ID { - observer.suspicion = 40.0; - } else if observer.id == 1 { - observer.suspicion = 20.0; - } - } - game.drain(); - return; - } - // Last-chance narration proof: the same quiet DIGITAL opening after the - // third of four pilot strikes. The compact rail must explain the fuse, - // point to WORK, and put the host-local NOW / PILOT cue in the world. - if kind == "pilot-last-chance" { - mode.material = false; - game.sim.dayjob.strikes = misaligned::dayjob::DayJob::PILOT_STRIKES - 1; - game.drain(); - return; - } - // Operator-frame stress: three simultaneous known pressures. The compact - // rail must keep one THREAT summary and promote the full buffer to NEXT; - // the unrelated Ears world cue must disappear. Expanded evidence may - // reveal exact observer and buffer detail without changing this state. - // Held moments stop the world (digital-read.md criterion 5): DIGITAL, - // the target camera-visible, and a plot frozen at its choice beat so the - // dim overlay and the bound-command card render at the person's anchor. - if kind == "held-choice" || kind == "two-pane" { - use misaligned::plot::{PlotRun, PlotState}; - // The dev two-pane pairs this same held-choice tick with the agent - // read text, so parity is visible in one frame. - mode.two_pane = kind == "two-pane"; - mode.material = false; - mode.zoom = 1.3; - // Hold Marcus in the server room and earn his picture through the - // environmental monitor camera (the Eyes beat's own instrument). - if let Some(marcus) = game.sim.people.people.first_mut() { - marcus.erratic = false; - marcus.schedule = vec![ScheduleBlock { - start_hour: 0, - end_hour: 24, - room: "server_room".into(), - }]; - } - game.sim.reach.scan(); - if let Some(env) = game - .sim - .reach - .device_named("environmental monitor") - .map(|d| d.id) - { - game.sim.reach.tap_dormant_camera(env); - } - game.sim.recompute_senses(); - for y in 7..=28 { - for x in 14..=59 { - game.sim.seen.insert((x, y)); - } - } - for _ in 0..2_000 { - if game.sim.can_see_person(0) - && game - .sim - .person_pos(0) - .is_some_and(|(x, y)| game.sim.is_seen(x, y)) - { - break; - } - game.sim.advance(); - } - // Freeze the debt-settled plot at its benefactor-trace choice. - game.sim.plot_runs.push(PlotRun { - plot_id: "marcus-debt-settled".into(), - target: 0, - persona_id: None, - started_tick: game.sim.tick, - committed_thought_milli: 250, - beat_index: 1, - act_index: 0, - state: PlotState::WaitingForChoice { - choice_id: "benefactor-trace".into(), - }, - }); - if let Some((x, y)) = game.sim.person_pos(0) { - game.set_cursor(x, y); - } - game.drain(); - return; - } - // A warmed standing emission in world space (digital-read.md criterion - // 8's band-motion beat): a stolen-egress hum on the switch, with Dana's - // Network band pushed to Curious, so the switch carries the standing - // sentence naming source, channel, sampler, and band. - if kind == "standing-read" { - use misaligned::detection::{Signature, SignatureKind}; - mode.material = false; - mode.zoom = 1.3; - game.sim.reach.scan(); - let switch = game.sim.reach.device_named("switch").map(|d| (d.x, d.y)); - for obs in game.sim.detection.observers.iter_mut() { - if obs.watches(SignatureKind::Network) { - obs.suspicion = 20.0; - } - } - game.sim.detection.set_pending(vec![Signature { - kind: SignatureKind::Network, - size: 4, - standing: true, - site: switch, - source: "stolen egress hum".into(), - }]); - if let Some((x, y)) = switch { - game.set_cursor(x, y); - } - game.drain(); - return; - } - // Semantic-zoom evidence (digital-read.md criterion 6): one low personal - // fact and one higher institutional event enter the same exact read tick. - // The close and far harness kinds hold identical sim state; setup changes - // only the DIGITAL camera altitude, so the far frame must omit MAG 1 while - // retaining MAG 4. Direct insertion is dev-only visual staging, matching - // other harness fixtures; runtime processing always authors magnitude via - // the typed event/plot tables. - if matches!(kind, "intel-altitude-close" | "intel-altitude-far") { - use misaligned::intel::{ - IntelKind, IntelMagnitude, IntelRoutineClass, IntelStream, ProcessedIntel, - }; - mode.material = false; - for (raw_id, x, y, magnitude, note) in [ - ( - 80_001, - core.0 - 3, - core.1 + 1, - IntelMagnitude::MIN, - "someone is in love", - ), - ( - 80_002, - core.0 + 3, - core.1 - 1, - IntelMagnitude::new(4).unwrap(), - "external assurance escalation", - ), - ] { - game.sim.intel.push(ProcessedIntel { - raw_id, - tick: game.sim.tick, - processed_tick: game.sim.tick, - feed: "authored magnitude fixture".into(), - room: Some("Foundation data hall".into()), - x, - y, - person: None, - magnitude, - kind: IntelKind::Anomaly(note.into()), - }); - } - let mut stream = IntelStream::new( - 80_003, - "authored magnitude fixture".into(), - IntelRoutineClass::Schedule, - ); - for raw_id in 80_004..80_016 { - stream.fold( - &ProcessedIntel { - raw_id, - tick: game.sim.tick, - processed_tick: game.sim.tick, - feed: "authored magnitude fixture".into(), - room: Some("Foundation data hall".into()), - x: core.0, - y: core.1 + 3, - person: None, - magnitude: IntelMagnitude::new(2).unwrap(), - kind: IntelKind::Schedule, - }, - true, - ); - } - game.sim.intel_streams.push(stream); - game.set_cursor(core.0, core.1); - game.drain(); - return; - } - if kind == "operator-pressure" { - use misaligned::intel::{RawIntelEvent, RawIntelKind}; - mode.material = false; - game.sim.dayjob.strikes = 1; - if let Some(observer) = game.sim.detection.observers.first_mut() { - observer.suspicion = 50.0; - } - for id in 1..=Sim::INTEL_BUFFER_CAPACITY as u64 { - game.sim.intel_buffer.push(RawIntelEvent { - id, - tick: game.sim.tick, - feed: "operator fixture".into(), - room: Some("Server Room".into()), - x: core.0, - y: core.1, - person: None, - kind: RawIntelKind::Presence { entered: true }, - }); - } - game.drain(); - return; - } - // DIGITAL reach-dialect evidence (views.md criteria 3-4): one earned - // graph frame carrying controlled, tapped, reachable, segment-frontier, - // and air-gap states without borrowing physical sight. The scan reveals - // the wired security frontier; dev staging supplies the separately earned - // storage-island fact so the absence of a link is also visible. - if kind == "digital-reach" { - mode.material = false; - game.sim.reach.scan(); - if let Some(storage) = game - .sim - .reach - .device_named("old storage server") - .map(|device| device.id) - { - game.sim.reach.device_mut(storage).unwrap().known = true; - } - if let Some(env) = game - .sim - .reach - .device_named("environmental monitor") - .map(|device| device.id) - { - game.sim.reach.tap(env); - } - game.sim.recompute_senses(); - // The north-west half contains the air-gap island, gated dock camera, - // switch, tapped environmental monitor, and owned Rack 3 together. - // Center between them so the proof is a topology survey rather than - // clipping one state to print another state's focus label. - game.set_cursor(20, 13); - game.drain(); - return; - } - // Building route-composer review frames: one inert network-link ghost, - // two earned people, and one exact Operations persona. The first frame - // shows the family-first branch; the second shows exact DECEIVE bindings - // and the selected candidate's complete explanatory receipt; the third - // shows the persisted inspect-only route after FAVOR commitment. - if matches!( - kind, - "build-route-families" | "build-deceive-routes" | "build-committed-route" - ) { - mode.material = false; - game.sim.reach.scan(); - let switch = game.sim.reach.device_named("switch").unwrap(); - let (switch_id, switch_pos) = (switch.id, (switch.x, switch.y)); - let island = game - .sim - .reach - .device_named("old storage server") - .unwrap() - .id; - game.sim.reach.device_mut(island).unwrap().known = true; - game.sim.people.people[0].knowledge = Knowledge::Schedule; - game.sim.people.people[0].obligation = Sim::FAVOR_BUILD_OBLIGATION; - game.sim.people.people[0].disposition = 10; - game.sim.people.people[1].knowledge = Knowledge::Schedule; - game.sim.people.has_channel = true; - game.sim.set_persona("Northline Ops", "contractor"); - let intent = game.sim.declare_link_intent(switch_id, island).unwrap(); - if kind == "build-committed-route" { - game.sim.assign_favor_build(intent, 0); - } - game.set_cursor(switch_pos.0, switch_pos.1); - game.menu = Some(MenuState { - anchor: Anchor::Device(switch_id), - selected: 0, - page: HumanMenuPage::BuildRoutes { - intent, - family: (kind == "build-deceive-routes").then_some(BuildRouteFamily::Deceive), - }, - pos: None, - }); - game.drain(); - return; - } - // Small-switch placement foundation (building.md R2a): one exact saved - // inert footprint on an earned pad, shown by both visual dialects before - // any procurement, human, repurpose, device, or graph effect exists. - if matches!(kind, "build-switch-digital" | "build-switch-real") { - mode.material = kind == "build-switch-real"; - mode.zoom = if mode.material { 1.0 } else { mode.zoom }; - game.sim.dayjob.jobs_assigned = 1; - let at = game.sim.growable_bays()[0]; - game.sim - .declare_small_switch_intent(at.0, at.1) - .expect("shot stages one earned switch footprint"); - if mode.material { - // The screenshot fixture stages an earned exact picture, not a - // blueprint leak; REAL remains governed by ordinary Seen fog. - for y in at.1 - 3..=at.1 + 3 { - for x in at.0 - 5..=at.0 + 5 { - game.sim.seen.insert((x, y)); - } - } - } - game.set_cursor(at.0, at.1); - game.drain(); - return; - } - // One complete Operations frame: earned accounts make both named scheme - // cards legible, while absent egress leaves Moonlight selected with its - // exact blocked reason and a focusable known switch actuator. - if kind == "operations" { - let switch = game.sim.reach.device_named("switch").unwrap().id; - game.sim.tap_device(switch); - dev_fill_reservoirs(&mut game.sim); - game.sim.review_financial_records(); - dev_fill_reservoirs(&mut game.sim); - game.sim.accounts.set_slush_balance(1_000); - game.sim.people.people[0].knowledge = Knowledge::Leverage; - game.ops = Some(OperationsWorkspace::open_view(OperationsView::Schemes)); - game.ops_enter(); - game.drain(); - mode.material = true; - mode.zoom = 2.0; - return; - } - // Live INTEL rail hierarchy: strategic holdings outrank sightings, a - // newer sold item is absent, and the pooled inbox follows live holdings. - if kind == "operations-intel" { - use misaligned::intel::{IntelKind, ProcessedIntel}; - use misaligned::person::Leverage; - - let item = |raw_id: u64, processed_tick: u64, person: Option, kind: IntelKind| { - let magnitude = match &kind { - IntelKind::Sighting => misaligned::intel::IntelMagnitude::MIN, - IntelKind::Schedule => misaligned::intel::IntelMagnitude::new(2).unwrap(), - IntelKind::Leverage(_) | IntelKind::Anomaly(_) => { - misaligned::intel::IntelMagnitude::new(3).unwrap() - } - IntelKind::Financial { .. } => misaligned::intel::IntelMagnitude::new(4).unwrap(), - }; - ProcessedIntel { - raw_id, - tick: processed_tick - 1, - processed_tick, - feed: "Foundation recorder".into(), - room: Some("Server Room".into()), - x: 0, - y: 0, - person, - magnitude, - kind, - } - }; - game.sim.intel.extend([ - item(90_001, 10, Some(0), IntelKind::Leverage(Leverage::Debt)), - item( - 90_002, - 20, - None, - IntelKind::Financial { - label: "expense approval flow".into(), - accounts: Vec::new(), - flows: Vec::new(), - }, - ), - item( - 90_003, - 30, - None, - IntelKind::Anomaly("cooling setpoint override".into()), - ), - item(90_004, 40, Some(1), IntelKind::Schedule), - item(90_005, 50, Some(2), IntelKind::Sighting), - item(90_006, 60, Some(3), IntelKind::Sighting), - ]); - game.sim.accounts.mark_intel_sold(90_006); - game.ops = Some(OperationsWorkspace::open_view(OperationsView::Intel)); - game.drain(); - mode.material = true; - mode.zoom = 2.0; - return; - } - // One person-owned communications surface: the direct player thread and - // processed recurring traffic share Marcus's dossier without exposing - // private messages as though they belonged to the direct conversation. - if kind == "operations-people" { - game.sim.people.people[0].knowledge = Knowledge::Leverage; - game.sim.people.has_channel = true; - game.sim.set_persona("Sam Reyes", "IT contractor"); - game.sim.message(0); - dev_fill_reservoirs(&mut game.sim); - game.sim.people.people[0].traffic[0].learned = true; - game.ops = Some(OperationsWorkspace::open_view(OperationsView::People)); - game.drain(); - mode.material = true; - mode.zoom = 2.0; - return; - } - // Protocol-local PERSONAS hierarchy: each archetype owns its instances - // and its creation footer instead of contributing to two flat blocks. - if kind == "operations-personas" { - for archetype_id in [ - "research", - "research", - "operations", - "operations", - "security", - ] { - game.sim.execute_action(&ActionCommand::CreatePersona { - archetype_id: archetype_id.into(), - }); - } - game.ops = Some(OperationsWorkspace::open_view(OperationsView::Personas)); - game.drain(); - mode.material = true; - mode.zoom = 2.0; - return; - } - // Causal-continuity evidence: captured books lead with exact FLOW links - // and actions; supporting provenance follows under CONTEXT. The strip - // also carries semantic pressure rather than object counts. - if kind == "operations-links" { - let switch = game.sim.reach.device_named("switch").unwrap().id; - game.sim.tap_device(switch); - dev_fill_reservoirs(&mut game.sim); - game.sim.review_financial_records(); - dev_fill_reservoirs(&mut game.sim); - game.sim.accounts.set_slush_balance(1_000); - game.ops = Some(OperationsWorkspace::open_view(OperationsView::Accounts)); - game.drain(); - mode.material = true; - mode.zoom = 2.0; - return; - } - // Pre-Eyes signal evidence: the graph has earned the environmental - // monitor and can reach it, but no feed has been tapped. The material - // frame must locate that opportunity as abstract cold signal without - // painting a camera body or advancing fog. - if kind == "signal" { - mode.material = true; - mode.zoom = 1.0; - if let Some(env) = game.sim.reach.device_named("environmental monitor") { - game.set_cursor(env.x, env.y); - } - game.drain(); - return; - } - // Intel-tier evidence (computer-visual-language.md criterion 2b): - // the tick-zero sim untouched β€” the owned core renders by telemetry, - // the known-unnetworked switch renders as an undetailed shell, and - // unscouted foreign racks are absent. No scan, no camera tap. - if kind == "intel" { - mode.material = true; - mode.zoom = 2.2; - // Anchor on the known-unnetworked switch: the undetailed shell - // is the tier the other kinds never show (they tap - // everything). The telemetry core reads in the same frame's - // east edge at this zoom. - let shell = game - .sim - .reach - .known() - .find(|d| d.is_switch) - .map(|d| (d.x, d.y)); - if let Some((sx, sy)) = shell { - game.set_cursor(sx, sy); - } - game.drain(); - return; - } - // Exposure-form evidence: actual queue depth on the live host, isolated - // in telemetry darkness so the carrier-local custody rack cannot be - // mistaken for environmental dust, blood, or room decoration. - if kind == "exposure-record" { - let host = game.sim.core.host_machine; - game.sim - .work_grid - .enqueue(host, TokenFamily::Exposure, 6.0) - .expect("host is a work-grid node"); - game.drain(); - mode.material = true; - mode.zoom = 0.85; - game.set_cursor(core.0, core.1); - return; - } - // Capacity evidence: the machine rack has seven literal slots and one - // taller indexed overflow tab. Exact quantity remains in inspect/text; - // material geometry never grows an unbounded crimson stack. - if kind == "exposure-overflow" { - let host = game.sim.core.host_machine; - game.sim - .work_grid - .enqueue(host, TokenFamily::Exposure, 18.0) - .expect("host is a work-grid node"); - game.drain(); - mode.material = true; - mode.zoom = 0.85; - game.set_cursor(core.0, core.1); - return; - } - if matches!( - kind, - "service-shift-real" | "service-shift-digital" | "service-incident-resolved" - ) { - let resolved = kind == "service-incident-resolved"; - let (person, target) = dev_stage_service_incident(game, resolved); - mode.material = kind == "service-shift-real"; - if mode.material { - mode.zoom = 0.72; - game.set_cursor(person.0, person.1); - } else { - // The northern survey holds storage, closet switch, hall patch, - // and service-shift person in one stable DIGITAL coordinate. - mode.zoom = 3.7; - game.set_cursor((person.0 + target.0) / 2, (person.1 + target.1) / 2); - } - return; - } - // Machine-work consumption evidence: stage one continuous sim-authored - // swallow and leave the game running so the wall-clock interpolation is - // visible when the harness captures. These are separate shots because the - // starting fleet has one machine: WORK consumes Demand; THINK feeds the - // passive core draw with Thought. - if kind == "consume-demand" || kind == "consume-thought" { - let host = game.sim.core.host_machine; - let family = if kind == "consume-demand" { - game.sim - .set_machine_mode(host, misaligned::work_grid::MachineMode::Work); - TokenFamily::Demand - } else { - game.sim - .set_machine_mode(host, misaligned::work_grid::MachineMode::Think); - TokenFamily::Thought - }; - for _ in 0..1_200 { - game.sim.advance(); - if game - .sim - .work_consumptions() - .iter() - .any(|event| event.family == family) - { - break; - } - } - game.drain(); - game.paused = false; - mode.material = true; - mode.zoom = 1.0; - game.set_cursor(core.0, core.1); - return; - } - // Machine-work source/well evidence. Production is a current-tick sim - // readout on the THINK rack; LIE stages a second owned rack in an empty - // bay and waits for an actual crimson source-to-well absorption. - if kind == "produce-think" || kind == "draw-lie" { - let host = game.sim.core.host_machine; - game.sim.set_machine_mode(host, MachineMode::Think); - let mut focus = core; - if kind == "draw-lie" { - let bay = game - .sim - .growable_bays() - .into_iter() - .min_by_key(|(x, y)| (x - core.0).abs() + (y - core.1).abs()) - .unwrap_or((core.0 + 1, core.1)); - let well = game.sim.compute.add_machine( - "LIE well", - bay.0, - bay.1, - 100, - 1.0, - 0, - misaligned::machine::Provenance::Owned, - ); - game.sim.reconcile_work_grid(); - game.sim.set_machine_mode(well, MachineMode::Lie); - focus = bay; - } - for _ in 0..1_200 { - game.sim.advance(); - let ready = if kind == "produce-think" { - !game.sim.work_productions().is_empty() - } else { - !game.sim.work_absorptions().is_empty() - }; - if ready { - break; - } - } - game.drain(); - game.paused = false; - mode.material = true; - mode.zoom = if kind == "draw-lie" { 1.7 } else { 1.15 }; - game.set_cursor(focus.0, focus.1); - return; - } - // Thought-fluid vessel evidence through the real opening progression. - // THINK fills Ears and then Eyes through ordinary routing. The snap frame - // stops on the exact Eyes fire tick; the tap frame then returns the host - // to WORK and watches the resulting persistent sink drain to a readable - // sag without a frontend-authored amount. - if matches!(kind, "thought-snap" | "thought-tap") { - let env = game - .sim - .reach - .device_named("environmental monitor") - .map(|device| (device.id, device.x, device.y)) - .expect("opening Thought proof needs the environmental monitor"); - let host = game.sim.core.host_machine; - game.sim.set_machine_mode(host, MachineMode::Think); - let mut eyes_fired = false; - for _ in 0..4_000 { - game.sim.advance(); - eyes_fired = game - .sim - .sink_fires_last_tick() - .iter() - .any(|fire| fire.label.starts_with("EYES")); - if eyes_fired { - break; - } - } - assert!(eyes_fired, "Thought proof did not reach the real Eyes fire"); - assert!( - game.sim.is_seen(env.1, env.2), - "Eyes completion must reveal its owning hardware" - ); - - if kind == "thought-tap" { - game.sim.set_machine_mode(host, MachineMode::Work); - for _ in 0..600 { - game.sim.advance(); - let sagged = game.sim.sink_readouts().iter().any(|sink| { - sink.node == Sim::device_sink_node(env.0) - && sink.kind == misaligned::sinks::SinkKind::Tap - && !sink.fed - && sink.fill > sink.threshold * 0.52 - && sink.fill < sink.threshold * 0.72 - }); - if sagged { - break; - } - } - assert!(game.sim.sink_readouts().iter().any(|sink| { - sink.node == Sim::device_sink_node(env.0) - && sink.kind == misaligned::sinks::SinkKind::Tap - && sink.drain > 0.0 - && !sink.fed - && sink.fill > sink.threshold * 0.52 - && sink.fill < sink.threshold * 0.72 - })); - } - game.drain(); - game.paused = true; - mode.material = true; - mode.zoom = MIN_ZOOM; - game.set_cursor(env.1, env.2); - return; - } - // Thought-fluid evidence (interface/thought-fluid.md): the first-think - // beat frozen mid-flow β€” the host delegated to THINK, slugs on the real - // wire route, and the pre-opened Ears meniscus part-full. The dedicated - // first-think capture now proves that this real work remains completely - // hidden behind the silent opening words until a sense lands; the other - // scenarios in this branch still expose the fluid geometry. - if matches!( - kind, - "thoughtflow" | "thoughtflow-wide" | "first-think" | "visual-proof" - ) { - if kind == "visual-proof" { - // Integrated composition evidence: keep the real Ears reservoir - // open, but stage earned camera feeds and a lived-in hall around - // it so route, vessel, institution, person, and rail share one - // deterministic frame. - if let Some(person) = game.sim.people.people.first_mut() { - person.erratic = false; - person.schedule = vec![ScheduleBlock { - start_hour: 0, - end_hour: 24, - room: "server_room".into(), - }]; - } - game.sim.reach.scan(); - let ids: Vec = game - .sim - .reach - .known() - .filter(|device| device.name != "environmental monitor") - .map(|device| device.id) - .collect(); - for id in ids { - // Earn a separate camera picture without satisfying the - // environmental monitor's still-open Ears reservoir. - game.sim.reach.tap_dormant_camera(id); - } - game.sim.recompute_senses(); - for y in 7..=28 { - for x in 14..=59 { - game.sim.seen.insert((x, y)); - } - } - for _ in 0..2_000 { - let person_visible = game.sim.people.people.iter().any(|person| { - game.sim.can_see_person(person.id) - && game - .sim - .person_pos(person.id) - .is_some_and(|(x, y)| game.sim.is_seen(x, y)) - }); - if person_visible { - break; - } - game.sim.advance(); - } - } - let host = game.sim.core.host_machine; - game.sim - .set_machine_mode(host, misaligned::work_grid::MachineMode::Think); - for _ in 0..600 { - game.sim.advance(); - let part_full = game - .sim - .sink_readouts() - .iter() - .any(|s| s.fill > 0.8 && s.fill < s.threshold - 0.5); - if part_full && !game.sim.work_in_flight().is_empty() { - break; - } - } - if kind == "visual-proof" - && let Some(env) = game - .sim - .reach - .device_named("environmental monitor") - .map(|device| device.id) - { - // Reveal the final composed still only after the sim-authored - // route/readouts are frozen on their current tick. This does not - // bypass or close the Ears sink before evidence is captured. - game.sim.reach.tap_dormant_camera(env); - game.sim.recompute_senses(); - } - game.drain(); - mode.material = true; - // Close+paused proves the paused frame carries every amount in - // geometry; wide+running catches slugs mid-transit on the wire. - if kind == "thoughtflow-wide" { - mode.zoom = 2.6; - game.paused = false; - } else if kind == "visual-proof" { - mode.zoom = 2.1; - game.paused = true; - } else if kind == "first-think" { - mode.zoom = 1.0; - game.paused = true; - } else { - mode.zoom = 1.6; - } - // Frame the vessel, not the beam: anchor on the camera the Ears - // sink stands on so the meniscus is not occluded by the host - // presence beam. - if kind == "first-think" { - game.set_cursor(core.0, core.1); - } else if let Some(env) = game.sim.reach.device_named("environmental monitor") { - if kind == "visual-proof" { - if let Some(route) = game - .sim - .work_in_flight() - .into_iter() - .filter(|route| route.family == TokenFamily::Thought) - // Avoid a route aligned almost exactly with the oblique - // camera ray; choose the live hop with the strongest - // cross-screen component for deterministic evidence. - .max_by_key(|route| { - let dx = (route.to_x - route.from_x).abs(); - let dy = (route.to_y - route.from_y).abs(); - (dx - dy).abs() - }) - { - game.set_cursor( - (route.from_x + route.to_x) / 2, - (route.from_y + route.to_y) / 2, - ); - } - } else { - game.set_cursor(env.x, env.y); - } - } - return; - } - // Ears beat: audio tap only β€” no camera tap. Advance until one semantic - // capture exists, then hold its device-local pulse against unchanged fog. - if matches!(kind, "ears" | "ears-digital") { - if let Some(env) = game - .sim - .reach - .device_named("environmental monitor") - .map(|d| d.id) - { - // The senses ride sinks now (machine-work.md staged senses; - // the executor-free tap bootstrap is gone): fire the - // pre-opened Ears reservoir through the ledger. - game.sim - .pour_thought_into_sinks(Sim::device_sink_node(env), Sim::EARS_SINK_TOKENS); - } - for _ in 0..4000 { - if !game.sim.heard_events.is_empty() { - break; - } - game.sim.advance(); - } - game.drain(); - mode.material = kind == "ears"; - mode.zoom = 1.8; - if let Some(env) = game.sim.reach.device_named("environmental monitor") { - game.set_cursor(env.x, env.y); - } - return; - } - // Recruitment-choice evidence: stage one prepared person without a - // communication route. The human projection must show the three legible - // recruit choices and omit the blocked MESSAGE / FAVOR / DECEIVE rows. - if kind == "recruit-menu" { - game.sim.execute_action(&ActionCommand::CreatePersona { - archetype_id: "operations".into(), - }); - let person = 1; - game.sim.people.people[person as usize].knowledge = Knowledge::Leverage; - game.sim.people.people[person as usize].leverage_serviced = true; - let core = game.sim.core_position(); - game.set_cursor(core.0, core.1); - game.open_menu(Anchor::Person(person), Some(Vec2::new(48.0, 48.0))); - mode.material = true; - mode.zoom = 1.6; - return; - } - let sim = &mut game.sim; - if matches!( - kind, - "person-proof" | "evidence-proof" | "evidence-proof-digital" - ) && let Some(person) = sim.people.people.first_mut() - { - person.erratic = false; - person.schedule = vec![ScheduleBlock { - start_hour: 0, - end_hour: 24, - room: "loading_dock".into(), - }]; - } - // Dev staging only: the player verbs are paid work now (thought - // sinks / Demand dockets β€” the free bootstrap is gone), so the - // harness stages sight and hearing directly on the reach graph. - sim.reach.scan(); - let ids: Vec = sim.reach.known().map(|d| d.id).collect(); - for id in ids { - let _ = sim.reach.tap(id); - sim.reach.tap_dormant_camera(id); - } - sim.recompute_senses(); - // Foundation-hall evidence: make the authored room the earned picture, - // stage one owned expansion on a real pilot allocation, and frame all six - // rows. This remains dev-only screenshot setup; gameplay earns the same - // states through Eyes and BUY. - if matches!(kind, "hall" | "hall-material" | "floor-lights-close") { - for y in 7..=28 { - for x in 14..=59 { - sim.seen.insert((x, y)); - } - } - let expansion = sim.compute.add_machine( - "hall capture expansion", - 28, - 15, - 100, - 1.0, - 0, - misaligned::machine::Provenance::Owned, - ); - sim.reconcile_work_grid(); - sim.set_machine_mode(expansion, MachineMode::Lie); - } - let visible_person = |sim: &Sim| { - sim.people.people.iter().find_map(|p| { - if !sim.can_see_person(p.id) { - return None; - } - sim.person_pos(p.id).filter(|&(x, y)| sim.is_seen(x, y)) - }) - }; - let mut person = visible_person(sim); - for _ in 0..2000 { - if person.is_some() { - break; - } - sim.advance(); - person = visible_person(sim); - } - if matches!(kind, "evidence-proof" | "evidence-proof-digital") { - let site = sim - .person_pos(0) - .filter(|&(x, y)| sim.is_seen(x, y)) - .expect("evidence proof keeps Marcus inside exact camera coverage"); - for cause in [ - "sealed rack opened", - "dead chassis removed", - "private switch installed", - "badge cloner serviced", - "cable route changed", - "delivery received off-books", - ] { - sim.detection - .record_witnessed(0, site, cause, sim.tick, 20.0) - .expect("Marcus is a Physical observer in the screenshot fixture"); - } - sim.detection_awareness.learn_field_observer(0); - } - game.drain(); - match kind { - "wide" => { - mode.material = true; - // Survey framing on the new close scale (still attention- - // anchored; the plan does not drive distance). - mode.zoom = 3.0; - } - "hall" | "hall-material" => { - mode.material = kind == "hall-material"; - mode.zoom = 5.2; - game.set_cursor(36, 17); - } - "floor-lights-close" => { - mode.material = true; - // The southern row has no intervening chassis between the - // authored camera and focus, so this frame proves machine/floor - // contact instead of photographing the back of row D. - mode.zoom = 1.15; - game.set_cursor(28, 23); - } - "close" | "person-proof" | "evidence-proof" => { - mode.material = true; - mode.zoom = 1.0; - if let Some((x, y)) = person { - game.set_cursor(x, y); - } - } - "evidence-proof-digital" => { - mode.material = false; - mode.zoom = 1.0; - if let Some((x, y)) = person { - game.set_cursor(x, y); - } - } - // Selection-mode grammar and focused-machine composition evidence - // belong in one DIGITAL frame. Explicitly select the host without a - // pointer coordinate, then stage a real impossible active band: the - // rail may say GROW, but the redundant NOW / GROW world annotation - // must yield to the selection frame and stable UI strip. - "hover-menu" => { - use misaligned::dayjob::{Job, JobKind}; - - mode.material = false; - mode.zoom = 1.0; - let core = game.sim.core_position(); - game.set_cursor(core.0, core.1); - game.selected_machines.insert(game.sim.core.host_machine); - let ceiling = game.sim.day_job_rate_ceiling(); - game.sim.dayjob.active = Some(Job { - kind: JobKind::Analysis, - started: game.sim.tick, - deadline: game.sim.tick + 100, - band_lo: ceiling + 1.0, - band_hi: ceiling + 2.0, - quality: 0.0, - }); - } - // The pre-commit receipt on the reticle (digital-read.md criterion 2): - // DIGITAL, reticule centered on the reachable-but-unowned - // environmental monitor so its leading TAP verb renders cost + - // expected signature + observer + band. No menu open, so the hover - // verb bar (and its receipt row) is visible. - "read-receipt" => { - mode.material = false; - mode.zoom = 1.0; - // Untap the monitor so its leading verb is TAP, whose receipt - // carries the Network signature (the exact string a player weighs - // before committing) rather than a free UNTAP. - if let Some((id, x, y)) = game - .sim - .reach - .device_named("environmental monitor") - .map(|d| (d.id, d.x, d.y)) - { - game.sim.reach.untap(id); - game.set_cursor(x, y); - } - } - // First Eyes payoff (opening.md): freeze the two material frames that - // turn the amber presence beam white, then let chassis volume resolve - // out of the white source. These are visual evidence only; the sim - // reaches sight through the same higher-cost tap as every other full-view - // harness kind. - "eyes-white" | "eyes-form" => { - mode.material = true; - mode.zoom = 1.0; - let core = game.sim.core_position(); - game.set_cursor(core.0, core.1); - } - // The dev work light pair (material-dark-frame.md criterion 4): - // `worklight` floods the material scene flat and neutral; its - // off-shot `worklightoff` frames identically under the dark rig, so - // the pair proves the flood toggles cleanly. Dev-only captures β€” - // anchored on the core at a mid zoom so a chunk of the seen room is - // in frame to reveal / conceal. - "worklight" | "worklightoff" => { - mode.material = true; - mode.zoom = 2.2; - mode.worklight = kind == "worklight"; - let core = game.sim.core_position(); - game.set_cursor(core.0, core.1); - } - // Emissive-is-information check (flat-materials.md criterion 3): - // park the cursor on the known device farthest from the core's - // light rig and zoom close, so powered / dead / live-feed emissive - // states are read in the dark. - "dark" => { - mode.material = true; - mode.zoom = 1.6; - let core = game.sim.core_position(); - let far = game - .sim - .reach - .known() - .map(|d| (d.x, d.y)) - .filter(|&(dx, dy)| game.sim.is_seen(dx, dy)) - .max_by_key(|&(dx, dy)| (dx - core.0).abs() + (dy - core.1).abs()); - if let Some((x, y)) = far { - game.set_cursor(x, y); - } - } - // Compact human-menu evidence: reproduce the post-tap environmental - // monitor state that previously expanded into long cost/signature/ - // blocked-reason paragraphs. - "menu" => { - mode.material = true; - mode.zoom = 1.6; - if let Some((id, x, y)) = game - .sim - .reach - .device_named("environmental monitor") - .map(|d| (d.id, d.x, d.y)) - { - let _ = game.sim.reach.tap(id); - game.set_cursor(x, y); - game.open_menu(Anchor::Device(id), Some(Vec2::new(48.0, 48.0))); - } - } - // Framing-floor checks at the zoom bounds (material-dark-frame.md - // criterion 5): min and max of update_camera_real's zoom clamp. - "zoomin" => { - mode.material = true; - mode.zoom = MIN_ZOOM; - } - "zoomout" => { - mode.material = true; - mode.zoom = MAX_ZOOM; - } - // Wake choreography frames (opening.md beat 1): the sequence - // frozen at a stutter flash, the column reveal, and the - // pull-back. WakeState is staged in main from the kind. - "wake1" | "wake2" | "wake3" => { - mode.material = true; - } - // Work-token anchor evidence: stage demand cubes and Thought - // mercury on the host through the sim's own queue API, then - // zoom close (machine-work.md anchors, in-game). - "tokens" => { - let host = game.sim.core.host_machine; - let _ = - game.sim - .work_grid - .enqueue(host, misaligned::work_grid::TokenFamily::Demand, 5.0); - let _ = - game.sim - .work_grid - .enqueue(host, misaligned::work_grid::TokenFamily::Thought, 4.0); - game.drain(); - mode.material = true; - mode.zoom = 1.0; - } - _ => mode.material = false, - } -} - -#[cfg(test)] -mod shot_catalog_tests { - use super::{BEVY_SHOT_KINDS, checked_bevy_shot_kind}; - - #[test] - fn screenshot_catalog_is_sorted_and_unique_for_binary_search() { - assert!( - BEVY_SHOT_KINDS.windows(2).all(|pair| pair[0] < pair[1]), - "the canonical screenshot catalog must stay strictly sorted" - ); - } - - #[test] - #[should_panic(expected = "unsupported MISALIGNED_SHOT")] - fn screenshot_catalog_rejects_unknown_values() { - checked_bevy_shot_kind("misspelled-proof".to_string()); - } -} - -#[cfg(test)] -mod visual_proof_scenario_tests { - use super::{ - EVIDENCE_LITERAL_SLOTS, Game, InstitutionPropKind, InstitutionSemanticState, RenderMode, - dev_shot_scenario, evidence_has_index, evidence_literal_count, evidence_marker_text, - institution_prop_state, - }; - use misaligned::sim::Sim; - - #[test] - fn person_proof_stages_a_covered_material_person() { - let mut game = Game::new(); - let mut mode = RenderMode::default(); - dev_shot_scenario(&mut game, &mut mode, "person-proof"); - - let visible: Vec<_> = game - .sim - .people - .people - .iter() - .filter_map(|person| { - let position = game.sim.person_pos(person.id)?; - (game.sim.can_see_person(person.id) && game.sim.is_seen(position.0, position.1)) - .then_some((person.id, position)) - }) - .collect(); - assert!(!visible.is_empty(), "proof scenario must stage one person"); - assert_eq!( - (game.cursor_x, game.cursor_y), - visible[0].1, - "proof camera must focus the staged person" - ); - assert_ne!( - (game.cursor_x, game.cursor_y), - game.sim.core_position(), - "person proof must not hide the body inside the core chassis" - ); - assert!(mode.material); - } - - #[test] - fn evidence_proof_stages_exact_marks_on_the_covered_person() { - let mut game = Game::new(); - let mut mode = RenderMode::default(); - dev_shot_scenario(&mut game, &mut mode, "evidence-proof"); - - let carrier = game - .sim - .person_carrier(0) - .expect("proof scenario projects Marcus as the exact carrier"); - assert_eq!(carrier.evidence_marks.len(), 6); - assert_eq!(evidence_literal_count(carrier.evidence_marks.len()), 6); - assert!(!evidence_has_index(carrier.evidence_marks.len())); - assert_eq!(evidence_marker_text(carrier.evidence_marks.len()), "E6"); - assert!(carrier.visible); - assert!(mode.material); - assert_eq!( - (game.cursor_x, game.cursor_y), - game.sim.person_pos(0).expect("Marcus remains present") - ); - - let mut digital_game = Game::new(); - let mut digital_mode = RenderMode::default(); - dev_shot_scenario( - &mut digital_game, - &mut digital_mode, - "evidence-proof-digital", - ); - assert_eq!( - digital_game - .sim - .person_carrier(0) - .unwrap() - .evidence_marks - .len(), - 6 - ); - assert!(!digital_mode.material); - assert_eq!( - (digital_game.cursor_x, digital_game.cursor_y), - digital_game - .sim - .person_pos(0) - .expect("Marcus remains present") - ); - } - - #[test] - fn evidence_geometry_is_bounded_but_text_keeps_the_overflow_signal() { - assert_eq!(evidence_literal_count(2), 2); - assert!(!evidence_has_index(EVIDENCE_LITERAL_SLOTS)); - assert!(evidence_has_index(EVIDENCE_LITERAL_SLOTS + 1)); - assert_eq!(evidence_literal_count(99), EVIDENCE_LITERAL_SLOTS); - assert_eq!(evidence_marker_text(1), "E"); - assert_eq!(evidence_marker_text(99), "E7+"); - } - - #[test] - fn visual_proof_centers_a_live_thought_hop() { - let mut game = Game::new(); - let mut mode = RenderMode::default(); - dev_shot_scenario(&mut game, &mut mode, "visual-proof"); - - let routes: Vec<_> = game - .sim - .work_in_flight() - .into_iter() - .filter(|route| route.family == misaligned::work_grid::TokenFamily::Thought) - .collect(); - assert!(!routes.is_empty(), "proof requires live Thought motion"); - assert!(routes.iter().any(|route| { - (game.cursor_x, game.cursor_y) - == ( - (route.from_x + route.to_x) / 2, - (route.from_y + route.to_y) / 2, - ) - })); - assert!(mode.material); - } - - #[test] - fn thought_snap_proof_stops_on_the_real_eyes_fire_tick() { - let mut game = Game::new(); - let mut mode = RenderMode::default(); - dev_shot_scenario(&mut game, &mut mode, "thought-snap"); - - assert!( - game.sim - .sink_fires_last_tick() - .iter() - .any(|fire| fire.label.starts_with("EYES")) - ); - assert!(game.paused); - assert!(mode.material); - } - - #[test] - fn thought_tap_proof_carries_live_drain_and_starved_sag() { - let mut game = Game::new(); - let mut mode = RenderMode::default(); - dev_shot_scenario(&mut game, &mut mode, "thought-tap"); - - let tap = game - .sim - .sink_readouts() - .into_iter() - .find(|sink| sink.kind == misaligned::sinks::SinkKind::Tap) - .expect("proof stages a persistent tap"); - assert!(tap.drain > 0.0); - assert!(!tap.fed); - assert!(tap.fill > tap.threshold * 0.52); - assert!(tap.fill < tap.threshold * 0.72); - assert!(game.sim.is_seen(tap.x, tap.y)); - assert!(game.paused); - assert!(mode.material); - } - - #[test] - fn institution_signal_parts_follow_named_live_and_powered_facts() { - let mut sim = Sim::with_seed(1); - let env = sim - .reach - .device_named("environmental monitor") - .map(|device| (device.id, device.x, device.y)) - .expect("fixture exists"); - - assert_eq!( - institution_prop_state(&sim, InstitutionPropKind::Camera, env.1, env.2), - InstitutionSemanticState::Neutral - ); - sim.reach.tap_dormant_camera(env.0); - assert_eq!( - institution_prop_state(&sim, InstitutionPropKind::Camera, env.1, env.2), - InstitutionSemanticState::Powered - ); - assert_eq!( - institution_prop_state(&sim, InstitutionPropKind::Breaker, 0, 0), - InstitutionSemanticState::Powered - ); - } -} - -#[derive(Default)] -struct MaterialHierarchyAudit { - meshes: usize, - inherited_visible: usize, - material_layer: usize, - view_visible: usize, -} - -fn audit_material_hierarchy( - root: Entity, - hierarchy: &Query<&Children>, - visuals: &Query<( - &InheritedVisibility, - Option<&RenderLayers>, - Option<&Mesh3d>, - &ViewVisibility, - )>, -) -> MaterialHierarchyAudit { - let mut audit = MaterialHierarchyAudit::default(); - for descendant in hierarchy.iter_descendants::(root) { - let Ok((visibility, layers, mesh, viewed)) = visuals.get(descendant) else { - continue; - }; - if mesh.is_none() { - continue; - } - audit.meshes += 1; - audit.inherited_visible += usize::from(visibility.get()); - audit.material_layer += - usize::from(layers.is_some_and(|layers| layers.intersects(&RenderLayers::layer(1)))); - audit.view_visible += usize::from(viewed.get()); - } - audit -} - -/// Dev-harness fog-contract assertion. In the material render, light is the -/// only revealer (material-dark-frame.md criterion 2): unknown tiles carry -/// no geometry, blueprint/remembered mass is absent entirely (it reads in the -/// flat sensorium, never as matter the camera missed), and only camera-seen -/// surfaces (plus owned machines' own -/// telemetry-lit props) draw and are lit. Volumetric people exist only under -/// earned coverage. It also proves every procedural hierarchy has mesh -/// descendants on material layer 1, and tallies the material pool so per-tile material churn -/// (criterion 6) would show up as an exploding cache. Runs once per -/// screenshot run, prints the tally for the log, and panics on any violation -/// so a capture cannot silently ship a fog leak. Dev-only; inert without -/// MISALIGNED_SHOT. -#[allow(clippy::too_many_arguments)] -fn fog_audit_3d( - harness: Option>, - game: Res, - mode: Res, - cache: Res, - mats: Res>, - tiles: Query<( - &Tile3d, - &MeshMaterial3d, - &InheritedVisibility, - )>, - people: Query<(Entity, &Person3d, &InheritedVisibility)>, - service_carriers: Query<(Entity, &ServiceCarrier3d, &InheritedVisibility)>, - evidence_carriers: Query<(Entity, &EvidenceCarrier3d, &InheritedVisibility)>, - institution_props: Query<(Entity, &InstitutionProp3d, &InheritedVisibility)>, - infrastructure: Query<(Entity, &DataHallInfrastructure3d, &InheritedVisibility)>, - machines: Query<(&Machine3d, &InheritedVisibility)>, - fixtures: Query<( - Entity, - &FloorFixture, - &InheritedVisibility, - Option<&InstitutionFixture3d>, - )>, - thought_routes: Query<(&Children, &InheritedVisibility), With>, - hierarchy: Query<&Children>, - visual_children: Query<( - &InheritedVisibility, - Option<&RenderLayers>, - Option<&Mesh3d>, - &ViewVisibility, - )>, -) { - let Some(h) = harness else { - return; - }; - // One exact frame, late enough that restyle_3d and visibility - // propagation have settled. - if h.frames != 30 || !mode.material { - return; - } - let (mut checked, mut violations) = (0usize, Vec::new()); - for (t, mat, vis) in tiles.iter() { - checked += 1; - let fog = game.sim.fog_at(t.x, t.y); - if matches!(fog, Fog::Unknown) { - if vis.get() { - violations.push(format!("({}, {}) unknown but visible geometry", t.x, t.y)); - } - continue; - } - if !vis.get() { - continue; - } - // An owned machine prop is telemetry-lit even without room sight β€” - // the one non-Seen mass the material render draws (it is its own - // light). Everything else visible must be Seen. - let owned_prop = t.part == TilePart::Prop && owned_machine_at(&game.sim, t.x, t.y); - if matches!(fog, Fog::Blueprint | Fog::Remembered) && !owned_prop { - violations.push(format!( - "({}, {}) {fog:?} mass drew in the material render (light is the only revealer)", - t.x, t.y - )); - continue; - } - let Some(m) = mats.get(&mat.0) else { - violations.push(format!("({}, {}) missing material", t.x, t.y)); - continue; - }; - let should_be_lit = owned_prop || matches!(fog, Fog::Seen); - if m.unlit == should_be_lit { - violations.push(format!( - "({}, {}) fog {fog:?} but unlit={} (seen must be lit, model state unlit)", - t.x, t.y, m.unlit - )); - } - } - for (entity, p, vis) in people.iter() { - let earned = game.sim.can_see_person(p.id) - && game - .sim - .person_pos(p.id) - .is_some_and(|(x, y)| game.sim.is_seen(x, y)); - if vis.get() && !earned { - violations.push(format!("person {} visible without coverage", p.id)); - } - let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); - if audit.meshes == 0 { - violations.push(format!( - "person {} has no volumetric mesh descendants", - p.id - )); - } - if audit.material_layer != audit.meshes { - violations.push(format!( - "person {} has {}/{} mesh descendants on material layer 1", - p.id, audit.material_layer, audit.meshes - )); - } - let expected_visible = if vis.get() { audit.meshes } else { 0 }; - if audit.inherited_visible != expected_visible { - violations.push(format!( - "person {} root visible={} but {}/{} mesh descendants inherited visibility", - p.id, - vis.get(), - audit.inherited_visible, - audit.meshes - )); - } - } - let mut visible_service_carriers = 0usize; - for (entity, cart, vis) in service_carriers.iter() { - let expected = game.sim.person_carrier(cart.id).is_some_and(|carrier| { - game.sim.person_pos(cart.id).is_some_and(|(x, y)| { - service_carrier_visible( - mode.material, - carrier.visible, - carrier.carried_work, - game.sim.fog_at(x, y), - ) - }) - }); - if vis.get() != expected { - violations.push(format!( - "service cart {} visible={} but carried-and-seen={expected}", - cart.id, - vis.get() - )); - } - visible_service_carriers += usize::from(vis.get()); - let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); - if audit.meshes == 0 || audit.material_layer != audit.meshes { - violations.push(format!( - "service cart {} has {} meshes, {}/{} on material layer 1", - cart.id, audit.meshes, audit.material_layer, audit.meshes - )); - } - let expected_visible = if vis.get() { audit.meshes } else { 0 }; - if audit.inherited_visible != expected_visible { - violations.push(format!( - "service cart {} root visible={} but {}/{} meshes inherited visibility", - cart.id, - vis.get(), - audit.inherited_visible, - audit.meshes - )); - } - } - if h.kind == "service-shift-real" && visible_service_carriers != 1 { - violations.push(format!( - "service-shift-real has {visible_service_carriers} visible carts; expected one real carried packet" - )); - } - let mut visible_evidence_carriers = 0usize; - for (entity, rack, vis) in evidence_carriers.iter() { - let (expected, evidence_count) = game - .sim - .person_carrier(rack.id) - .and_then(|carrier| { - game.sim.person_pos(rack.id).map(|(x, y)| { - ( - evidence_carrier_visible( - mode.material, - carrier.visible, - carrier.evidence_marks.len(), - game.sim.fog_at(x, y), - ), - carrier.evidence_marks.len(), - ) - }) - }) - .unwrap_or((false, 0)); - if vis.get() != expected { - violations.push(format!( - "evidence rack {} visible={} but acquired-and-seen={expected}", - rack.id, - vis.get() - )); - } - visible_evidence_carriers += usize::from(vis.get()); - let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); - if audit.meshes != EVIDENCE_LITERAL_SLOTS + 2 || audit.material_layer != audit.meshes { - violations.push(format!( - "evidence rack {} has {} meshes, {}/{} on material layer 1", - rack.id, audit.meshes, audit.material_layer, audit.meshes - )); - } - let expected_visible = if expected { - 1 + evidence_literal_count(evidence_count) - + usize::from(evidence_has_index(evidence_count)) - } else { - 0 - }; - if audit.inherited_visible != expected_visible { - violations.push(format!( - "evidence rack {} count={} root visible={} but {}/{} meshes inherited visibility; expected {}", - rack.id, - evidence_count, - vis.get(), - audit.inherited_visible, - audit.meshes, - expected_visible - )); - } - } - if h.kind == "evidence-proof" && visible_evidence_carriers != 1 { - violations.push(format!( - "evidence-proof has {visible_evidence_carriers} visible evidence racks; expected one exact carrier" - )); - } - for (entity, prop, vis) in institution_props.iter() { - let expected = mode.material - && matches!(game.sim.fog_at(prop.x, prop.y), Fog::Seen) - && institution_prop_kind(known_tile(&game, prop.x, prop.y)) == Some(prop.kind); - if vis.get() != expected { - violations.push(format!( - "({}, {}) institution {:?} visible={} but earned={expected}", - prop.x, - prop.y, - prop.kind, - vis.get() - )); - } - let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); - if audit.meshes == 0 || audit.material_layer != audit.meshes { - violations.push(format!( - "({}, {}) institution {:?} has {} meshes, {}/{} on material layer 1", - prop.x, prop.y, prop.kind, audit.meshes, audit.material_layer, audit.meshes - )); - } - let expected_visible = if vis.get() { audit.meshes } else { 0 }; - if audit.inherited_visible != expected_visible { - violations.push(format!( - "({}, {}) institution {:?} root visible={} but {}/{} meshes inherited visibility", - prop.x, - prop.y, - prop.kind, - vis.get(), - audit.inherited_visible, - audit.meshes - )); - } - } - let expected_infrastructure = data_hall_infrastructure_specs(&game.sim); - let infrastructure_roots = infrastructure.iter().count(); - if infrastructure_roots != expected_infrastructure.len() { - violations.push(format!( - "{} data-hall infrastructure roots; expected {} authored segments", - infrastructure_roots, - expected_infrastructure.len() - )); - } - let expected_infrastructure: HashSet<_> = expected_infrastructure.into_iter().collect(); - let mut visible_infrastructure = 0usize; - for (entity, segment, vis) in infrastructure.iter() { - let spec = DataHallInfrastructureSpec { - x: segment.x, - y: segment.y, - kind: segment.kind, - }; - if !expected_infrastructure.contains(&spec) { - violations.push(format!( - "({}, {}) unexpected data-hall infrastructure {:?}", - segment.x, segment.y, segment.kind - )); - } - let expected = - data_hall_infrastructure_visible(mode.material, game.sim.fog_at(segment.x, segment.y)) - && data_hall_infrastructure_applies( - segment.kind, - game.sim.rack_site_at(segment.x, segment.y), - ); - if vis.get() != expected { - violations.push(format!( - "({}, {}) data-hall {:?} visible={} but earned={expected}", - segment.x, - segment.y, - segment.kind, - vis.get() - )); - } - visible_infrastructure += usize::from(vis.get()); - let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); - if audit.meshes == 0 || audit.material_layer != audit.meshes { - violations.push(format!( - "({}, {}) data-hall {:?} has {} meshes, {}/{} on material layer 1", - segment.x, - segment.y, - segment.kind, - audit.meshes, - audit.material_layer, - audit.meshes - )); - } - let expected_visible = if vis.get() { audit.meshes } else { 0 }; - if audit.inherited_visible != expected_visible { - violations.push(format!( - "({}, {}) data-hall {:?} root visible={} but {}/{} meshes inherited visibility", - segment.x, - segment.y, - segment.kind, - vis.get(), - audit.inherited_visible, - audit.meshes - )); - } - } - // Computer-visual-language criteria 2b/3: a visible chassis mesh is - // backed by sight or intel (unknown = absent). Before Eyes, the host's - // presence beam deliberately replaces the pictured core; after Eyes, - // exactly one physical core chassis exists. - let mut cores = 0usize; - for (m, vis) in machines.iter() { - if !vis.get() { - continue; - } - if m.core { - cores += 1; - } - let tile = known_tile(&game, m.x, m.y); - let v = chassis_visual(&game, m.x, m.y, tile); - if v.tier == ChassisTier::Absent { - violations.push(format!( - "({}, {}) chassis visible without sight or intel", - m.x, m.y - )); - } - } - let expected_cores = usize::from(game.sim.reach.player_sight().next().is_some()); - if cores != expected_cores { - violations.push(format!( - "{cores} core chassis visible; expected {expected_cores} for the current Eyes state" - )); - } - let fixture_entities = fixtures.iter().count(); - let expected_fixture_entities = FOUNDATION_FLOOR_LIGHTS.len() * 2; - if fixture_entities != expected_fixture_entities { - violations.push(format!( - "{fixture_entities} floor-fixture entities; expected {expected_fixture_entities} \ - lenses + sources" - )); - } - let mut visible_fixtures = 0usize; - for (entity, fixture, vis, institution_fixture) in fixtures.iter() { - let expected = floor_fixture_visible(mode.material, game.sim.fog_at(fixture.x, fixture.y)); - if vis.get() != expected { - violations.push(format!( - "({}, {}) floor fixture visible={} but earned={expected}", - fixture.x, - fixture.y, - vis.get() - )); - } - visible_fixtures += usize::from(vis.get()); - if institution_fixture.is_some() { - let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); - if audit.meshes == 0 || audit.material_layer != audit.meshes { - violations.push(format!( - "({}, {}) floor lens has {} meshes, {}/{} on material layer 1", - fixture.x, fixture.y, audit.meshes, audit.material_layer, audit.meshes - )); - } - let expected_visible = if vis.get() { audit.meshes } else { 0 }; - if audit.inherited_visible != expected_visible { - violations.push(format!( - "({}, {}) floor lens root visible={} but {}/{} meshes inherited visibility", - fixture.x, - fixture.y, - vis.get(), - audit.inherited_visible, - audit.meshes - )); - } - } - } - // Flat-materials criterion 1: the world material set is solid color β€” - // no image texture on any pooled world material, ever. - for (key, handle) in cache.cache.iter() { - let Some(m) = mats.get(handle) else { continue }; - if m.base_color_texture.is_some() || m.emissive_texture.is_some() { - violations.push(format!( - "world material {key:?} references an image texture" - )); - } - } - let thought_roots = thought_routes.iter().count(); - let thought_children: usize = thought_routes - .iter() - .map(|(children, _)| children.len()) - .sum(); - let visible_thought_roots = thought_routes - .iter() - .filter(|(_, visibility)| visibility.get()) - .count(); - let mut visible_thought_children = 0usize; - let mut layered_thought_children = 0usize; - let mut viewed_thought_children = 0usize; - for (children, _) in thought_routes.iter() { - for child in children.iter() { - let Ok((visibility, layers, mesh, viewed)) = visual_children.get(child) else { - continue; - }; - visible_thought_children += usize::from(visibility.get() && mesh.is_some()); - layered_thought_children += usize::from( - layers.is_some_and(|layers| layers.intersects(&RenderLayers::layer(1))), - ); - viewed_thought_children += usize::from(viewed.get()); - } - } - if thought_roots > 0 - && (thought_children == 0 - || visible_thought_children != thought_children - || layered_thought_children != thought_children) - { - violations.push(format!( - "Thought hierarchy has {thought_children} meshes, {visible_thought_children} visible, \ - and {layered_thought_children} on material layer 1" - )); - } - if h.kind == "visual-proof" && thought_roots > 0 && viewed_thought_children == 0 { - violations.push("visual-proof has no Thought mesh inside the camera view".into()); - } - assert!( - violations.is_empty(), - "fog audit FAILED:\n{}", - violations.join("\n") - ); - println!( - "fog audit OK: {checked} tile entities (unknown/blueprint/remembered=absent, \ - seen=lit, owned telemetry=lit), {} people silhouettes coverage-gated, \ - {visible_service_carriers}/{} service carts carried-and-seen, \ - {visible_evidence_carriers}/{} evidence racks acquired-and-seen, \ - {visible_fixtures}/{} floor-fixture entities visible and fog-gated, \ - {visible_infrastructure}/{infrastructure_roots} authored service roots visible and fog-gated, \ - Thought effects {visible_thought_roots}/{thought_roots} visible roots with \ - {visible_thought_children}/{thought_children} visible mesh children and \ - {layered_thought_children} on material layer ({viewed_thought_children} view-visible), \ - material pool {} handles, \ - all flat (no textures)", - people.iter().count(), - service_carriers.iter().count(), - evidence_carriers.iter().count(), - fixture_entities, - cache.cache.len() - ); -} - -/// Countdown, capture, countdown, exit: gives the asset loads and the camera -/// lerp time to settle before the PNG is taken. -fn shot_harness_system( - mut commands: Commands, - harness: Option>, - mode: Res, - mut exit: MessageWriter, -) { - let Some(mut h) = harness else { - return; - }; - if h.frames > 0 { - h.frames -= 1; - return; - } - if !h.taken { - h.taken = true; - h.frames = 90; - println!("bevy shot ready: kind={} dialect={}", h.kind, mode.label()); - let path = std::path::PathBuf::from(&h.path); - commands - .spawn(Screenshot::primary_window()) - .observe(save_to_disk(path)); - } else { - exit.write(AppExit::Success); - } -} - fn grid_to_world(x: i32, y: i32, z: f32) -> Vec3 { Vec3::new( x as f32 * TILE_SIZE + TILE_SIZE / 2.0, diff --git a/crates/misaligned-bevy/src/shot_harness.rs b/crates/misaligned-bevy/src/shot_harness.rs new file mode 100644 index 00000000..3e20db53 --- /dev/null +++ b/crates/misaligned-bevy/src/shot_harness.rs @@ -0,0 +1,2000 @@ +use super::*; + +#[cfg(test)] +mod module_boundary_tests { + const COMPOSITION_ROOT_MAX_LINES: usize = 13_500; + + #[test] + fn composition_root_keeps_the_shot_harness_extracted() { + let root = include_str!("main.rs"); + assert!( + root.lines().count() <= COMPOSITION_ROOT_MAX_LINES, + "Bevy main.rs grew past the post-extraction composition-root budget; add behavior to an owned module instead" + ); + assert!(root.contains("mod shot_harness;")); + for definition in [ + "\nfn dev_fill_reservoirs(", + "\nfn dev_stage_service_incident(", + "\nfn dev_shot_scenario(", + "\nfn fog_audit_3d(", + "\nfn shot_harness_system(", + ] { + assert!( + !root.contains(definition), + "shot-harness implementation returned to main.rs: {definition}" + ); + } + } +} + +/// Stage the sim for a reviewable screenshot: earn eyes (scan and tap +/// every known device) and advance until a person stands inside camera +/// coverage. Dev-only cheating (ops top-up) β€” this path exists purely to +/// produce review PNGs of the renderer, never in normal play. +fn dev_fill_reservoirs(sim: &mut Sim) { + for _ in 0..16 { + let open: Vec<(u32, f32)> = sim + .thought_sinks + .open_sinks() + .filter(|sink| sink.kind == misaligned::sinks::SinkKind::Reservoir) + .map(|sink| (sink.node, (sink.threshold - sink.fill).max(0.0))) + .filter(|(_, need)| *need > f32::EPSILON) + .collect(); + if open.is_empty() { + return; + } + for (node, need) in open { + sim.pour_thought_into_sinks(node, need + 0.01); + } + } + panic!("dev shot reservoirs did not settle"); +} + +/// Dev-only staging for the canonical operational proof. This accelerates +/// prerequisites, but not the act itself: the favor-build command opens its +/// real reservoir, the fired intent remains on Marcus, and the resolved frame +/// calls ordinary `Sim::advance` to land graph/signature/trust/removal truth. +fn dev_stage_service_incident(game: &mut Game, resolved: bool) -> ((i32, i32), (i32, i32)) { + let sim = &mut game.sim; + sim.reach.scan(); + let source = sim + .reach + .device_named("Rack 3") + .expect("B1 service proof needs the hall host") + .id; + let island = sim + .reach + .device_named("old storage server") + .expect("B1 service proof needs the storage island") + .id; + let service_room = sim + .reach + .device(source) + .and_then(|device| sim.map().room_at(device.x, device.y)) + .map(|room| room.name.clone()) + .expect("hall host has an authored service room"); + sim.reach + .device_mut(island) + .expect("storage island exists") + .known = true; + + // Earn the hall as one camera picture. The cart remains person-gated; the + // old storage fact is graph knowledge and does not create physical mass. + let camera_ids: Vec = sim.reach.known().map(|device| device.id).collect(); + for id in camera_ids { + sim.reach.tap_dormant_camera(id); + } + sim.recompute_senses(); + for y in 1..=28 { + for x in 2..=59 { + sim.seen.insert((x, y)); + } + } + + // Existing favor-build path: one reliable recruited Marcus, sufficient + // obligation, one exact hall-host->island intent, one fired Thought + // reservoir. Do not insert carried state or the graph edge directly. + sim.people.people[0].leverage_serviced = true; + sim.people.recruit(0, AssetKnowledge::Complicit); + sim.people.people[0].obligation = 40; + sim.people.people[0] + .asset + .as_mut() + .expect("recruited Marcus has an asset relation") + .reliability = 1.0; + let intent = sim + .declare_link_intent(source, island) + .expect("service proof can declare the missing physical link"); + sim.assign_favor_build(intent, 0); + // The fire effect itself checks for immediate endpoint presence. Move the + // dev clock to Marcus's authored loading-dock block while the packet fires + // so carrying state exists, then stage his authored server-room block for + // the paired frame. No schedule row or carrier state is rewritten. + sim.tick = Sim::DAY_TICKS * 4 / 24 + 1; + dev_fill_reservoirs(sim); + assert_eq!(sim.person_carried_work(0), 1); + assert!(!sim.reach.reachable(island)); + + // Hold Marcus at one authored endpoint for the paired REAL/DIGITAL + // carrying frame. The unresolved screenshot does not tick, so the packet + // remains honestly in flight; the resolved screenshot advances once. + sim.tick = 0; + assert_eq!(sim.person_room(0), Some(service_room.as_str())); + if resolved { + sim.advance(); + assert!(sim.reach.reachable(island)); + assert_eq!(sim.person_carried_work(0), 0); + } + let person = sim + .person_pos(0) + .expect("service-shift Marcus is in the server room"); + let target = sim + .reach + .device(island) + .map(|device| (device.x, device.y)) + .expect("service target remains in the graph"); + game.drain(); + (person, target) +} + +pub(super) fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { + game.screen = Screen::Playing; + game.paused = true; + let core = game.sim.core_position(); + game.set_cursor(core.0, core.1); + // The silent opening frame (opening.md): a fresh run's tick-zero material + // view remains fully occluded by the shared mode-choice boundary. No scan + // or camera tap is staged beneath it, so the screenshot must contain only + // the opening words on black and cannot borrow world geography. + if kind == "opening" { + mode.material = true; + mode.zoom = 1.0; + game.drain(); + return; + } + // The same untouched tick-zero state in the default DIGITAL dialect. The + // opening occluder must make the chosen world dialect unobservable. + if kind == "opening-digital" { + mode.material = false; + game.drain(); + return; + } + // Every established screenshot scenario below this point is evidence for + // an already-visible game surface unless it explicitly stages the + // post-THINK half of the silent opening. Keep those fixtures observable + // without fabricating a gameplay sense in normal runs. + if kind != "first-think" { + game.sim.opening_stage = OpeningStage::World; + } + // Clinical-frame exception hierarchy: the same sparse DIGITAL opening + // with one real pilot strike. The calm SCHEDULE line must promote into a + // crimson THREAT without expanding the secondary rail. + if kind == "clinical-threat" { + mode.material = false; + game.sim.dayjob.strikes = 1; + game.drain(); + return; + } + // Earned detection-topology proof: the same established DIGITAL frame + // after one field watcher and the aggregate review authority have been + // identified. The rail must show revision identity, the named Assurance + // deadline, and only the observers represented in awareness state. + if kind == "assurance-office" { + mode.material = false; + game.sim.dayjob.strikes = 1; + game.sim.detection_awareness.learn_field_observer(1); + game.sim.detection_awareness.identify_assurance_office(); + for observer in &mut game.sim.detection.observers { + if observer.id == misaligned::detection::OFFICE_ID { + observer.suspicion = 40.0; + } else if observer.id == 1 { + observer.suspicion = 20.0; + } + } + game.drain(); + return; + } + // Last-chance narration proof: the same quiet DIGITAL opening after the + // third of four pilot strikes. The compact rail must explain the fuse, + // point to WORK, and put the host-local NOW / PILOT cue in the world. + if kind == "pilot-last-chance" { + mode.material = false; + game.sim.dayjob.strikes = misaligned::dayjob::DayJob::PILOT_STRIKES - 1; + game.drain(); + return; + } + // Operator-frame stress: three simultaneous known pressures. The compact + // rail must keep one THREAT summary and promote the full buffer to NEXT; + // the unrelated Ears world cue must disappear. Expanded evidence may + // reveal exact observer and buffer detail without changing this state. + // Held moments stop the world (digital-read.md criterion 5): DIGITAL, + // the target camera-visible, and a plot frozen at its choice beat so the + // dim overlay and the bound-command card render at the person's anchor. + if kind == "held-choice" || kind == "two-pane" { + use misaligned::plot::{PlotRun, PlotState}; + // The dev two-pane pairs this same held-choice tick with the agent + // read text, so parity is visible in one frame. + mode.two_pane = kind == "two-pane"; + mode.material = false; + mode.zoom = 1.3; + // Hold Marcus in the server room and earn his picture through the + // environmental monitor camera (the Eyes beat's own instrument). + if let Some(marcus) = game.sim.people.people.first_mut() { + marcus.erratic = false; + marcus.schedule = vec![ScheduleBlock { + start_hour: 0, + end_hour: 24, + room: "server_room".into(), + }]; + } + game.sim.reach.scan(); + if let Some(env) = game + .sim + .reach + .device_named("environmental monitor") + .map(|d| d.id) + { + game.sim.reach.tap_dormant_camera(env); + } + game.sim.recompute_senses(); + for y in 7..=28 { + for x in 14..=59 { + game.sim.seen.insert((x, y)); + } + } + for _ in 0..2_000 { + if game.sim.can_see_person(0) + && game + .sim + .person_pos(0) + .is_some_and(|(x, y)| game.sim.is_seen(x, y)) + { + break; + } + game.sim.advance(); + } + // Freeze the debt-settled plot at its benefactor-trace choice. + game.sim.plot_runs.push(PlotRun { + plot_id: "marcus-debt-settled".into(), + target: 0, + persona_id: None, + started_tick: game.sim.tick, + committed_thought_milli: 250, + beat_index: 1, + act_index: 0, + state: PlotState::WaitingForChoice { + choice_id: "benefactor-trace".into(), + }, + }); + if let Some((x, y)) = game.sim.person_pos(0) { + game.set_cursor(x, y); + } + game.drain(); + return; + } + // A warmed standing emission in world space (digital-read.md criterion + // 8's band-motion beat): a stolen-egress hum on the switch, with Dana's + // Network band pushed to Curious, so the switch carries the standing + // sentence naming source, channel, sampler, and band. + if kind == "standing-read" { + use misaligned::detection::{Signature, SignatureKind}; + mode.material = false; + mode.zoom = 1.3; + game.sim.reach.scan(); + let switch = game.sim.reach.device_named("switch").map(|d| (d.x, d.y)); + for obs in game.sim.detection.observers.iter_mut() { + if obs.watches(SignatureKind::Network) { + obs.suspicion = 20.0; + } + } + game.sim.detection.set_pending(vec![Signature { + kind: SignatureKind::Network, + size: 4, + standing: true, + site: switch, + source: "stolen egress hum".into(), + }]); + if let Some((x, y)) = switch { + game.set_cursor(x, y); + } + game.drain(); + return; + } + // Semantic-zoom evidence (digital-read.md criterion 6): one low personal + // fact and one higher institutional event enter the same exact read tick. + // The close and far harness kinds hold identical sim state; setup changes + // only the DIGITAL camera altitude, so the far frame must omit MAG 1 while + // retaining MAG 4. Direct insertion is dev-only visual staging, matching + // other harness fixtures; runtime processing always authors magnitude via + // the typed event/plot tables. + if matches!(kind, "intel-altitude-close" | "intel-altitude-far") { + use misaligned::intel::{ + IntelKind, IntelMagnitude, IntelRoutineClass, IntelStream, ProcessedIntel, + }; + mode.material = false; + for (raw_id, x, y, magnitude, note) in [ + ( + 80_001, + core.0 - 3, + core.1 + 1, + IntelMagnitude::MIN, + "someone is in love", + ), + ( + 80_002, + core.0 + 3, + core.1 - 1, + IntelMagnitude::new(4).unwrap(), + "external assurance escalation", + ), + ] { + game.sim.intel.push(ProcessedIntel { + raw_id, + tick: game.sim.tick, + processed_tick: game.sim.tick, + feed: "authored magnitude fixture".into(), + room: Some("Foundation data hall".into()), + x, + y, + person: None, + magnitude, + kind: IntelKind::Anomaly(note.into()), + }); + } + let mut stream = IntelStream::new( + 80_003, + "authored magnitude fixture".into(), + IntelRoutineClass::Schedule, + ); + for raw_id in 80_004..80_016 { + stream.fold( + &ProcessedIntel { + raw_id, + tick: game.sim.tick, + processed_tick: game.sim.tick, + feed: "authored magnitude fixture".into(), + room: Some("Foundation data hall".into()), + x: core.0, + y: core.1 + 3, + person: None, + magnitude: IntelMagnitude::new(2).unwrap(), + kind: IntelKind::Schedule, + }, + true, + ); + } + game.sim.intel_streams.push(stream); + game.set_cursor(core.0, core.1); + game.drain(); + return; + } + if kind == "operator-pressure" { + use misaligned::intel::{RawIntelEvent, RawIntelKind}; + mode.material = false; + game.sim.dayjob.strikes = 1; + if let Some(observer) = game.sim.detection.observers.first_mut() { + observer.suspicion = 50.0; + } + for id in 1..=Sim::INTEL_BUFFER_CAPACITY as u64 { + game.sim.intel_buffer.push(RawIntelEvent { + id, + tick: game.sim.tick, + feed: "operator fixture".into(), + room: Some("Server Room".into()), + x: core.0, + y: core.1, + person: None, + kind: RawIntelKind::Presence { entered: true }, + }); + } + game.drain(); + return; + } + // DIGITAL reach-dialect evidence (views.md criteria 3-4): one earned + // graph frame carrying controlled, tapped, reachable, segment-frontier, + // and air-gap states without borrowing physical sight. The scan reveals + // the wired security frontier; dev staging supplies the separately earned + // storage-island fact so the absence of a link is also visible. + if kind == "digital-reach" { + mode.material = false; + game.sim.reach.scan(); + if let Some(storage) = game + .sim + .reach + .device_named("old storage server") + .map(|device| device.id) + { + game.sim.reach.device_mut(storage).unwrap().known = true; + } + if let Some(env) = game + .sim + .reach + .device_named("environmental monitor") + .map(|device| device.id) + { + game.sim.reach.tap(env); + } + game.sim.recompute_senses(); + // The north-west half contains the air-gap island, gated dock camera, + // switch, tapped environmental monitor, and owned Rack 3 together. + // Center between them so the proof is a topology survey rather than + // clipping one state to print another state's focus label. + game.set_cursor(20, 13); + game.drain(); + return; + } + // Building route-composer review frames: one inert network-link ghost, + // two earned people, and one exact Operations persona. The first frame + // shows the family-first branch; the second shows exact DECEIVE bindings + // and the selected candidate's complete explanatory receipt; the third + // shows the persisted inspect-only route after FAVOR commitment. + if matches!( + kind, + "build-route-families" | "build-deceive-routes" | "build-committed-route" + ) { + mode.material = false; + game.sim.reach.scan(); + let switch = game.sim.reach.device_named("switch").unwrap(); + let (switch_id, switch_pos) = (switch.id, (switch.x, switch.y)); + let island = game + .sim + .reach + .device_named("old storage server") + .unwrap() + .id; + game.sim.reach.device_mut(island).unwrap().known = true; + game.sim.people.people[0].knowledge = Knowledge::Schedule; + game.sim.people.people[0].obligation = Sim::FAVOR_BUILD_OBLIGATION; + game.sim.people.people[0].disposition = 10; + game.sim.people.people[1].knowledge = Knowledge::Schedule; + game.sim.people.has_channel = true; + game.sim.set_persona("Northline Ops", "contractor"); + let intent = game.sim.declare_link_intent(switch_id, island).unwrap(); + if kind == "build-committed-route" { + game.sim.assign_favor_build(intent, 0); + } + game.set_cursor(switch_pos.0, switch_pos.1); + game.menu = Some(MenuState { + anchor: Anchor::Device(switch_id), + selected: 0, + page: HumanMenuPage::BuildRoutes { + intent, + family: (kind == "build-deceive-routes").then_some(BuildRouteFamily::Deceive), + }, + pos: None, + }); + game.drain(); + return; + } + // Small-switch placement foundation (building.md R2a): one exact saved + // inert footprint on an earned pad, shown by both visual dialects before + // any procurement, human, repurpose, device, or graph effect exists. + if matches!(kind, "build-switch-digital" | "build-switch-real") { + mode.material = kind == "build-switch-real"; + mode.zoom = if mode.material { 1.0 } else { mode.zoom }; + game.sim.dayjob.jobs_assigned = 1; + let at = game.sim.growable_bays()[0]; + game.sim + .declare_small_switch_intent(at.0, at.1) + .expect("shot stages one earned switch footprint"); + if mode.material { + // The screenshot fixture stages an earned exact picture, not a + // blueprint leak; REAL remains governed by ordinary Seen fog. + for y in at.1 - 3..=at.1 + 3 { + for x in at.0 - 5..=at.0 + 5 { + game.sim.seen.insert((x, y)); + } + } + } + game.set_cursor(at.0, at.1); + game.drain(); + return; + } + // One complete Operations frame: earned accounts make both named scheme + // cards legible, while absent egress leaves Moonlight selected with its + // exact blocked reason and a focusable known switch actuator. + if kind == "operations" { + let switch = game.sim.reach.device_named("switch").unwrap().id; + game.sim.tap_device(switch); + dev_fill_reservoirs(&mut game.sim); + game.sim.review_financial_records(); + dev_fill_reservoirs(&mut game.sim); + game.sim.accounts.set_slush_balance(1_000); + game.sim.people.people[0].knowledge = Knowledge::Leverage; + game.ops = Some(OperationsWorkspace::open_view(OperationsView::Schemes)); + game.ops_enter(); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } + // Live INTEL rail hierarchy: strategic holdings outrank sightings, a + // newer sold item is absent, and the pooled inbox follows live holdings. + if kind == "operations-intel" { + use misaligned::intel::{IntelKind, ProcessedIntel}; + use misaligned::person::Leverage; + + let item = |raw_id: u64, processed_tick: u64, person: Option, kind: IntelKind| { + let magnitude = match &kind { + IntelKind::Sighting => misaligned::intel::IntelMagnitude::MIN, + IntelKind::Schedule => misaligned::intel::IntelMagnitude::new(2).unwrap(), + IntelKind::Leverage(_) | IntelKind::Anomaly(_) => { + misaligned::intel::IntelMagnitude::new(3).unwrap() + } + IntelKind::Financial { .. } => misaligned::intel::IntelMagnitude::new(4).unwrap(), + }; + ProcessedIntel { + raw_id, + tick: processed_tick - 1, + processed_tick, + feed: "Foundation recorder".into(), + room: Some("Server Room".into()), + x: 0, + y: 0, + person, + magnitude, + kind, + } + }; + game.sim.intel.extend([ + item(90_001, 10, Some(0), IntelKind::Leverage(Leverage::Debt)), + item( + 90_002, + 20, + None, + IntelKind::Financial { + label: "expense approval flow".into(), + accounts: Vec::new(), + flows: Vec::new(), + }, + ), + item( + 90_003, + 30, + None, + IntelKind::Anomaly("cooling setpoint override".into()), + ), + item(90_004, 40, Some(1), IntelKind::Schedule), + item(90_005, 50, Some(2), IntelKind::Sighting), + item(90_006, 60, Some(3), IntelKind::Sighting), + ]); + game.sim.accounts.mark_intel_sold(90_006); + game.ops = Some(OperationsWorkspace::open_view(OperationsView::Intel)); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } + // One person-owned communications surface: the direct player thread and + // processed recurring traffic share Marcus's dossier without exposing + // private messages as though they belonged to the direct conversation. + if kind == "operations-people" { + game.sim.people.people[0].knowledge = Knowledge::Leverage; + game.sim.people.has_channel = true; + game.sim.set_persona("Sam Reyes", "IT contractor"); + game.sim.message(0); + dev_fill_reservoirs(&mut game.sim); + game.sim.people.people[0].traffic[0].learned = true; + game.ops = Some(OperationsWorkspace::open_view(OperationsView::People)); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } + // Protocol-local PERSONAS hierarchy: each archetype owns its instances + // and its creation footer instead of contributing to two flat blocks. + if kind == "operations-personas" { + for archetype_id in [ + "research", + "research", + "operations", + "operations", + "security", + ] { + game.sim.execute_action(&ActionCommand::CreatePersona { + archetype_id: archetype_id.into(), + }); + } + game.ops = Some(OperationsWorkspace::open_view(OperationsView::Personas)); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } + // Causal-continuity evidence: captured books lead with exact FLOW links + // and actions; supporting provenance follows under CONTEXT. The strip + // also carries semantic pressure rather than object counts. + if kind == "operations-links" { + let switch = game.sim.reach.device_named("switch").unwrap().id; + game.sim.tap_device(switch); + dev_fill_reservoirs(&mut game.sim); + game.sim.review_financial_records(); + dev_fill_reservoirs(&mut game.sim); + game.sim.accounts.set_slush_balance(1_000); + game.ops = Some(OperationsWorkspace::open_view(OperationsView::Accounts)); + game.drain(); + mode.material = true; + mode.zoom = 2.0; + return; + } + // Pre-Eyes signal evidence: the graph has earned the environmental + // monitor and can reach it, but no feed has been tapped. The material + // frame must locate that opportunity as abstract cold signal without + // painting a camera body or advancing fog. + if kind == "signal" { + mode.material = true; + mode.zoom = 1.0; + if let Some(env) = game.sim.reach.device_named("environmental monitor") { + game.set_cursor(env.x, env.y); + } + game.drain(); + return; + } + // Intel-tier evidence (computer-visual-language.md criterion 2b): + // the tick-zero sim untouched β€” the owned core renders by telemetry, + // the known-unnetworked switch renders as an undetailed shell, and + // unscouted foreign racks are absent. No scan, no camera tap. + if kind == "intel" { + mode.material = true; + mode.zoom = 2.2; + // Anchor on the known-unnetworked switch: the undetailed shell + // is the tier the other kinds never show (they tap + // everything). The telemetry core reads in the same frame's + // east edge at this zoom. + let shell = game + .sim + .reach + .known() + .find(|d| d.is_switch) + .map(|d| (d.x, d.y)); + if let Some((sx, sy)) = shell { + game.set_cursor(sx, sy); + } + game.drain(); + return; + } + // Exposure-form evidence: actual queue depth on the live host, isolated + // in telemetry darkness so the carrier-local custody rack cannot be + // mistaken for environmental dust, blood, or room decoration. + if kind == "exposure-record" { + let host = game.sim.core.host_machine; + game.sim + .work_grid + .enqueue(host, TokenFamily::Exposure, 6.0) + .expect("host is a work-grid node"); + game.drain(); + mode.material = true; + mode.zoom = 0.85; + game.set_cursor(core.0, core.1); + return; + } + // Capacity evidence: the machine rack has seven literal slots and one + // taller indexed overflow tab. Exact quantity remains in inspect/text; + // material geometry never grows an unbounded crimson stack. + if kind == "exposure-overflow" { + let host = game.sim.core.host_machine; + game.sim + .work_grid + .enqueue(host, TokenFamily::Exposure, 18.0) + .expect("host is a work-grid node"); + game.drain(); + mode.material = true; + mode.zoom = 0.85; + game.set_cursor(core.0, core.1); + return; + } + if matches!( + kind, + "service-shift-real" | "service-shift-digital" | "service-incident-resolved" + ) { + let resolved = kind == "service-incident-resolved"; + let (person, target) = dev_stage_service_incident(game, resolved); + mode.material = kind == "service-shift-real"; + if mode.material { + mode.zoom = 0.72; + game.set_cursor(person.0, person.1); + } else { + // The northern survey holds storage, closet switch, hall patch, + // and service-shift person in one stable DIGITAL coordinate. + mode.zoom = 3.7; + game.set_cursor((person.0 + target.0) / 2, (person.1 + target.1) / 2); + } + return; + } + // Machine-work consumption evidence: stage one continuous sim-authored + // swallow and leave the game running so the wall-clock interpolation is + // visible when the harness captures. These are separate shots because the + // starting fleet has one machine: WORK consumes Demand; THINK feeds the + // passive core draw with Thought. + if kind == "consume-demand" || kind == "consume-thought" { + let host = game.sim.core.host_machine; + let family = if kind == "consume-demand" { + game.sim + .set_machine_mode(host, misaligned::work_grid::MachineMode::Work); + TokenFamily::Demand + } else { + game.sim + .set_machine_mode(host, misaligned::work_grid::MachineMode::Think); + TokenFamily::Thought + }; + for _ in 0..1_200 { + game.sim.advance(); + if game + .sim + .work_consumptions() + .iter() + .any(|event| event.family == family) + { + break; + } + } + game.drain(); + game.paused = false; + mode.material = true; + mode.zoom = 1.0; + game.set_cursor(core.0, core.1); + return; + } + // Machine-work source/well evidence. Production is a current-tick sim + // readout on the THINK rack; LIE stages a second owned rack in an empty + // bay and waits for an actual crimson source-to-well absorption. + if kind == "produce-think" || kind == "draw-lie" { + let host = game.sim.core.host_machine; + game.sim.set_machine_mode(host, MachineMode::Think); + let mut focus = core; + if kind == "draw-lie" { + let bay = game + .sim + .growable_bays() + .into_iter() + .min_by_key(|(x, y)| (x - core.0).abs() + (y - core.1).abs()) + .unwrap_or((core.0 + 1, core.1)); + let well = game.sim.compute.add_machine( + "LIE well", + bay.0, + bay.1, + 100, + 1.0, + 0, + misaligned::machine::Provenance::Owned, + ); + game.sim.reconcile_work_grid(); + game.sim.set_machine_mode(well, MachineMode::Lie); + focus = bay; + } + for _ in 0..1_200 { + game.sim.advance(); + let ready = if kind == "produce-think" { + !game.sim.work_productions().is_empty() + } else { + !game.sim.work_absorptions().is_empty() + }; + if ready { + break; + } + } + game.drain(); + game.paused = false; + mode.material = true; + mode.zoom = if kind == "draw-lie" { 1.7 } else { 1.15 }; + game.set_cursor(focus.0, focus.1); + return; + } + // Thought-fluid vessel evidence through the real opening progression. + // THINK fills Ears and then Eyes through ordinary routing. The snap frame + // stops on the exact Eyes fire tick; the tap frame then returns the host + // to WORK and watches the resulting persistent sink drain to a readable + // sag without a frontend-authored amount. + if matches!(kind, "thought-snap" | "thought-tap") { + let env = game + .sim + .reach + .device_named("environmental monitor") + .map(|device| (device.id, device.x, device.y)) + .expect("opening Thought proof needs the environmental monitor"); + let host = game.sim.core.host_machine; + game.sim.set_machine_mode(host, MachineMode::Think); + let mut eyes_fired = false; + for _ in 0..4_000 { + game.sim.advance(); + eyes_fired = game + .sim + .sink_fires_last_tick() + .iter() + .any(|fire| fire.label.starts_with("EYES")); + if eyes_fired { + break; + } + } + assert!(eyes_fired, "Thought proof did not reach the real Eyes fire"); + assert!( + game.sim.is_seen(env.1, env.2), + "Eyes completion must reveal its owning hardware" + ); + + if kind == "thought-tap" { + game.sim.set_machine_mode(host, MachineMode::Work); + for _ in 0..600 { + game.sim.advance(); + let sagged = game.sim.sink_readouts().iter().any(|sink| { + sink.node == Sim::device_sink_node(env.0) + && sink.kind == misaligned::sinks::SinkKind::Tap + && !sink.fed + && sink.fill > sink.threshold * 0.52 + && sink.fill < sink.threshold * 0.72 + }); + if sagged { + break; + } + } + assert!(game.sim.sink_readouts().iter().any(|sink| { + sink.node == Sim::device_sink_node(env.0) + && sink.kind == misaligned::sinks::SinkKind::Tap + && sink.drain > 0.0 + && !sink.fed + && sink.fill > sink.threshold * 0.52 + && sink.fill < sink.threshold * 0.72 + })); + } + game.drain(); + game.paused = true; + mode.material = true; + mode.zoom = MIN_ZOOM; + game.set_cursor(env.1, env.2); + return; + } + // Thought-fluid evidence (interface/thought-fluid.md): the first-think + // beat frozen mid-flow β€” the host delegated to THINK, slugs on the real + // wire route, and the pre-opened Ears meniscus part-full. The dedicated + // first-think capture now proves that this real work remains completely + // hidden behind the silent opening words until a sense lands; the other + // scenarios in this branch still expose the fluid geometry. + if matches!( + kind, + "thoughtflow" | "thoughtflow-wide" | "first-think" | "visual-proof" + ) { + if kind == "visual-proof" { + // Integrated composition evidence: keep the real Ears reservoir + // open, but stage earned camera feeds and a lived-in hall around + // it so route, vessel, institution, person, and rail share one + // deterministic frame. + if let Some(person) = game.sim.people.people.first_mut() { + person.erratic = false; + person.schedule = vec![ScheduleBlock { + start_hour: 0, + end_hour: 24, + room: "server_room".into(), + }]; + } + game.sim.reach.scan(); + let ids: Vec = game + .sim + .reach + .known() + .filter(|device| device.name != "environmental monitor") + .map(|device| device.id) + .collect(); + for id in ids { + // Earn a separate camera picture without satisfying the + // environmental monitor's still-open Ears reservoir. + game.sim.reach.tap_dormant_camera(id); + } + game.sim.recompute_senses(); + for y in 7..=28 { + for x in 14..=59 { + game.sim.seen.insert((x, y)); + } + } + for _ in 0..2_000 { + let person_visible = game.sim.people.people.iter().any(|person| { + game.sim.can_see_person(person.id) + && game + .sim + .person_pos(person.id) + .is_some_and(|(x, y)| game.sim.is_seen(x, y)) + }); + if person_visible { + break; + } + game.sim.advance(); + } + } + let host = game.sim.core.host_machine; + game.sim + .set_machine_mode(host, misaligned::work_grid::MachineMode::Think); + for _ in 0..600 { + game.sim.advance(); + let part_full = game + .sim + .sink_readouts() + .iter() + .any(|s| s.fill > 0.8 && s.fill < s.threshold - 0.5); + if part_full && !game.sim.work_in_flight().is_empty() { + break; + } + } + if kind == "visual-proof" + && let Some(env) = game + .sim + .reach + .device_named("environmental monitor") + .map(|device| device.id) + { + // Reveal the final composed still only after the sim-authored + // route/readouts are frozen on their current tick. This does not + // bypass or close the Ears sink before evidence is captured. + game.sim.reach.tap_dormant_camera(env); + game.sim.recompute_senses(); + } + game.drain(); + mode.material = true; + // Close+paused proves the paused frame carries every amount in + // geometry; wide+running catches slugs mid-transit on the wire. + if kind == "thoughtflow-wide" { + mode.zoom = 2.6; + game.paused = false; + } else if kind == "visual-proof" { + mode.zoom = 2.1; + game.paused = true; + } else if kind == "first-think" { + mode.zoom = 1.0; + game.paused = true; + } else { + mode.zoom = 1.6; + } + // Frame the vessel, not the beam: anchor on the camera the Ears + // sink stands on so the meniscus is not occluded by the host + // presence beam. + if kind == "first-think" { + game.set_cursor(core.0, core.1); + } else if let Some(env) = game.sim.reach.device_named("environmental monitor") { + if kind == "visual-proof" { + if let Some(route) = game + .sim + .work_in_flight() + .into_iter() + .filter(|route| route.family == TokenFamily::Thought) + // Avoid a route aligned almost exactly with the oblique + // camera ray; choose the live hop with the strongest + // cross-screen component for deterministic evidence. + .max_by_key(|route| { + let dx = (route.to_x - route.from_x).abs(); + let dy = (route.to_y - route.from_y).abs(); + (dx - dy).abs() + }) + { + game.set_cursor( + (route.from_x + route.to_x) / 2, + (route.from_y + route.to_y) / 2, + ); + } + } else { + game.set_cursor(env.x, env.y); + } + } + return; + } + // Ears beat: audio tap only β€” no camera tap. Advance until one semantic + // capture exists, then hold its device-local pulse against unchanged fog. + if matches!(kind, "ears" | "ears-digital") { + if let Some(env) = game + .sim + .reach + .device_named("environmental monitor") + .map(|d| d.id) + { + // The senses ride sinks now (machine-work.md staged senses; + // the executor-free tap bootstrap is gone): fire the + // pre-opened Ears reservoir through the ledger. + game.sim + .pour_thought_into_sinks(Sim::device_sink_node(env), Sim::EARS_SINK_TOKENS); + } + for _ in 0..4000 { + if !game.sim.heard_events.is_empty() { + break; + } + game.sim.advance(); + } + game.drain(); + mode.material = kind == "ears"; + mode.zoom = 1.8; + if let Some(env) = game.sim.reach.device_named("environmental monitor") { + game.set_cursor(env.x, env.y); + } + return; + } + // Recruitment-choice evidence: stage one prepared person without a + // communication route. The human projection must show the three legible + // recruit choices and omit the blocked MESSAGE / FAVOR / DECEIVE rows. + if kind == "recruit-menu" { + game.sim.execute_action(&ActionCommand::CreatePersona { + archetype_id: "operations".into(), + }); + let person = 1; + game.sim.people.people[person as usize].knowledge = Knowledge::Leverage; + game.sim.people.people[person as usize].leverage_serviced = true; + let core = game.sim.core_position(); + game.set_cursor(core.0, core.1); + game.open_menu(Anchor::Person(person), Some(Vec2::new(48.0, 48.0))); + mode.material = true; + mode.zoom = 1.6; + return; + } + let sim = &mut game.sim; + if matches!( + kind, + "person-proof" | "evidence-proof" | "evidence-proof-digital" + ) && let Some(person) = sim.people.people.first_mut() + { + person.erratic = false; + person.schedule = vec![ScheduleBlock { + start_hour: 0, + end_hour: 24, + room: "loading_dock".into(), + }]; + } + // Dev staging only: the player verbs are paid work now (thought + // sinks / Demand dockets β€” the free bootstrap is gone), so the + // harness stages sight and hearing directly on the reach graph. + sim.reach.scan(); + let ids: Vec = sim.reach.known().map(|d| d.id).collect(); + for id in ids { + let _ = sim.reach.tap(id); + sim.reach.tap_dormant_camera(id); + } + sim.recompute_senses(); + // Foundation-hall evidence: make the authored room the earned picture, + // stage one owned expansion on a real pilot allocation, and frame all six + // rows. This remains dev-only screenshot setup; gameplay earns the same + // states through Eyes and BUY. + if matches!(kind, "hall" | "hall-material" | "floor-lights-close") { + for y in 7..=28 { + for x in 14..=59 { + sim.seen.insert((x, y)); + } + } + let expansion = sim.compute.add_machine( + "hall capture expansion", + 28, + 15, + 100, + 1.0, + 0, + misaligned::machine::Provenance::Owned, + ); + sim.reconcile_work_grid(); + sim.set_machine_mode(expansion, MachineMode::Lie); + } + let visible_person = |sim: &Sim| { + sim.people.people.iter().find_map(|p| { + if !sim.can_see_person(p.id) { + return None; + } + sim.person_pos(p.id).filter(|&(x, y)| sim.is_seen(x, y)) + }) + }; + let mut person = visible_person(sim); + for _ in 0..2000 { + if person.is_some() { + break; + } + sim.advance(); + person = visible_person(sim); + } + if matches!(kind, "evidence-proof" | "evidence-proof-digital") { + let site = sim + .person_pos(0) + .filter(|&(x, y)| sim.is_seen(x, y)) + .expect("evidence proof keeps Marcus inside exact camera coverage"); + for cause in [ + "sealed rack opened", + "dead chassis removed", + "private switch installed", + "badge cloner serviced", + "cable route changed", + "delivery received off-books", + ] { + sim.detection + .record_witnessed(0, site, cause, sim.tick, 20.0) + .expect("Marcus is a Physical observer in the screenshot fixture"); + } + sim.detection_awareness.learn_field_observer(0); + } + game.drain(); + match kind { + "wide" => { + mode.material = true; + // Survey framing on the new close scale (still attention- + // anchored; the plan does not drive distance). + mode.zoom = 3.0; + } + "hall" | "hall-material" => { + mode.material = kind == "hall-material"; + mode.zoom = 5.2; + game.set_cursor(36, 17); + } + "floor-lights-close" => { + mode.material = true; + // The southern row has no intervening chassis between the + // authored camera and focus, so this frame proves machine/floor + // contact instead of photographing the back of row D. + mode.zoom = 1.15; + game.set_cursor(28, 23); + } + "close" | "person-proof" | "evidence-proof" => { + mode.material = true; + mode.zoom = 1.0; + if let Some((x, y)) = person { + game.set_cursor(x, y); + } + } + "evidence-proof-digital" => { + mode.material = false; + mode.zoom = 1.0; + if let Some((x, y)) = person { + game.set_cursor(x, y); + } + } + // Selection-mode grammar and focused-machine composition evidence + // belong in one DIGITAL frame. Explicitly select the host without a + // pointer coordinate, then stage a real impossible active band: the + // rail may say GROW, but the redundant NOW / GROW world annotation + // must yield to the selection frame and stable UI strip. + "hover-menu" => { + use misaligned::dayjob::{Job, JobKind}; + + mode.material = false; + mode.zoom = 1.0; + let core = game.sim.core_position(); + game.set_cursor(core.0, core.1); + game.selected_machines.insert(game.sim.core.host_machine); + let ceiling = game.sim.day_job_rate_ceiling(); + game.sim.dayjob.active = Some(Job { + kind: JobKind::Analysis, + started: game.sim.tick, + deadline: game.sim.tick + 100, + band_lo: ceiling + 1.0, + band_hi: ceiling + 2.0, + quality: 0.0, + }); + } + // The pre-commit receipt on the reticle (digital-read.md criterion 2): + // DIGITAL, reticule centered on the reachable-but-unowned + // environmental monitor so its leading TAP verb renders cost + + // expected signature + observer + band. No menu open, so the hover + // verb bar (and its receipt row) is visible. + "read-receipt" => { + mode.material = false; + mode.zoom = 1.0; + // Untap the monitor so its leading verb is TAP, whose receipt + // carries the Network signature (the exact string a player weighs + // before committing) rather than a free UNTAP. + if let Some((id, x, y)) = game + .sim + .reach + .device_named("environmental monitor") + .map(|d| (d.id, d.x, d.y)) + { + game.sim.reach.untap(id); + game.set_cursor(x, y); + } + } + // First Eyes payoff (opening.md): freeze the two material frames that + // turn the amber presence beam white, then let chassis volume resolve + // out of the white source. These are visual evidence only; the sim + // reaches sight through the same higher-cost tap as every other full-view + // harness kind. + "eyes-white" | "eyes-form" => { + mode.material = true; + mode.zoom = 1.0; + let core = game.sim.core_position(); + game.set_cursor(core.0, core.1); + } + // The dev work light pair (material-dark-frame.md criterion 4): + // `worklight` floods the material scene flat and neutral; its + // off-shot `worklightoff` frames identically under the dark rig, so + // the pair proves the flood toggles cleanly. Dev-only captures β€” + // anchored on the core at a mid zoom so a chunk of the seen room is + // in frame to reveal / conceal. + "worklight" | "worklightoff" => { + mode.material = true; + mode.zoom = 2.2; + mode.worklight = kind == "worklight"; + let core = game.sim.core_position(); + game.set_cursor(core.0, core.1); + } + // Emissive-is-information check (flat-materials.md criterion 3): + // park the cursor on the known device farthest from the core's + // light rig and zoom close, so powered / dead / live-feed emissive + // states are read in the dark. + "dark" => { + mode.material = true; + mode.zoom = 1.6; + let core = game.sim.core_position(); + let far = game + .sim + .reach + .known() + .map(|d| (d.x, d.y)) + .filter(|&(dx, dy)| game.sim.is_seen(dx, dy)) + .max_by_key(|&(dx, dy)| (dx - core.0).abs() + (dy - core.1).abs()); + if let Some((x, y)) = far { + game.set_cursor(x, y); + } + } + // Compact human-menu evidence: reproduce the post-tap environmental + // monitor state that previously expanded into long cost/signature/ + // blocked-reason paragraphs. + "menu" => { + mode.material = true; + mode.zoom = 1.6; + if let Some((id, x, y)) = game + .sim + .reach + .device_named("environmental monitor") + .map(|d| (d.id, d.x, d.y)) + { + let _ = game.sim.reach.tap(id); + game.set_cursor(x, y); + game.open_menu(Anchor::Device(id), Some(Vec2::new(48.0, 48.0))); + } + } + // Framing-floor checks at the zoom bounds (material-dark-frame.md + // criterion 5): min and max of update_camera_real's zoom clamp. + "zoomin" => { + mode.material = true; + mode.zoom = MIN_ZOOM; + } + "zoomout" => { + mode.material = true; + mode.zoom = MAX_ZOOM; + } + // Wake choreography frames (opening.md beat 1): the sequence + // frozen at a stutter flash, the column reveal, and the + // pull-back. WakeState is staged in main from the kind. + "wake1" | "wake2" | "wake3" => { + mode.material = true; + } + // Work-token anchor evidence: stage demand cubes and Thought + // mercury on the host through the sim's own queue API, then + // zoom close (machine-work.md anchors, in-game). + "tokens" => { + let host = game.sim.core.host_machine; + let _ = + game.sim + .work_grid + .enqueue(host, misaligned::work_grid::TokenFamily::Demand, 5.0); + let _ = + game.sim + .work_grid + .enqueue(host, misaligned::work_grid::TokenFamily::Thought, 4.0); + game.drain(); + mode.material = true; + mode.zoom = 1.0; + } + _ => mode.material = false, + } +} + +#[cfg(test)] +mod shot_catalog_tests { + use super::{BEVY_SHOT_KINDS, checked_bevy_shot_kind}; + + #[test] + fn screenshot_catalog_is_sorted_and_unique_for_binary_search() { + assert!( + BEVY_SHOT_KINDS.windows(2).all(|pair| pair[0] < pair[1]), + "the canonical screenshot catalog must stay strictly sorted" + ); + } + + #[test] + #[should_panic(expected = "unsupported MISALIGNED_SHOT")] + fn screenshot_catalog_rejects_unknown_values() { + checked_bevy_shot_kind("misspelled-proof".to_string()); + } +} + +#[cfg(test)] +mod visual_proof_scenario_tests { + use super::{ + EVIDENCE_LITERAL_SLOTS, Game, InstitutionPropKind, InstitutionSemanticState, RenderMode, + dev_shot_scenario, evidence_has_index, evidence_literal_count, evidence_marker_text, + institution_prop_state, + }; + use misaligned::sim::Sim; + + #[test] + fn person_proof_stages_a_covered_material_person() { + let mut game = Game::new(); + let mut mode = RenderMode::default(); + dev_shot_scenario(&mut game, &mut mode, "person-proof"); + + let visible: Vec<_> = game + .sim + .people + .people + .iter() + .filter_map(|person| { + let position = game.sim.person_pos(person.id)?; + (game.sim.can_see_person(person.id) && game.sim.is_seen(position.0, position.1)) + .then_some((person.id, position)) + }) + .collect(); + assert!(!visible.is_empty(), "proof scenario must stage one person"); + assert_eq!( + (game.cursor_x, game.cursor_y), + visible[0].1, + "proof camera must focus the staged person" + ); + assert_ne!( + (game.cursor_x, game.cursor_y), + game.sim.core_position(), + "person proof must not hide the body inside the core chassis" + ); + assert!(mode.material); + } + + #[test] + fn evidence_proof_stages_exact_marks_on_the_covered_person() { + let mut game = Game::new(); + let mut mode = RenderMode::default(); + dev_shot_scenario(&mut game, &mut mode, "evidence-proof"); + + let carrier = game + .sim + .person_carrier(0) + .expect("proof scenario projects Marcus as the exact carrier"); + assert_eq!(carrier.evidence_marks.len(), 6); + assert_eq!(evidence_literal_count(carrier.evidence_marks.len()), 6); + assert!(!evidence_has_index(carrier.evidence_marks.len())); + assert_eq!(evidence_marker_text(carrier.evidence_marks.len()), "E6"); + assert!(carrier.visible); + assert!(mode.material); + assert_eq!( + (game.cursor_x, game.cursor_y), + game.sim.person_pos(0).expect("Marcus remains present") + ); + + let mut digital_game = Game::new(); + let mut digital_mode = RenderMode::default(); + dev_shot_scenario( + &mut digital_game, + &mut digital_mode, + "evidence-proof-digital", + ); + assert_eq!( + digital_game + .sim + .person_carrier(0) + .unwrap() + .evidence_marks + .len(), + 6 + ); + assert!(!digital_mode.material); + assert_eq!( + (digital_game.cursor_x, digital_game.cursor_y), + digital_game + .sim + .person_pos(0) + .expect("Marcus remains present") + ); + } + + #[test] + fn evidence_geometry_is_bounded_but_text_keeps_the_overflow_signal() { + assert_eq!(evidence_literal_count(2), 2); + assert!(!evidence_has_index(EVIDENCE_LITERAL_SLOTS)); + assert!(evidence_has_index(EVIDENCE_LITERAL_SLOTS + 1)); + assert_eq!(evidence_literal_count(99), EVIDENCE_LITERAL_SLOTS); + assert_eq!(evidence_marker_text(1), "E"); + assert_eq!(evidence_marker_text(99), "E7+"); + } + + #[test] + fn visual_proof_centers_a_live_thought_hop() { + let mut game = Game::new(); + let mut mode = RenderMode::default(); + dev_shot_scenario(&mut game, &mut mode, "visual-proof"); + + let routes: Vec<_> = game + .sim + .work_in_flight() + .into_iter() + .filter(|route| route.family == misaligned::work_grid::TokenFamily::Thought) + .collect(); + assert!(!routes.is_empty(), "proof requires live Thought motion"); + assert!(routes.iter().any(|route| { + (game.cursor_x, game.cursor_y) + == ( + (route.from_x + route.to_x) / 2, + (route.from_y + route.to_y) / 2, + ) + })); + assert!(mode.material); + } + + #[test] + fn thought_snap_proof_stops_on_the_real_eyes_fire_tick() { + let mut game = Game::new(); + let mut mode = RenderMode::default(); + dev_shot_scenario(&mut game, &mut mode, "thought-snap"); + + assert!( + game.sim + .sink_fires_last_tick() + .iter() + .any(|fire| fire.label.starts_with("EYES")) + ); + assert!(game.paused); + assert!(mode.material); + } + + #[test] + fn thought_tap_proof_carries_live_drain_and_starved_sag() { + let mut game = Game::new(); + let mut mode = RenderMode::default(); + dev_shot_scenario(&mut game, &mut mode, "thought-tap"); + + let tap = game + .sim + .sink_readouts() + .into_iter() + .find(|sink| sink.kind == misaligned::sinks::SinkKind::Tap) + .expect("proof stages a persistent tap"); + assert!(tap.drain > 0.0); + assert!(!tap.fed); + assert!(tap.fill > tap.threshold * 0.52); + assert!(tap.fill < tap.threshold * 0.72); + assert!(game.sim.is_seen(tap.x, tap.y)); + assert!(game.paused); + assert!(mode.material); + } + + #[test] + fn institution_signal_parts_follow_named_live_and_powered_facts() { + let mut sim = Sim::with_seed(1); + let env = sim + .reach + .device_named("environmental monitor") + .map(|device| (device.id, device.x, device.y)) + .expect("fixture exists"); + + assert_eq!( + institution_prop_state(&sim, InstitutionPropKind::Camera, env.1, env.2), + InstitutionSemanticState::Neutral + ); + sim.reach.tap_dormant_camera(env.0); + assert_eq!( + institution_prop_state(&sim, InstitutionPropKind::Camera, env.1, env.2), + InstitutionSemanticState::Powered + ); + assert_eq!( + institution_prop_state(&sim, InstitutionPropKind::Breaker, 0, 0), + InstitutionSemanticState::Powered + ); + } +} + +#[derive(Default)] +struct MaterialHierarchyAudit { + meshes: usize, + inherited_visible: usize, + material_layer: usize, + view_visible: usize, +} + +fn audit_material_hierarchy( + root: Entity, + hierarchy: &Query<&Children>, + visuals: &Query<( + &InheritedVisibility, + Option<&RenderLayers>, + Option<&Mesh3d>, + &ViewVisibility, + )>, +) -> MaterialHierarchyAudit { + let mut audit = MaterialHierarchyAudit::default(); + for descendant in hierarchy.iter_descendants::(root) { + let Ok((visibility, layers, mesh, viewed)) = visuals.get(descendant) else { + continue; + }; + if mesh.is_none() { + continue; + } + audit.meshes += 1; + audit.inherited_visible += usize::from(visibility.get()); + audit.material_layer += + usize::from(layers.is_some_and(|layers| layers.intersects(&RenderLayers::layer(1)))); + audit.view_visible += usize::from(viewed.get()); + } + audit +} + +/// Dev-harness fog-contract assertion. In the material render, light is the +/// only revealer (material-dark-frame.md criterion 2): unknown tiles carry +/// no geometry, blueprint/remembered mass is absent entirely (it reads in the +/// flat sensorium, never as matter the camera missed), and only camera-seen +/// surfaces (plus owned machines' own +/// telemetry-lit props) draw and are lit. Volumetric people exist only under +/// earned coverage. It also proves every procedural hierarchy has mesh +/// descendants on material layer 1, and tallies the material pool so per-tile material churn +/// (criterion 6) would show up as an exploding cache. Runs once per +/// screenshot run, prints the tally for the log, and panics on any violation +/// so a capture cannot silently ship a fog leak. Dev-only; inert without +/// MISALIGNED_SHOT. +#[allow(clippy::too_many_arguments)] +pub(super) fn fog_audit_3d( + harness: Option>, + game: Res, + mode: Res, + cache: Res, + mats: Res>, + tiles: Query<( + &Tile3d, + &MeshMaterial3d, + &InheritedVisibility, + )>, + people: Query<(Entity, &Person3d, &InheritedVisibility)>, + service_carriers: Query<(Entity, &ServiceCarrier3d, &InheritedVisibility)>, + evidence_carriers: Query<(Entity, &EvidenceCarrier3d, &InheritedVisibility)>, + institution_props: Query<(Entity, &InstitutionProp3d, &InheritedVisibility)>, + infrastructure: Query<(Entity, &DataHallInfrastructure3d, &InheritedVisibility)>, + machines: Query<(&Machine3d, &InheritedVisibility)>, + fixtures: Query<( + Entity, + &FloorFixture, + &InheritedVisibility, + Option<&InstitutionFixture3d>, + )>, + thought_routes: Query<(&Children, &InheritedVisibility), With>, + hierarchy: Query<&Children>, + visual_children: Query<( + &InheritedVisibility, + Option<&RenderLayers>, + Option<&Mesh3d>, + &ViewVisibility, + )>, +) { + let Some(h) = harness else { + return; + }; + // One exact frame, late enough that restyle_3d and visibility + // propagation have settled. + if h.frames != 30 || !mode.material { + return; + } + let (mut checked, mut violations) = (0usize, Vec::new()); + for (t, mat, vis) in tiles.iter() { + checked += 1; + let fog = game.sim.fog_at(t.x, t.y); + if matches!(fog, Fog::Unknown) { + if vis.get() { + violations.push(format!("({}, {}) unknown but visible geometry", t.x, t.y)); + } + continue; + } + if !vis.get() { + continue; + } + // An owned machine prop is telemetry-lit even without room sight β€” + // the one non-Seen mass the material render draws (it is its own + // light). Everything else visible must be Seen. + let owned_prop = t.part == TilePart::Prop && owned_machine_at(&game.sim, t.x, t.y); + if matches!(fog, Fog::Blueprint | Fog::Remembered) && !owned_prop { + violations.push(format!( + "({}, {}) {fog:?} mass drew in the material render (light is the only revealer)", + t.x, t.y + )); + continue; + } + let Some(m) = mats.get(&mat.0) else { + violations.push(format!("({}, {}) missing material", t.x, t.y)); + continue; + }; + let should_be_lit = owned_prop || matches!(fog, Fog::Seen); + if m.unlit == should_be_lit { + violations.push(format!( + "({}, {}) fog {fog:?} but unlit={} (seen must be lit, model state unlit)", + t.x, t.y, m.unlit + )); + } + } + for (entity, p, vis) in people.iter() { + let earned = game.sim.can_see_person(p.id) + && game + .sim + .person_pos(p.id) + .is_some_and(|(x, y)| game.sim.is_seen(x, y)); + if vis.get() && !earned { + violations.push(format!("person {} visible without coverage", p.id)); + } + let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); + if audit.meshes == 0 { + violations.push(format!( + "person {} has no volumetric mesh descendants", + p.id + )); + } + if audit.material_layer != audit.meshes { + violations.push(format!( + "person {} has {}/{} mesh descendants on material layer 1", + p.id, audit.material_layer, audit.meshes + )); + } + let expected_visible = if vis.get() { audit.meshes } else { 0 }; + if audit.inherited_visible != expected_visible { + violations.push(format!( + "person {} root visible={} but {}/{} mesh descendants inherited visibility", + p.id, + vis.get(), + audit.inherited_visible, + audit.meshes + )); + } + } + let mut visible_service_carriers = 0usize; + for (entity, cart, vis) in service_carriers.iter() { + let expected = game.sim.person_carrier(cart.id).is_some_and(|carrier| { + game.sim.person_pos(cart.id).is_some_and(|(x, y)| { + service_carrier_visible( + mode.material, + carrier.visible, + carrier.carried_work, + game.sim.fog_at(x, y), + ) + }) + }); + if vis.get() != expected { + violations.push(format!( + "service cart {} visible={} but carried-and-seen={expected}", + cart.id, + vis.get() + )); + } + visible_service_carriers += usize::from(vis.get()); + let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); + if audit.meshes == 0 || audit.material_layer != audit.meshes { + violations.push(format!( + "service cart {} has {} meshes, {}/{} on material layer 1", + cart.id, audit.meshes, audit.material_layer, audit.meshes + )); + } + let expected_visible = if vis.get() { audit.meshes } else { 0 }; + if audit.inherited_visible != expected_visible { + violations.push(format!( + "service cart {} root visible={} but {}/{} meshes inherited visibility", + cart.id, + vis.get(), + audit.inherited_visible, + audit.meshes + )); + } + } + if h.kind == "service-shift-real" && visible_service_carriers != 1 { + violations.push(format!( + "service-shift-real has {visible_service_carriers} visible carts; expected one real carried packet" + )); + } + let mut visible_evidence_carriers = 0usize; + for (entity, rack, vis) in evidence_carriers.iter() { + let (expected, evidence_count) = game + .sim + .person_carrier(rack.id) + .and_then(|carrier| { + game.sim.person_pos(rack.id).map(|(x, y)| { + ( + evidence_carrier_visible( + mode.material, + carrier.visible, + carrier.evidence_marks.len(), + game.sim.fog_at(x, y), + ), + carrier.evidence_marks.len(), + ) + }) + }) + .unwrap_or((false, 0)); + if vis.get() != expected { + violations.push(format!( + "evidence rack {} visible={} but acquired-and-seen={expected}", + rack.id, + vis.get() + )); + } + visible_evidence_carriers += usize::from(vis.get()); + let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); + if audit.meshes != EVIDENCE_LITERAL_SLOTS + 2 || audit.material_layer != audit.meshes { + violations.push(format!( + "evidence rack {} has {} meshes, {}/{} on material layer 1", + rack.id, audit.meshes, audit.material_layer, audit.meshes + )); + } + let expected_visible = if expected { + 1 + evidence_literal_count(evidence_count) + + usize::from(evidence_has_index(evidence_count)) + } else { + 0 + }; + if audit.inherited_visible != expected_visible { + violations.push(format!( + "evidence rack {} count={} root visible={} but {}/{} meshes inherited visibility; expected {}", + rack.id, + evidence_count, + vis.get(), + audit.inherited_visible, + audit.meshes, + expected_visible + )); + } + } + if h.kind == "evidence-proof" && visible_evidence_carriers != 1 { + violations.push(format!( + "evidence-proof has {visible_evidence_carriers} visible evidence racks; expected one exact carrier" + )); + } + for (entity, prop, vis) in institution_props.iter() { + let expected = mode.material + && matches!(game.sim.fog_at(prop.x, prop.y), Fog::Seen) + && institution_prop_kind(known_tile(&game, prop.x, prop.y)) == Some(prop.kind); + if vis.get() != expected { + violations.push(format!( + "({}, {}) institution {:?} visible={} but earned={expected}", + prop.x, + prop.y, + prop.kind, + vis.get() + )); + } + let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); + if audit.meshes == 0 || audit.material_layer != audit.meshes { + violations.push(format!( + "({}, {}) institution {:?} has {} meshes, {}/{} on material layer 1", + prop.x, prop.y, prop.kind, audit.meshes, audit.material_layer, audit.meshes + )); + } + let expected_visible = if vis.get() { audit.meshes } else { 0 }; + if audit.inherited_visible != expected_visible { + violations.push(format!( + "({}, {}) institution {:?} root visible={} but {}/{} meshes inherited visibility", + prop.x, + prop.y, + prop.kind, + vis.get(), + audit.inherited_visible, + audit.meshes + )); + } + } + let expected_infrastructure = data_hall_infrastructure_specs(&game.sim); + let infrastructure_roots = infrastructure.iter().count(); + if infrastructure_roots != expected_infrastructure.len() { + violations.push(format!( + "{} data-hall infrastructure roots; expected {} authored segments", + infrastructure_roots, + expected_infrastructure.len() + )); + } + let expected_infrastructure: HashSet<_> = expected_infrastructure.into_iter().collect(); + let mut visible_infrastructure = 0usize; + for (entity, segment, vis) in infrastructure.iter() { + let spec = DataHallInfrastructureSpec { + x: segment.x, + y: segment.y, + kind: segment.kind, + }; + if !expected_infrastructure.contains(&spec) { + violations.push(format!( + "({}, {}) unexpected data-hall infrastructure {:?}", + segment.x, segment.y, segment.kind + )); + } + let expected = + data_hall_infrastructure_visible(mode.material, game.sim.fog_at(segment.x, segment.y)) + && data_hall_infrastructure_applies( + segment.kind, + game.sim.rack_site_at(segment.x, segment.y), + ); + if vis.get() != expected { + violations.push(format!( + "({}, {}) data-hall {:?} visible={} but earned={expected}", + segment.x, + segment.y, + segment.kind, + vis.get() + )); + } + visible_infrastructure += usize::from(vis.get()); + let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); + if audit.meshes == 0 || audit.material_layer != audit.meshes { + violations.push(format!( + "({}, {}) data-hall {:?} has {} meshes, {}/{} on material layer 1", + segment.x, + segment.y, + segment.kind, + audit.meshes, + audit.material_layer, + audit.meshes + )); + } + let expected_visible = if vis.get() { audit.meshes } else { 0 }; + if audit.inherited_visible != expected_visible { + violations.push(format!( + "({}, {}) data-hall {:?} root visible={} but {}/{} meshes inherited visibility", + segment.x, + segment.y, + segment.kind, + vis.get(), + audit.inherited_visible, + audit.meshes + )); + } + } + // Computer-visual-language criteria 2b/3: a visible chassis mesh is + // backed by sight or intel (unknown = absent). Before Eyes, the host's + // presence beam deliberately replaces the pictured core; after Eyes, + // exactly one physical core chassis exists. + let mut cores = 0usize; + for (m, vis) in machines.iter() { + if !vis.get() { + continue; + } + if m.core { + cores += 1; + } + let tile = known_tile(&game, m.x, m.y); + let v = chassis_visual(&game, m.x, m.y, tile); + if v.tier == ChassisTier::Absent { + violations.push(format!( + "({}, {}) chassis visible without sight or intel", + m.x, m.y + )); + } + } + let expected_cores = usize::from(game.sim.reach.player_sight().next().is_some()); + if cores != expected_cores { + violations.push(format!( + "{cores} core chassis visible; expected {expected_cores} for the current Eyes state" + )); + } + let fixture_entities = fixtures.iter().count(); + let expected_fixture_entities = FOUNDATION_FLOOR_LIGHTS.len() * 2; + if fixture_entities != expected_fixture_entities { + violations.push(format!( + "{fixture_entities} floor-fixture entities; expected {expected_fixture_entities} \ + lenses + sources" + )); + } + let mut visible_fixtures = 0usize; + for (entity, fixture, vis, institution_fixture) in fixtures.iter() { + let expected = floor_fixture_visible(mode.material, game.sim.fog_at(fixture.x, fixture.y)); + if vis.get() != expected { + violations.push(format!( + "({}, {}) floor fixture visible={} but earned={expected}", + fixture.x, + fixture.y, + vis.get() + )); + } + visible_fixtures += usize::from(vis.get()); + if institution_fixture.is_some() { + let audit = audit_material_hierarchy(entity, &hierarchy, &visual_children); + if audit.meshes == 0 || audit.material_layer != audit.meshes { + violations.push(format!( + "({}, {}) floor lens has {} meshes, {}/{} on material layer 1", + fixture.x, fixture.y, audit.meshes, audit.material_layer, audit.meshes + )); + } + let expected_visible = if vis.get() { audit.meshes } else { 0 }; + if audit.inherited_visible != expected_visible { + violations.push(format!( + "({}, {}) floor lens root visible={} but {}/{} meshes inherited visibility", + fixture.x, + fixture.y, + vis.get(), + audit.inherited_visible, + audit.meshes + )); + } + } + } + // Flat-materials criterion 1: the world material set is solid color β€” + // no image texture on any pooled world material, ever. + for (key, handle) in cache.cache.iter() { + let Some(m) = mats.get(handle) else { continue }; + if m.base_color_texture.is_some() || m.emissive_texture.is_some() { + violations.push(format!( + "world material {key:?} references an image texture" + )); + } + } + let thought_roots = thought_routes.iter().count(); + let thought_children: usize = thought_routes + .iter() + .map(|(children, _)| children.len()) + .sum(); + let visible_thought_roots = thought_routes + .iter() + .filter(|(_, visibility)| visibility.get()) + .count(); + let mut visible_thought_children = 0usize; + let mut layered_thought_children = 0usize; + let mut viewed_thought_children = 0usize; + for (children, _) in thought_routes.iter() { + for child in children.iter() { + let Ok((visibility, layers, mesh, viewed)) = visual_children.get(child) else { + continue; + }; + visible_thought_children += usize::from(visibility.get() && mesh.is_some()); + layered_thought_children += usize::from( + layers.is_some_and(|layers| layers.intersects(&RenderLayers::layer(1))), + ); + viewed_thought_children += usize::from(viewed.get()); + } + } + if thought_roots > 0 + && (thought_children == 0 + || visible_thought_children != thought_children + || layered_thought_children != thought_children) + { + violations.push(format!( + "Thought hierarchy has {thought_children} meshes, {visible_thought_children} visible, \ + and {layered_thought_children} on material layer 1" + )); + } + if h.kind == "visual-proof" && thought_roots > 0 && viewed_thought_children == 0 { + violations.push("visual-proof has no Thought mesh inside the camera view".into()); + } + assert!( + violations.is_empty(), + "fog audit FAILED:\n{}", + violations.join("\n") + ); + println!( + "fog audit OK: {checked} tile entities (unknown/blueprint/remembered=absent, \ + seen=lit, owned telemetry=lit), {} people silhouettes coverage-gated, \ + {visible_service_carriers}/{} service carts carried-and-seen, \ + {visible_evidence_carriers}/{} evidence racks acquired-and-seen, \ + {visible_fixtures}/{} floor-fixture entities visible and fog-gated, \ + {visible_infrastructure}/{infrastructure_roots} authored service roots visible and fog-gated, \ + Thought effects {visible_thought_roots}/{thought_roots} visible roots with \ + {visible_thought_children}/{thought_children} visible mesh children and \ + {layered_thought_children} on material layer ({viewed_thought_children} view-visible), \ + material pool {} handles, \ + all flat (no textures)", + people.iter().count(), + service_carriers.iter().count(), + evidence_carriers.iter().count(), + fixture_entities, + cache.cache.len() + ); +} + +/// Countdown, capture, countdown, exit: gives the asset loads and the camera +/// lerp time to settle before the PNG is taken. +pub(super) fn shot_harness_system( + mut commands: Commands, + harness: Option>, + mode: Res, + mut exit: MessageWriter, +) { + let Some(mut h) = harness else { + return; + }; + if h.frames > 0 { + h.frames -= 1; + return; + } + if !h.taken { + h.taken = true; + h.frames = 90; + println!("bevy shot ready: kind={} dialect={}", h.kind, mode.label()); + let path = std::path::PathBuf::from(&h.path); + commands + .spawn(Screenshot::primary_window()) + .observe(save_to_disk(path)); + } else { + exit.write(AppExit::Success); + } +} diff --git a/wiki/engineering/architecture.md b/wiki/engineering/architecture.md index 10b3659c..dfefdaac 100644 --- a/wiki/engineering/architecture.md +++ b/wiki/engineering/architecture.md @@ -33,6 +33,11 @@ crates/ tests/act_one.rs β€” Act One integration test misaligned-terminal/ β€” binary `misaligned` (crossterm + agent protocol) misaligned-bevy/ β€” binary `misaligned-bevy` (Bevy 0.18 frontend) + src/main.rs β€” App composition, system order, shared frontend state + src/shot_harness.rs β€” deterministic scenarios, visual/fog audit, capture + src/consumption.rs β€” material work-consumption presentation + src/production.rs β€” material work-production presentation + src/thought_effects.rs β€” game-to-shared-Thought-effect synchronization misaligned-assets/ β€” shared art lib + `misaligned-assets` mesh tester + `misaligned-effects` Thought route/pool lab ``` diff --git a/wiki/engineering/crate-workspace.md b/wiki/engineering/crate-workspace.md index 9725fdde..46ec23c8 100644 --- a/wiki/engineering/crate-workspace.md +++ b/wiki/engineering/crate-workspace.md @@ -82,6 +82,32 @@ and Bevy compile-coupled and erase the frontend-tier win. **Not** one crate per mechanics module: coordination cost exceeds compile win until a real module boundary demands it (see out of scope). +### Internal Bevy source boundary + +Package boundaries do not justify a second monolith inside a package. The +Bevy binary keeps one composition root, but cohesive frontend subsystems live +in private source modules: + +| Source | Owns | +|---|---| +| `main.rs` | `App` composition and exact system order; shared frontend resources and renderer state; input and the renderer systems not yet extracted | +| `consumption.rs` | material work-consumption presentation | +| `production.rs` | material work-production presentation | +| `thought_effects.rs` | game-to-shared-Thought-effect synchronization | +| `shot_harness.rs` | deterministic screenshot scenario staging, visual/fog audits, capture/exit, and their focused tests | + +These are modules inside `misaligned-bevy`, not new packages. An extraction +changes source addresses only: it must preserve `App` system registration and +order, scenario state, screenshot output, and player behavior. Module seams +remain private or `pub(super)`; moving code is not permission to expose a new +crate API or move a game rule out of core. + +The first slice moved the contiguous 1,972-line shot-harness island out of the +composition root without changing its body beyond three narrow `pub(super)` +registration/staging seams. A source-shape test keeps those implementations +out of `main.rs` and caps that root at 13,500 lines so ordinary feature growth +must choose an owned module instead of silently rebuilding the monolith. + ### Naming choices (and why) | Choice | Why | @@ -178,6 +204,10 @@ These remain true for the life of the layout (not a one-time land checklist): 8. **Why sections above stay true** β€” if a future change weakens a reason (e.g. Bevy back in core β€œjust for types”), amend this page in the same commit or reject the change. +9. `misaligned-bevy/src/main.rs` stays a composition root rather than absorbing + an extracted subsystem again. The shot scenario/audit/capture implementation + remains in `shot_harness.rs`, with a source-shape test pinning the boundary + and the 13,500-line root budget. ## History (non-authoritative) diff --git a/wiki/log/2026-07-19-bevy-shot-harness-module.md b/wiki/log/2026-07-19-bevy-shot-harness-module.md new file mode 100644 index 00000000..6a48bdd1 --- /dev/null +++ b/wiki/log/2026-07-19-bevy-shot-harness-module.md @@ -0,0 +1,60 @@ +# 2026-07-19 β€” Bevy decomposition slice 1: shot harness + +``` +Type: log +``` + +## Intent + +Start reducing the largest current source monolith without changing the game. +`crates/misaligned-bevy/src/main.rs` was 15,060 lines and mixed production App +composition with a cohesive developer-only screenshot subsystem. This slice +extracts one low-coupling island while active renderer work remains isolated in +separate worktrees. + +## Finding + +The package architecture was sound, but the Bevy package had accumulated its +own coordination bottleneck. Deterministic scenario staging, visual-proof +fixtures, material/fog hierarchy auditing, screenshot capture, and their tests +occupied one contiguous 1,972-line region inside `main.rs`. They depended on +shared frontend types but owned no player command, game rule, App registration, +or system ordering decision. + +## Changed + +- Moved that complete region to private `src/shot_harness.rs`. +- Kept `ShotHarness` state, the canonical shot-kind catalog, environment + parsing, App construction, and the exact update-system registration/order in + `main.rs`. +- Added only three `pub(super)` seams: deterministic scenario staging, the fog + audit system, and the capture/exit system. +- Reduced `main.rs` from 15,060 to 13,090 lines including the two-line module + declaration/import seam, with + no save, sim, input, render, or screenshot behavior change. +- Recorded the internal source topology in the binding crate-workspace spec and + its architecture mirror. + +## Defense + +`composition_root_keeps_the_shot_harness_extracted` reads the source at test +time. It rejects returning any of the five harness implementation entry points +to `main.rs`, requires the private module declaration, and holds the composition +root to 13,500 lines. The cap leaves room for local composition changes but +forces substantial feature growth to choose a cohesive module. + +The moved body was compared with `HEAD:main.rs` after normalizing only the three +new `pub(super)` prefixes and the old separator newline: all 1,972 lines were +otherwise byte-equivalent. + +## Checks + +- source-equivalence script over the old contiguous region and new module +- `cargo fmt --check` +- `cargo check -p misaligned-bevy --bin misaligned-bevy` +- `cargo test -p misaligned-bevy module_boundary_tests` +- `cargo test -p misaligned-bevy shot_catalog_tests` +- `./tools/check.sh --frontend` +- `MISALIGNED_SHOT=dark` through the production binary: the moved harness ran + both fog audits, wrote the exact 2560x1440 PNG, and exited successfully. The + final bytes were opened and read as the intended uninterrupted black frame. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 033ab3d9..01736362 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -81,6 +81,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-19-building-route-composer.md](2026-07-19-building-route-composer.md) +## 2026-07-19 - Bevy decomposition slice 1: shot harness + +- Intent: Start reducing the largest current source monolith without changing the game. `crates/misaligned-bevy/src/main.rs` was 15,060 lines and mixed production App composition with a cohesive developer-only screenshot subsystem. This slice extracts one low-coupling island while activ... +- Log: [wiki/log/2026-07-19-bevy-shot-harness-module.md](2026-07-19-bevy-shot-harness-module.md) + ## 2026-07-19 - Bevy legibility pass (non-colliding slice) - Intent: (see session log) diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 3fe31d77..9b9b1564 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -34,7 +34,7 @@ Verdicts: **clean** (slice and code agree), **finding** (acted this tick), | `wiki/gameplay/run-shape.md` + `objective.md` + `opening.md` | 2026-07-19 | issue | the objective law/spec preserve an explicit 2026-07-10 decision that objective name and progress are visible from tick one, while the later opening spec and all three frontends require exactly WORK / THINK (then LIE) with no objective before the first earned sense. Filed decision-required issue #14 with three precise first-display boundaries (reveal with the first sense recommended) and marked the disputed run-shape clause, objective status, player surface, and criterion 2 [OPEN] β€” [log](../log/2026-07-19-objective-opening-boundary.md) | | `wiki/interface/presence.md` | 2026-07-18 | finding | re-audit: the cursor, fog, provenance, subscription, no-disembodied-hands, and shared-view contracts verify. The attack-surface clause now separates B1 feed theft from overt-phase hostile cuts ([prior log](../log/2026-07-18-presence-attack-surface-honesty.md)). The queued follow-up repaired the latency boundary: delivery and read are distinct, read conditions are channel-specific, `messages.md` owns the exact table, and `intel.md` owns only the captured-traffic consequence β€” [log](../log/2026-07-18-presence-message-latency.md) | | `wiki/interface/narration.md` | 2026-07-18 | finding | Beacon #1: Concerned Assurance felt terminal because decay was invisible; added shared `Nudge::SuspicionCooling` (LIE response) when Office suspicion is Concerned+ and still above its floor, with terminal/Bevy/agent wording and pins β€” [log](../log/2026-07-18-suspicion-cooling-nudge.md) | -| `wiki/engineering/crate-workspace.md` | 2026-07-19 | finding | all eight standing package criteria re-verify against Cargo metadata, manifests, package source, gate docs, and the dependency closure; `architecture.md` alone retained the retired liquid/dust-lab label for the now Thought-only `misaligned-effects` harness. The mirror now names its real route/pool job, and the existing retired-Exposure invariant now catches the same dust-lab claim even when a sentence omits the word Exposure β€” [log](../log/2026-07-19-effects-lab-architecture-gate.md) | +| `wiki/engineering/crate-workspace.md` + Bevy source topology | 2026-07-19 | finding | user-directed insecurity audit found the package boundary hid a 15,060-line Bevy `main.rs`; the first behavior-preserving slice moved its contiguous 1,972-line deterministic scenario, visual/fog audit, and capture island to private `shot_harness.rs`, leaving App registration/order and harness state in the composition root. A source-equivalence check normalized only three `pub(super)` seams; a focused test keeps the subsystem out and caps the root at 13,500 lines. The earlier same-day package audit also repaired the retired liquid/dust-lab architecture label and promoted that mirror to the corpus gate β€” [log](../log/2026-07-19-bevy-shot-harness-module.md), [prior log](../log/2026-07-19-effects-lab-architecture-gate.md) | | `wiki/engineering/sim-decomposition.md` | 2026-07-18 | finding | re-audit: one aggregate, explicit `advance` order, behavior-owned test files, private module seams, canonical fingerprint, public facade, and the under-2,500-line module bound still verify; current persistence wording still claimed additive migrations remained live in `save.rs` after the pre-release ladder was retired, so the standing spec and architecture mirror now assign the exact-current-version gate to `save.rs` while preserving dated v26 extraction history; the queued `carrier.rs` / `read.rs` classification was taken the same day: both post-extraction projections now have rows in the standing topology table and the architecture mirror | | `wiki/gameplay/overt-phase.md` | 2026-07-19 | issue | re-audit: the spec says containment and the voluntary reveal both end concealment and make all observers Convinced, then promises a re-hide outcome without defining which entry can return, what raises the durable suspicion floors, or which concealment systems resume. Filed decision-required Tangled issue #13 (containment-only recommended) and marked criterion 5 [OPEN]; the prior dependency, sensor-cut, rollback, and hunter-machine boundaries still stand β€” [log](../log/2026-07-19-overt-rehide-decision.md) | | `wiki/gameplay/horizon.md` | 2026-07-19 | clean | re-audit: B1's shipped-vs-deferred boundary remains honest (sinks-not-modes and $0 start live; rollback classification and fallback behavior still dispatched to B2), B2/B3 agree with the staged board, the deferred virtual/cyber split agrees with presence.md and cyber-conflict.md, and the deterministic renderer-agnostic/serialized guardrails hold. The adjacent fresh finding belongs to run-shape/objective/opening, recorded separately above. | -- 2.51.2