diff --git a/crates/misaligned-bevy/src/consumption.rs b/crates/misaligned-bevy/src/consumption.rs new file mode 100644 index 00000000..914f299d --- /dev/null +++ b/crates/misaligned-bevy/src/consumption.rs @@ -0,0 +1,101 @@ +//! Final token-swallow motion for machine-work consumption events. +//! +//! The sim owns which token left which queue (`Sim::work_consumptions`). This +//! module only interpolates that event over the frontend's wall-clock tick; +//! it never compares queue snapshots or keeps a second amount counter. + +use bevy::prelude::*; +use misaligned::sim::WorkConsumptionTarget; +use misaligned::work_grid::TokenFamily; + +use super::{BONE, Game, RealGizmos, RenderMode, SIGNAL, SimClock, TILE_SIZE}; + +/// Draw the bottom Demand cube pulling into an executor and the last Thought +/// slug collapsing into the passive core draw. A paused frame hides the +/// transient swallow: amount remains legible in the persistent stack/sink +/// geometry, while motion communicates rate only (machine-work.md). +pub(super) fn render_work_consumptions( + game: Res, + clock: Res, + mode: Res, + mut flat: Gizmos, + mut real: Gizmos, +) { + if game.screen != super::Screen::Playing || game.paused { + return; + } + + let progress = clock.timer.fraction().clamp(0.0, 1.0); + for event in game.sim.work_consumptions() { + if event.amount <= f32::EPSILON { + continue; + } + + let flat_center = Vec2::new( + event.x as f32 * TILE_SIZE + TILE_SIZE * 0.5, + -(event.y as f32 * TILE_SIZE + TILE_SIZE * 0.5), + ); + + match (event.family, event.target) { + (TokenFamily::Demand, WorkConsumptionTarget::Machine) => { + // Mirrors rack.rs's upper-left Demand anchor. The packet bows + // inward as it descends so the read is "bottom cube inhaled" + // rather than a generic falling block. + let start = Vec3::new(event.x as f32 + 0.02, 1.23, event.y as f32 + 0.63); + let center = Vec3::new(event.x as f32 + 0.5, 0.46, event.y as f32 + 0.5); + let eased = progress * progress; + let mut pos = start.lerp(center, eased); + pos.y += (progress * std::f32::consts::PI).sin() * 0.06; + let edge = 0.14 * (1.0 - 0.58 * eased); + let alpha = (1.0 - 0.72 * progress).max(0.18); + let signal = SIGNAL.to_srgba(); + let color = Color::srgba(signal.red, signal.green, signal.blue, alpha); + real.primitive_3d( + &Cuboid::from_size(Vec3::splat(edge)), + Isometry3d::from_translation(pos), + color, + ); + // Bright contained core inside the dark frame read. + real.sphere( + Isometry3d::from_translation(pos), + edge * 0.28, + Color::srgba(signal.red, signal.green, signal.blue, alpha * 0.9), + ); + + if !mode.material { + let from = flat_center + Vec2::new(-TILE_SIZE * 0.28, -TILE_SIZE * 0.28); + let to = from.lerp(flat_center, eased); + flat.line_2d(from, to, color); + flat.circle_2d(to, 2.2 * (1.0 - 0.5 * eased), color); + } + } + (TokenFamily::Thought, WorkConsumptionTarget::Core) => { + // The route slug already reaches this tile. This final bead + // folds from the upper-right Thought anchor into the core and + // contracts as it becomes research compute. + let start = Vec3::new(event.x as f32 + 0.99, 1.40, event.y as f32 + 0.63); + let center = Vec3::new(event.x as f32 + 0.5, 0.72, event.y as f32 + 0.5); + let eased = progress * progress * (3.0 - 2.0 * progress); + let pos = start.lerp(center, eased); + let radius = (0.055 + 0.025 * event.amount.min(1.5)) * (1.0 - 0.72 * eased); + let bone = BONE.to_srgba(); + let color = Color::srgba( + bone.red, + bone.green, + bone.blue, + (1.0 - 0.68 * progress).max(0.20), + ); + real.line(start.lerp(center, progress * 0.78), pos, color); + real.sphere(Isometry3d::from_translation(pos), radius.max(0.012), color); + + if !mode.material { + let from = flat_center + Vec2::new(TILE_SIZE * 0.28, -TILE_SIZE * 0.28); + let to = from.lerp(flat_center, eased); + flat.line_2d(from, to, color); + flat.circle_2d(to, 2.0 * (1.0 - 0.65 * eased), color); + } + } + _ => {} + } + } +} diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 80a89b92..f9a9fdf3 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -6,6 +6,8 @@ //! the B1 sidebar), and input. Text is ASCII-only so the //! default embedded font renders every glyph. +mod consumption; + use std::collections::{BTreeSet, HashMap, HashSet}; use bevy::asset::RenderAssetUsages; @@ -34,6 +36,8 @@ use misaligned_assets::rack::{ spawn_token_anchors, }; +use consumption::render_work_consumptions; + const TILE_SIZE: f32 = 16.0; const SIDEBAR_WIDTH: f32 = 420.0; const SIDEBAR_SCROLL_LINE: f32 = 18.0; @@ -1239,6 +1243,7 @@ fn main() { render_tokens, ), ( + render_work_consumptions, render_thought_sinks, render_people, render_people_3d, @@ -1322,6 +1327,40 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { game.drain(); 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; + } // 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 diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index a0f8ccab..5a7f9349 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -1074,12 +1074,13 @@ fn parse_on_off(tokens: &[&str]) -> Result { fn parse_track(tokens: &[&str]) -> Result { if tokens.len() != 2 { - return Err("usage: research [efficiency|tradecraft|perception]".into()); + return Err("usage: research [efficiency|tradecraft|perception|routing]".into()); } match tokens[1].to_ascii_lowercase().as_str() { "efficiency" | "eff" => Ok(Track::Efficiency), "tradecraft" | "trade" => Ok(Track::Tradecraft), "perception" | "perc" => Ok(Track::Perception), + "routing" | "route" => Ok(Track::Routing), other => Err(format!("unknown research track: {other}")), } } @@ -2503,6 +2504,12 @@ fn room_label(room: &str) -> &str { mod narration_tests { use super::*; + #[test] + fn routing_research_is_agent_selectable() { + assert_eq!(parse_track(&["research", "routing"]), Ok(Track::Routing)); + assert_eq!(parse_track(&["research", "route"]), Ok(Track::Routing)); + } + #[test] fn every_agent_panel_keeps_the_story_spine_visible() { let sim = Sim::with_seed(1); diff --git a/wiki/engineering/env.md b/wiki/engineering/env.md index af61836a..c9a6ab28 100644 --- a/wiki/engineering/env.md +++ b/wiki/engineering/env.md @@ -38,7 +38,7 @@ is sim or frontend state, never an environment variable. | Variable | Surface | Values | Effect | |---|---|---|---| -| `MISALIGNED_SHOT` | `misaligned-bevy` | `flat`, `wide`, `close`, `dark`, `zoomin`, `zoomout`, `intel`, `tokens`, `signal`, `ears`, `eyes-white`, `eyes-form`, `wake1`, `wake2`, `wake3` | Dev screenshot harness: stage a deterministic scenario, settle, save one PNG, run the fog audit, exit. `intel` stages tick-zero intel tiers (no camera tap); `tokens` enqueues D5/K4 on the host; `signal` focuses the known environmental monitor before any feed tap to capture its cold-signal presence cue; `ears` stages hearing-feed coverage (material dark mass; flat may still diagram coverage); `eyes-white` / `eyes-form` freeze the first-Eyes source held white and then contracted around the resolving chassis; `wake1/2/3` freeze the wake choreography at the stutter flash, the column, and the pull-back. | +| `MISALIGNED_SHOT` | `misaligned-bevy` | `flat`, `wide`, `close`, `dark`, `zoomin`, `zoomout`, `intel`, `tokens`, `signal`, `ears`, `eyes-white`, `eyes-form`, `consume-demand`, `consume-thought`, `wake1`, `wake2`, `wake3` | Dev screenshot harness: stage a deterministic scenario, settle, save one PNG, run the fog audit, exit. `intel` stages tick-zero intel tiers (no camera tap); `tokens` enqueues D5/K4 on the host; `signal` focuses the known environmental monitor before any feed tap to capture its cold-signal presence cue; `ears` stages hearing-feed coverage (material dark mass; flat may still diagram coverage); `eyes-white` / `eyes-form` freeze the first-Eyes source held white and then contracted around the resolving chassis; `consume-demand` / `consume-thought` run the host through a sim-authored WORK queue swallow or passive-core Thought draw; `wake1/2/3` freeze the wake choreography at the stutter flash, the column, and the pull-back. | | `MISALIGNED_SHOT` | `misaligned-assets` | `dead`, `foreign`, `idle`, `busy`, `core`, `dayjob`, `research`, `conceal`, `operations` (each optionally suffixed `_ladder`/`_ring`/`_wash`), `lineup[_