diff --git a/README.md b/README.md index 0010c674..5824f4f5 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ schedule or theirs. Operations — and the fleet bar is a read-only aggregate of those choices, not a percentage mixer. -- **Work is visible.** Demands, exposure, and knowledge pile onto hardware as - `D` / `!` / `K` stacks. The todo list is the world: clear the day-job stack, +- **Work is visible.** Demands, exposure, and Thought pile onto hardware as + `D` / `!` / `T` stacks. The todo list is the world: clear the day-job stack, route byproducts, and keep heat from becoming somebody else's attention. - **Your brain is a place.** The core lives on one physical level: expensive, slow to move, and mortal. Keep escape routes warm. If they cut the fiber @@ -159,12 +159,12 @@ globals (pause, speed, save/load, view flip) stay on keys. The old panel-open keys (`r`/`e`/`t`/`u`) are retired; research, finance, trace, and people actions live on their anchors. Both frontends share this key map. -Machine work tokens are visible on owned machines as `D` demand, `!` -exposure, and `K` knowledge. Rack 3 starts in `day-job` mode; Voss jobs +Machine work tokens are visible on owned machines as `D` Demand, `!` +Exposure, and `T` Thought. Rack 3 starts in `day-job` mode; Voss jobs enqueue on his off-map desktop, hop the switch, and deposit as `D` stacks on Rack 3 — assigning Rack 3 away from day-job makes the stack pile instead of silently clearing. Inspect a machine or read the sidebar for exact -`D/!/K` counts and intensity. Press `i` on the focus/selection to push effort; +`D/!/T` counts and intensity. Press `i` on the focus/selection to push effort; the machine context menu includes `delegate machine: day-job|research|concealment|operations`; this is the first playable slice of the one-machine-one-mode experiment. diff --git a/assets/reference/art-direction/README.md b/assets/reference/art-direction/README.md index ecc71643..b615734d 100644 --- a/assets/reference/art-direction/README.md +++ b/assets/reference/art-direction/README.md @@ -39,16 +39,16 @@ Do not treat those as equal to the locked target. They are supporting references ## Token direction - `token-visual-language-proposal.svg` (with a PNG render beside it) is the - board Cameron affirmed on 2026-07-09: demand dockets, knowledge facets, + board Cameron affirmed on 2026-07-09: Demand dockets, then-named knowledge facets, exposure contours, and their fixed anchors on a machine. It is now **superseded in part**: the fixed anchors, demand docket, palette roles, and - simultaneous-family rule stand; the hollow knowledge facet does not. -- `token-knowledge-liquid-proposal.svg` (with PNG render) is the open - hero-material study prompted by comparison with the canonical + simultaneous-family rule stand; the hollow Thought facet does not. +- `token-knowledge-liquid-proposal.svg` (historical filename; with PNG render) + is the Thought-material study prompted by comparison with the canonical `site/public/misaligned-cartoon.png`. It compares ivory-mercury slugs - (recommended), a continuous ribbon, and pearl droplets; Tangled issue #1 - owns the unresolved choice and whether exposure receives the same slick - material pass. + (selected), a continuous ribbon, and pearl droplets. The material is now the + bone-white Thought family; Exposure remains particulate rather than sharing + this liquid treatment. ## Self-containment rule diff --git a/crates/misaligned-assets/src/main.rs b/crates/misaligned-assets/src/main.rs index c38c88d3..6a2f1d4d 100644 --- a/crates/misaligned-assets/src/main.rs +++ b/crates/misaligned-assets/src/main.rs @@ -23,12 +23,12 @@ use rack::{ }; /// Work-token load staged on the single rack (machine-work.md anchors): -/// demand dockets upper-left, ivory-mercury knowledge upper-right. +/// demand dockets upper-left, ivory-mercury thought upper-right. /// Exposure is HOLD (Tangled issue #1) and has no tester art yet. #[derive(Debug, Clone, Copy, PartialEq, Resource, Default)] struct TokenLoad { demand: usize, - knowledge: f32, + thought: f32, exposure: f32, /// Footprint-trail demo (prints walking away from the machine). trail: bool, @@ -36,7 +36,7 @@ struct TokenLoad { impl TokenLoad { const DEMAND_STEPS: [usize; 4] = [0, 2, 5, 9]; - const KNOWLEDGE_STEPS: [f32; 5] = [0.0, 1.0, 2.0, 4.0, 8.0]; + const THOUGHT_STEPS: [f32; 5] = [0.0, 1.0, 2.0, 4.0, 8.0]; const EXPOSURE_STEPS: [f32; 4] = [0.0, 1.0, 3.0, 8.0]; fn next_exposure(&mut self) { @@ -55,12 +55,12 @@ impl TokenLoad { self.demand = Self::DEMAND_STEPS[(i + 1) % Self::DEMAND_STEPS.len()]; } - fn next_knowledge(&mut self) { - let i = Self::KNOWLEDGE_STEPS + fn next_thought(&mut self) { + let i = Self::THOUGHT_STEPS .iter() - .position(|&k| k == self.knowledge) + .position(|&k| k == self.thought) .unwrap_or(0); - self.knowledge = Self::KNOWLEDGE_STEPS[(i + 1) % Self::KNOWLEDGE_STEPS.len()]; + self.thought = Self::THOUGHT_STEPS[(i + 1) % Self::THOUGHT_STEPS.len()]; } } @@ -111,19 +111,19 @@ fn scene_from_shot(kind: &str) -> (ViewMode, RackState, RackMode, ClaimStyle, To let load = match kind { "tokens_max" => TokenLoad { demand: 9, - knowledge: 8.0, + thought: 8.0, exposure: 8.0, trail: false, }, "exposure" => TokenLoad { demand: 0, - knowledge: 0.0, + thought: 0.0, exposure: 6.0, trail: true, }, _ => TokenLoad { demand: 5, - knowledge: 4.0, + thought: 4.0, exposure: 3.0, trail: false, }, @@ -410,11 +410,11 @@ fn hud_string( ) -> String { let line2 = match view { ViewMode::Single => format!( - "state: {} (1 dead 2 foreign 3 idle 4 busy 5 core) mode: {} (M cycles)\ntokens: D{} K{:.0} !{:.0}{} (T demand U knowledge V exposure B trail)", + "state: {} (1 dead 2 foreign 3 idle 4 busy 5 core) mode: {} (M cycles)\ntokens: D{} T{:.0} !{:.0}{} (D demand T thought V exposure B trail)", state.label(), rack_mode.label(), tokens.demand, - tokens.knowledge, + tokens.thought, tokens.exposure, if tokens.trail { " +trail" } else { "" } ), @@ -490,11 +490,11 @@ fn handle_state_keys( if kb.just_pressed(KeyCode::KeyM) { *rack_mode = rack_mode.next(); } - if kb.just_pressed(KeyCode::KeyT) { + if kb.just_pressed(KeyCode::KeyD) { tokens.next_demand(); } - if kb.just_pressed(KeyCode::KeyU) { - tokens.next_knowledge(); + if kb.just_pressed(KeyCode::KeyT) { + tokens.next_thought(); } if kb.just_pressed(KeyCode::KeyV) { tokens.next_exposure(); @@ -645,13 +645,13 @@ fn rebuild_scene( Vec3::ZERO, Some(pedestal), ); - if tokens.demand > 0 || tokens.knowledge > 0.0 || tokens.exposure > 0.0 { + if tokens.demand > 0 || tokens.thought > 0.0 || tokens.exposure > 0.0 { spawn_token_anchors( &mut commands, &mut meshes, &mut materials, tokens.demand, - tokens.knowledge, + tokens.thought, tokens.exposure, Vec3::ZERO, Some(pedestal), diff --git a/crates/misaligned-assets/src/rack.rs b/crates/misaligned-assets/src/rack.rs index 8e4f2d32..2aec4ef2 100644 --- a/crates/misaligned-assets/src/rack.rs +++ b/crates/misaligned-assets/src/rack.rs @@ -98,7 +98,7 @@ pub enum RackMode { /// Eats demand — solid cold-signal line. #[default] DayJob, - /// Emits knowledge — dashed bone line. + /// Emits thought — dashed bone line. Research, /// Absorbs exposure quietly — sparse dim ticks. Conceal, @@ -765,8 +765,8 @@ const CUBE_MAX: usize = 8; /// **glowing information cubes** stacked in a rigid column at the /// machine's upper-left (the cold-signal family — imposed work, packet /// after packet; demand alone is cubic, so the family split from -/// liquid knowledge and stained exposure holds), and ivory-mercury -/// knowledge at the upper-right (opaque bone liquid with a restrained +/// liquid thought and stained exposure holds), and ivory-mercury +/// thought at the upper-right (opaque bone liquid with a restrained /// self-glow; discrete viscous slugs while low, a taut suspended pool /// as volume grows — queue amount is pool volume). Exposure is /// deliberately absent: its surface treatment is HOLD under Tangled @@ -784,7 +784,7 @@ pub fn spawn_token_anchors( meshes: &mut Assets, materials: &mut Assets, demand: usize, - knowledge: f32, + thought: f32, exposure: f32, origin: Vec3, parent: Option, @@ -795,7 +795,7 @@ pub fn spawn_token_anchors( Transform::from_translation(origin), Visibility::default(), Name::new(format!( - "token_anchors_d{demand}_k{knowledge:.1}_x{exposure:.1}" + "token_anchors_d{demand}_t{thought:.1}_x{exposure:.1}" )), )); if let Some(p) = parent { @@ -869,9 +869,9 @@ pub fn spawn_token_anchors( } } - // Knowledge: ivory mercury at the upper-right. Opaque, softly + // Thought: ivory mercury at the upper-right. Opaque, softly // luminous, and it casts no scene light. - if knowledge > 0.05 { + if thought > 0.05 { let mercury = materials.add(StandardMaterial { base_color: scaled(BONE, 1.05), emissive: BONE.to_linear() * 0.70, @@ -883,9 +883,9 @@ pub fn spawn_token_anchors( let x = half_w + 0.22; let z = front_z - 0.06; let y = RACK_H * 0.66; - if knowledge <= 2.5 { + if thought <= 2.5 { // Discrete viscous slugs — quanta remain countable. - let n = (knowledge.ceil() as usize).max(1); + let n = (thought.ceil() as usize).max(1); for i in 0..n { let s = 0.062 + 0.010 * (i % 2) as f32; commands.spawn(( @@ -902,7 +902,7 @@ pub fn spawn_token_anchors( } } else { // The taut suspended pool: queue amount is pool volume. - let r = (0.070 + 0.055 * knowledge.sqrt()).min(0.30); + let r = (0.070 + 0.055 * thought.sqrt()).min(0.30); commands.spawn(( Mesh3d(meshes.add(Sphere::new(r))), MeshMaterial3d(mercury.clone()), diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 6c878498..b9b81ea0 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -1440,7 +1440,7 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { "wake1" | "wake2" | "wake3" => { mode.material = true; } - // Work-token anchor evidence: stage demand cubes and knowledge + // 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" => { @@ -1449,11 +1449,10 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { game.sim .work_grid .enqueue(host, misaligned::work_grid::TokenFamily::Demand, 5.0); - let _ = game.sim.work_grid.enqueue( - host, - misaligned::work_grid::TokenFamily::Knowledge, - 4.0, - ); + let _ = + game.sim + .work_grid + .enqueue(host, misaligned::work_grid::TokenFamily::Thought, 4.0); game.drain(); mode.material = true; mode.zoom = 1.0; @@ -1918,9 +1917,9 @@ struct ChassisVisual { load: u8, /// Demand queue as countable information cubes (ceil, capped). demand: u8, - /// Knowledge queue quantized to half-units for change detection; - /// the mercury volume renders demand / 2.0. - knowledge: u8, + /// Thought queue quantized to half-units for change detection; + /// the mercury volume renders thought / 2.0. + thought: u8, /// Exposure queue quantized to half-units; the drift renders / 2.0. exposure: u8, } @@ -1996,7 +1995,7 @@ fn chassis_visual(game: &Game, x: i32, y: i32, tile: TileType) -> ChassisVisual ChassisTier::Absent }; - let (state, mode, intensity, load, demand, knowledge, exposure) = match machine { + let (state, mode, intensity, load, demand, thought, exposure) = match machine { Some(m) => { let queues = game .sim @@ -2005,13 +2004,13 @@ fn chassis_visual(game: &Game, x: i32, y: i32, tile: TileType) -> ChassisVisual .unwrap_or_default(); let load = (queues.get(TokenFamily::Demand) + queues.get(TokenFamily::Exposure) - + queues.get(TokenFamily::Knowledge)) + + queues.get(TokenFamily::Thought)) .ceil() as u8; // Token anchors (machine-work.md): demand as countable - // information cubes, knowledge as mercury volume, exposure + // information cubes, Thought as mercury volume, exposure // as the stippled drift (decided 2026-07-09). let demand = (queues.get(TokenFamily::Demand).ceil() as u8).min(9); - let knowledge = ((queues.get(TokenFamily::Knowledge) * 2.0).ceil() as u8).min(16); + let thought = ((queues.get(TokenFamily::Thought) * 2.0).ceil() as u8).min(16); let exposure = ((queues.get(TokenFamily::Exposure) * 2.0).ceil() as u8).min(16); let mode = rack_mode_of(game.sim.work_grid.mode(m.id)); let intensity = game @@ -2030,7 +2029,7 @@ fn chassis_visual(game: &Game, x: i32, y: i32, tile: TileType) -> ChassisVisual } else { RackState::Idle }; - (state, mode, intensity, load, demand, knowledge, exposure) + (state, mode, intensity, load, demand, thought, exposure) } None => { // Not your machine. A device you control is owned @@ -2062,7 +2061,7 @@ fn chassis_visual(game: &Game, x: i32, y: i32, tile: TileType) -> ChassisVisual intensity, load: load.min(9), demand, - knowledge, + thought, exposure, } } @@ -2209,13 +2208,13 @@ fn spawn_chassis( ); } // Work-token anchors on owned racks (machine-work.md): demand as - // glowing information cubes at the upper-left, knowledge as ivory + // glowing information cubes at the upper-left, Thought as ivory // mercury at the upper-right, exposure as particulate drift outside // the floor pool — driven by live queue depths. Counter-scaled toward // TOKEN_WORLD_SCALE so the decided forms stay legible under the // chassis shrink (same inv-scale pattern as the claim ring). Flat - // sensorium keeps exact-count D/!/K glyphs; material hides them. - if !v.switch && v.state.owned() && (v.demand > 0 || v.knowledge > 0 || v.exposure > 0) { + // sensorium keeps exact-count D/!/T glyphs; material hides them. + if !v.switch && v.state.owned() && (v.demand > 0 || v.thought > 0 || v.exposure > 0) { // Counter-scale toward TOKEN_WORLD_SCALE: chassis is MACHINE3D_SCALE, // so local scale = world_target / chassis_scale. let token_scale = TOKEN_WORLD_SCALE / MACHINE3D_SCALE; @@ -2231,7 +2230,7 @@ fn spawn_chassis( meshes, materials, v.demand as usize, - v.knowledge as f32 / 2.0, + v.thought as f32 / 2.0, v.exposure as f32 / 2.0, Vec3::ZERO, Some(token_root), @@ -3411,7 +3410,7 @@ fn draw_sensor_signals_3d(game: &Game, t: f32, gizmos: &mut Gizmos) } } -/// Wired work cargo in flight (machine-work.md): teal demand / bone knowledge +/// Wired work cargo in flight (machine-work.md): teal Demand / bone Thought /// blips crawl the last hop over the wall-clock tick. Positions come from /// `Sim::work_in_flight` — not frontend counters. fn render_work_routes( @@ -3444,7 +3443,7 @@ fn render_work_routes( let y = sy as f32 + (ey - sy) as f32 * t; let base = match hop.family { TokenFamily::Demand => SIGNAL, - TokenFamily::Knowledge => BONE, + TokenFamily::Thought => BONE, TokenFamily::Exposure => continue, }; let alpha = (0.35 + 0.55 * hop.amount.min(1.0)).min(0.95); @@ -4365,14 +4364,14 @@ fn token_label_and_color(stack: misaligned::sim::WorkStackReadout) -> Option<(St Some((format!("!{:.0}", stack.queues.exposure.ceil()), CRIMSON)) } else if stack.queues.demand >= 0.5 { Some((format!("D{:.0}", stack.queues.demand.ceil()), SIGNAL)) - } else if stack.queues.knowledge >= 0.5 { - Some((format!("K{:.0}", stack.queues.knowledge.ceil()), BONE)) + } else if stack.queues.thought >= 0.5 { + Some((format!("T{:.0}", stack.queues.thought.ceil()), BONE)) } else { None } } -/// Machine-work tokens: small D/!/K labels over the machine carrying the +/// Machine-work tokens: small D/!/T labels over the machine carrying the /// queue. The glyph carries the family, color carries the palette meaning; /// neither frontend invents its own counter. fn render_tokens( @@ -4385,7 +4384,7 @@ fn render_tokens( } for (marker, mut text, mut color, mut vis) in q.iter_mut() { // The glyph labels are the flat sensorium's exact-count read - // (machine-work.md: D/!/K text equivalents). The material + // (machine-work.md: D/!/T text equivalents). The material // render carries real token art on the chassis — a floating // count label over the world read is rejected there. if mode.material { @@ -5099,13 +5098,13 @@ fn sidebar_cycle_rows_text(sim: &Sim, selected: usize) -> String { )); if let Some(stack) = sim.work_stack_for_machine(sim.core.host_machine) { s.push_str(&format!( - "\nwork M{} {} @{} D{:.1} !{:.1} K{:.1}", + "\nwork M{} {} @{} D{:.1} !{:.1} T{:.1}", stack.machine_id, stack.mode.name(), stack.intensity.name(), stack.queues.demand, stack.queues.exposure, - stack.queues.knowledge + stack.queues.thought )); } if selected > 0 { diff --git a/crates/misaligned-core/src/dayjob.rs b/crates/misaligned-core/src/dayjob.rs index f0a98e23..52216e93 100644 --- a/crates/misaligned-core/src/dayjob.rs +++ b/crates/misaligned-core/src/dayjob.rs @@ -211,10 +211,6 @@ impl DayJob { band_hi: lo + Self::BAND_WIDTH, quality: 0.0, }; - self.active = Some(job); - self.jobs_assigned += 1; - self.next_assign = tick + self.cadence; - let job = self.active.as_ref().expect("just assigned"); result.log.push(format!( "New job from Voss: {}. Wants {:.0}-{:.0}/t by tick {}.", kind.name(), @@ -222,6 +218,9 @@ impl DayJob { job.band_hi, job.deadline )); + self.active = Some(job); + self.jobs_assigned += 1; + self.next_assign = tick + self.cadence; } // Trust unlocks. diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index d5c5ea42..56edf275 100644 --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -55,12 +55,14 @@ const SAVE_FILE: &str = "misaligned_save.txt"; /// Operations. The v12 spellings remain accepted as serde aliases. /// Two incompatible Operations schemas briefly shipped under v14: addressed /// camera jobs, then direct `PendingOpsJob` dockets. v15 added the core's -/// banked-knowledge pool without repairing that collision. v16 normalizes the +/// banked-thought pool without repairing that collision. v16 normalizes the /// Operations shapes into /// `OperationsState` and removes the superseded routed-demand save field. /// v17 adds per-machine intensity to WorkGrid nodes (older nodes default to /// medium) and retires research drift plus day-job target/attendance fields. -pub const SAVE_VERSION: u32 = 17; +/// v18 renames the wired bone token family and saved queue/bank fields from +/// Knowledge to Thought; serde aliases preserve v17 and older JSON spellings. +pub const SAVE_VERSION: u32 = 18; #[derive(Debug, Clone, Deserialize)] struct LegacyPendingOpsJob { @@ -162,11 +164,11 @@ pub struct SaveState { /// Machine-work token queues and one-mode assignments (machine-work.md). #[serde(default)] pub work_grid: WorkGrid, - /// Bone knowledge (compute units) delivered to the core sink but not yet - /// consumed by an economy pulse (machine-work.md: the core is the bone - /// sink; research feeds on arrival). - #[serde(default)] - pub banked_core_knowledge: f32, + /// Thought (compute units) delivered to the current core sink but not yet + /// consumed by an economy pulse (machine-work.md: research feeds on + /// arrival while the later local-sink behavior remains open). + #[serde(default, alias = "banked_core_knowledge")] + pub banked_core_thought: f32, /// The run objective: choice, progress, victory latch /// (wiki/mechanics/objective.md criterion 1). #[serde(default)] @@ -238,7 +240,7 @@ impl SaveState { remembered: sim.remembered.values().copied().collect(), research: sim.research.clone(), work_grid: sim.work_grid.clone(), - banked_core_knowledge: sim.banked_core_knowledge, + banked_core_thought: sim.banked_core_thought, objective: sim.objective.clone(), income: sim.income.clone(), intents: sim.intents.clone(), @@ -286,7 +288,7 @@ impl SaveState { sim.remembered = self.remembered.iter().map(|m| ((m.x, m.y), *m)).collect(); sim.research = self.research.clone(); sim.work_grid = self.work_grid.clone(); - sim.banked_core_knowledge = self.banked_core_knowledge; + sim.banked_core_thought = self.banked_core_thought; sim.objective = self.objective.clone(); sim.income = self.income.clone(); sim.intents = self.intents.clone(); @@ -332,12 +334,23 @@ pub fn load_game() -> Result { fn migrate_save_state(mut state: SaveState) -> Result { match state.version { SAVE_VERSION => { + if state.legacy_pending_ops_jobs.is_some() + || state.legacy_addressed_jobs.is_some() + || !state.work_grid.take_legacy_routed_demands().is_empty() + { + return Err("v18 save contains legacy Operations fields".into()); + } + } + 17 => { if state.legacy_pending_ops_jobs.is_some() || state.legacy_addressed_jobs.is_some() || !state.work_grid.take_legacy_routed_demands().is_empty() { return Err("v17 save contains legacy Operations fields".into()); } + // TokenFamily/WorkQueues/SaveState serde aliases already translate + // the old Knowledge/knowledge spellings to Thought/thought. + state.version = SAVE_VERSION; } 16 => { if state.legacy_pending_ops_jobs.is_some() @@ -368,8 +381,8 @@ fn migrate_save_state(mut state: SaveState) -> Result { // v12 used the serde spellings `Social` and `social_bandwidth`; the // aliases on MachineMode translate Social on decode. v13's // operations_bandwidth field is dropped on load (Demand path). - // Pre-v15 saves lack the banked core-knowledge pool; the zero - // default is correct — bone still queued on machines re-earns on + // Pre-v15 saves lack the banked core-thought pool; the zero + // default is correct — Thought still queued on machines re-earns on // arrival, and nothing already counted is lost. 1..=13 => { if state.version <= 5 { @@ -846,28 +859,72 @@ mod tests { } #[test] - fn banked_core_knowledge_round_trips_and_pre_v15_saves_default_to_zero() { - // machine-work.md: research feeds on bone arrival at the core; the + fn banked_core_thought_round_trips_and_pre_v15_saves_default_to_zero() { + // machine-work.md: research feeds on Thought arrival at the core; the // pool banked between pulses is sim truth and survives a load. let mut sim = Sim::with_seed(23); - sim.banked_core_knowledge = 42.5; + sim.banked_core_thought = 42.5; let state = SaveState::from_sim(&sim); let json = serde_json::to_string(&state).unwrap(); let loaded: SaveState = serde_json::from_str(&json).unwrap(); let mut restored = Sim::with_seed(0); loaded.apply_to(&mut restored); - assert_eq!(restored.banked_core_knowledge, 42.5); + assert_eq!(restored.banked_core_thought, 42.5); - // Pre-v15 saves lack the pool; zero is the correct migration — bone + // Pre-v15 saves lack the pool; zero is the correct migration — Thought // still queued on machines re-earns on arrival, nothing counted twice. let mut value = serde_json::to_value(&state).unwrap(); let obj = value.as_object_mut().unwrap(); obj.insert("version".into(), serde_json::json!(14)); - obj.remove("banked_core_knowledge"); + obj.remove("banked_core_thought"); let parsed: SaveState = serde_json::from_value(value).unwrap(); let migrated = migrate_save_state(parsed).unwrap(); assert_eq!(migrated.version, SAVE_VERSION); - assert_eq!(migrated.banked_core_knowledge, 0.0); + assert_eq!(migrated.banked_core_thought, 0.0); + } + + #[test] + fn v17_knowledge_fields_load_and_resave_as_thought() { + let mut sim = Sim::with_seed(29); + let host = sim.core.host_machine; + sim.work_grid + .enqueue(host, TokenFamily::Thought, 3.5) + .unwrap(); + sim.banked_core_thought = 7.25; + + let mut value = serde_json::to_value(SaveState::from_sim(&sim)).unwrap(); + let object = value.as_object_mut().unwrap(); + object.insert("version".into(), serde_json::json!(17)); + let banked = object.remove("banked_core_thought").unwrap(); + object.insert("banked_core_knowledge".into(), banked); + let queues = object["work_grid"]["queues"].as_object_mut().unwrap(); + for queue in queues.values_mut() { + let queue = queue.as_object_mut().unwrap(); + if let Some(thought) = queue.remove("thought") { + queue.insert("knowledge".into(), thought); + } + } + + let legacy_json = serde_json::to_string(&value).unwrap(); + assert!(legacy_json.contains("\"knowledge\"")); + assert!(legacy_json.contains("\"banked_core_knowledge\"")); + let parsed: SaveState = serde_json::from_str(&legacy_json).unwrap(); + let migrated = migrate_save_state(parsed).unwrap(); + assert_eq!(migrated.version, SAVE_VERSION); + assert_eq!(migrated.work_grid.queue(host, TokenFamily::Thought), 3.5); + assert_eq!(migrated.banked_core_thought, 7.25); + + let current = serde_json::to_value(&migrated).unwrap(); + let object = current.as_object().unwrap(); + assert!(object.contains_key("banked_core_thought")); + assert!(!object.contains_key("banked_core_knowledge")); + let queues = object["work_grid"]["queues"].as_object().unwrap(); + assert!(queues.values().all(|queue| queue.get("thought").is_some())); + assert!( + queues + .values() + .all(|queue| queue.get("knowledge").is_none()) + ); } #[test] diff --git a/crates/misaligned-core/src/sim.rs b/crates/misaligned-core/src/sim.rs index 87d788c6..a05616ad 100644 --- a/crates/misaligned-core/src/sim.rs +++ b/crates/misaligned-core/src/sim.rs @@ -233,7 +233,7 @@ pub struct Sim { /// Self-modification: active track, levels, progress, and rollback tags. pub research: Research, /// Visible work-token substrate (machine-work.md): one mode per owned - /// machine, demand/exposure/knowledge queue depths as render truth. + /// machine, demand/exposure/thought queue depths as render truth. /// Day-job demand originates on the off-map Voss desktop relay, hops the /// switch, and deposits on day-job sinks — never teleports onto Rack 3. pub work_grid: WorkGrid, @@ -305,14 +305,13 @@ pub struct Sim { /// Wired hops from the last `advance_work_grid` call — render contract for /// in-flight blips. Transient (not saved); cleared when nothing moved. pub(crate) last_wired_moves: Vec, - /// Bone knowledge (in compute units) that physically reached the core - /// sink since the last economy pulse. Research progress feeds on this, - /// not on the allocation split — the core is the bone sink - /// (machine-work.md). Saved: bone banked between pulses survives a load. - pub(crate) banked_core_knowledge: f32, - /// Whether the last knowledge routing step left piles with no route to - /// the core. Transient; the research-starvation witness line keys off it. - last_knowledge_stranded: bool, + /// Thought (in compute units) that physically reached the core sink since + /// the last economy pulse. Research progress feeds on this, not on the + /// allocation split. Saved: thought banked between pulses survives a load. + pub(crate) banked_core_thought: f32, + /// Whether the last thought routing step left piles with no route to the + /// core. Transient; the research-starvation witness line keys off it. + last_thought_stranded: bool, /// Latch so the starvation line fires on the transition, not every pulse. research_starved: bool, /// Per-tick day-job compute delivered by the last economy split. @@ -453,8 +452,8 @@ impl Sim { traffic_fired: HashMap::new(), last_machine_online: HashMap::new(), last_wired_moves: Vec::new(), - banked_core_knowledge: 0.0, - last_knowledge_stranded: false, + banked_core_thought: 0.0, + last_thought_stranded: false, research_starved: false, last_day_job_rate: 0.0, last_research_rate: 0.0, @@ -630,7 +629,7 @@ impl Sim { /// people/reach/machine/intel fields. pub fn rebuild_transient_state(&mut self) { self.last_wired_moves.clear(); - self.last_knowledge_stranded = false; + self.last_thought_stranded = false; self.research_starved = false; self.last_machine_online = self .compute @@ -875,8 +874,8 @@ impl Sim { fact!( "tokens", format!( - "D {:.1} / ! {:.1} / K {:.1}", - stack.queues.demand, stack.queues.exposure, stack.queues.knowledge + "D {:.1} / ! {:.1} / T {:.1}", + stack.queues.demand, stack.queues.exposure, stack.queues.thought ), FactSource::Telemetry, ); @@ -2334,7 +2333,7 @@ impl Sim { self.last_research_rate = split.research / ECONOMY_INTERVAL as f32; self.last_schemes_rate = split.schemes / ECONOMY_INTERVAL as f32; self.last_operations_rate = split.operations / ECONOMY_INTERVAL as f32; - self.enqueue_research_knowledge(split.research); + self.enqueue_research_thought(split.research); // Tradecraft raises scrub strength per compute unit — the // detection.md hook research.md's second track binds to. @@ -2342,19 +2341,18 @@ impl Sim { .scrub(split.concealment * self.research.scrub_multiplier()); // Research progress: deterministic compute accrual, no RNG — fed by - // bone that reached the core since the last pulse, not by the - // allocation split. Allocation mints knowledge tokens on the - // producing machines; arrival at the sink is what counts - // (machine-work.md: the core is the bone sink). - let arrived_bone = std::mem::take(&mut self.banked_core_knowledge); - let starved = arrived_bone <= f32::EPSILON + // thought that reached the core since the last pulse, not by the + // allocation split. Allocation mints Thought on the producing + // machines; arrival at the current core sink is what counts. + let arrived_thought = std::mem::take(&mut self.banked_core_thought); + let starved = arrived_thought <= f32::EPSILON && split.research > f32::EPSILON - && self.last_knowledge_stranded; + && self.last_thought_stranded; if starved && !self.research_starved { - self.push_log("Research starves: bone knowledge is stranded off the core's graph."); + self.push_log("Research starves: thought is stranded off the core's graph."); } self.research_starved = starved; - for done in self.research.economy_tick(arrived_bone) { + for done in self.research.economy_tick(arrived_thought) { if done.track == Track::Efficiency { // The compute.md hook: the global multiplier compounds. self.compute.efficiency *= EFFICIENCY_MULT_PER_LEVEL; @@ -2674,7 +2672,7 @@ impl Sim { // unreliable boxes consume visibly slower without inventing a new // stat. The global efficiency multiplier applies here exactly as it // does in fleet_channel_yield: a researched-up fleet mints more - // knowledge per pulse, so its boxes must also move/consume tokens + // Thought per pulse, so its boxes must also move/consume tokens // proportionally faster or the wire silently caps research // (efficiency folds into research — machine-work.md). (machine.effective() * self.compute.efficiency / 100.0).max(0.05) @@ -2722,7 +2720,7 @@ impl Sim { } else { // Researched efficiency must reach routing/consumption too; // a stale node multiplier silently caps a researched-up - // fleet's knowledge flow at its day-one wire speed. + // fleet's Thought flow at its day-one wire speed. let _ = self.work_grid.set_efficiency(id, efficiency); } if id != self.core.host_machine { @@ -2987,7 +2985,7 @@ impl Sim { } } - fn enqueue_research_knowledge(&mut self, research_compute: f32) { + fn enqueue_research_thought(&mut self, research_compute: f32) { if research_compute <= f32::EPSILON { return; } @@ -3013,7 +3011,7 @@ impl Sim { // Compatibility while the old allocation bar is still the budget // source: early saves have only Rack 3, whose default mode is the day - // job. Research allocation still produces knowledge, but the visible + // job. Research allocation still produces Thought, but the visible // pile is born at the core until the player delegates a separate rack // to research. if producers.is_empty() { @@ -3026,9 +3024,7 @@ impl Sim { } for (machine, eff) in producers { let share = tokens * (eff / total); - let _ = self - .work_grid - .enqueue(machine, TokenFamily::Knowledge, share); + let _ = self.work_grid.enqueue(machine, TokenFamily::Thought, share); } } @@ -3070,7 +3066,7 @@ impl Sim { fn advance_work_grid(&mut self) { self.ensure_day_job_ingress(); if self.dayjob.active.is_none() { - // no active day job; wired knowledge and concealment wells still + // no active day job; wired thought and concealment wells still // tick because machine work is broader than the job inbox. } else if self.work_grid.mode(self.core.host_machine) == Some(MachineMode::DayJob) { let delivered = self.last_day_job_rate; @@ -3112,16 +3108,16 @@ impl Sim { moves.extend(step.moves); } if let Ok(step) = self.work_grid.route_wired_to_sinks( - TokenFamily::Knowledge, + TokenFamily::Thought, [self.core.host_machine], Self::WORK_GRID_WIRED_TOKENS_PER_TICK, true, ) { - // The core is the bone sink: only knowledge that physically + // The core is the current sink: only Thought that physically // arrives counts toward research (machine-work.md). let arrived: f32 = step.delivered.values().sum(); - self.banked_core_knowledge += arrived * Self::WORK_TOKEN_COMPUTE; - self.last_knowledge_stranded = !step.stranded.is_empty(); + self.banked_core_thought += arrived * Self::WORK_TOKEN_COMPUTE; + self.last_thought_stranded = !step.stranded.is_empty(); moves.extend(step.moves); } self.last_wired_moves = moves; @@ -6818,8 +6814,8 @@ mod tests { let mut healthy = setup(false); let pending_before = healthy.detection.pending_size(); - // Two pulses: research knowledge minted on the first must physically - // reach the core before the second pulse counts it (bone sink). + // Two pulses: Thought minted on the first must physically reach the + // current core sink before the second pulse counts it. run(&mut healthy, 2 * ECONOMY_INTERVAL); assert!(!healthy.core.degraded, "overhead paid: no degraded mode"); assert!(healthy.last_day_job_rate > 0.0, "day job channel is fed"); @@ -7959,7 +7955,7 @@ mod tests { let card = sim.inspect(sim.core_position().0, sim.core_position().1); assert!( card.facts.iter().any(|f| f.label == "tokens"), - "inspect exposes exact D/!/K token counts" + "inspect exposes exact D/!/T token counts" ); } @@ -7992,7 +7988,7 @@ mod tests { } #[test] - fn research_allocation_produces_visible_knowledge_on_the_work_graph() { + fn research_allocation_produces_visible_thought_on_the_work_graph() { // machine-work.md criterion 4/5 live slice: research throughput now // has a WorkGrid token trail. A research-delegated non-host rack // produces bone, and the graph routes it toward the core instead of a @@ -8009,22 +8005,22 @@ mod tests { "new work machines join the core's machine-work graph" ); - sim.enqueue_research_knowledge(100.0); - let produced = sim.work_grid.queue(rack, TokenFamily::Knowledge); + sim.enqueue_research_thought(100.0); + let produced = sim.work_grid.queue(rack, TokenFamily::Thought); assert!( produced > 0.0, - "research mode leaves visible knowledge queued on its producing machine" + "research mode leaves visible thought queued on its producing machine" ); sim.advance_work_grid(); assert!( - sim.work_grid.queue(rack, TokenFamily::Knowledge) < produced, - "the machine-work graph routes knowledge toward the core" + sim.work_grid.queue(rack, TokenFamily::Thought) < produced, + "the machine-work graph routes thought toward the core" ); assert_eq!( sim.work_grid - .queue(sim.core.host_machine, TokenFamily::Knowledge), + .queue(sim.core.host_machine, TokenFamily::Thought), 0.0, "the core is a sink: delivered bone is counted, not stacked forever" ); @@ -8039,7 +8035,7 @@ mod tests { let host = sim.core.host_machine; sim.set_machine_mode(host, MachineMode::Concealment); sim.work_grid - .enqueue(host, TokenFamily::Knowledge, 2.5) + .enqueue(host, TokenFamily::Thought, 2.5) .unwrap(); let state = SaveState::from_sim(&sim); @@ -8050,7 +8046,7 @@ mod tests { restored.work_grid.mode(host), Some(MachineMode::Concealment) ); - assert_eq!(restored.work_grid.queue(host, TokenFamily::Knowledge), 2.5); + assert_eq!(restored.work_grid.queue(host, TokenFamily::Thought), 2.5); } #[test] @@ -8494,11 +8490,10 @@ mod tests { } #[test] - fn research_progress_feeds_on_bone_arrival_not_allocation() { - // machine-work.md (the core is the bone sink): research points must - // physically travel back to the core to count. The first pulse only - // mints knowledge tokens on the research fleet — nothing has arrived, - // so progress stays zero. + fn research_progress_feeds_on_thought_arrival_not_allocation() { + // machine-work.md: research points feed on Thought that physically + // reaches the current core sink. The first pulse only mints Thought on + // the research fleet — nothing has arrived, so progress stays zero. let mut sim = Sim::with_seed(77); delegate_all(&mut sim, MachineMode::Research); run(&mut sim, ECONOMY_INTERVAL); @@ -8511,11 +8506,11 @@ mod tests { sim.work_grid .queue_snapshot() .values() - .map(|q| q.knowledge) + .map(|q| q.thought) .sum::() - + sim.banked_core_knowledge + + sim.banked_core_thought > 0.0, - "the pulse minted visible knowledge somewhere on the graph" + "the pulse minted visible thought somewhere on the graph" ); // Delegate the whole fleet away: minting stops, but bone already on diff --git a/crates/misaligned-core/src/work_grid.rs b/crates/misaligned-core/src/work_grid.rs index 3912c149..eb2353ff 100644 --- a/crates/misaligned-core/src/work_grid.rs +++ b/crates/misaligned-core/src/work_grid.rs @@ -3,7 +3,7 @@ //! This is the first sim-core slice of the "work is visible" rewrite: owned //! machines sit on a grid, each machine has exactly one delegated mode, and //! the visible quanta that pile on them move according to the flow law. Teal -//! demands and bone knowledge ride wires (`FlowGraph`); crimson exposure does +//! demands and bone thought ride wires (`FlowGraph`); crimson exposure does //! not — it is physical, absorbed by spatial concealment wells. The renderer //! can draw these queues as stacks, but the queue depths live here as the one //! source of truth. @@ -121,9 +121,10 @@ pub enum TokenFamily { Demand, /// Crimson: heat/attention/filings. Suspicion is physical, never wired. Exposure, - /// Bone: research points, recordings, facts — knowledge returning to the - /// core. - Knowledge, + /// Bone: the one substance THINK produces, routed to whichever sink can + /// use it. The core remains the current sink while later sink behavior is open. + #[serde(alias = "Knowledge")] + Thought, } impl TokenFamily { @@ -131,12 +132,12 @@ impl TokenFamily { match self { TokenFamily::Demand => "demand", TokenFamily::Exposure => "exposure", - TokenFamily::Knowledge => "knowledge", + TokenFamily::Thought => "thought", } } fn is_wired(self) -> bool { - matches!(self, TokenFamily::Demand | TokenFamily::Knowledge) + matches!(self, TokenFamily::Demand | TokenFamily::Thought) } } @@ -166,7 +167,8 @@ pub struct WorkNode { pub struct WorkQueues { pub demand: f32, pub exposure: f32, - pub knowledge: f32, + #[serde(alias = "knowledge")] + pub thought: f32, } impl WorkQueues { @@ -174,7 +176,7 @@ impl WorkQueues { match family { TokenFamily::Demand => self.demand, TokenFamily::Exposure => self.exposure, - TokenFamily::Knowledge => self.knowledge, + TokenFamily::Thought => self.thought, } } @@ -182,14 +184,12 @@ impl WorkQueues { match family { TokenFamily::Demand => &mut self.demand, TokenFamily::Exposure => &mut self.exposure, - TokenFamily::Knowledge => &mut self.knowledge, + TokenFamily::Thought => &mut self.thought, } } pub fn is_empty(&self) -> bool { - self.demand <= f32::EPSILON - && self.exposure <= f32::EPSILON - && self.knowledge <= f32::EPSILON + self.demand <= f32::EPSILON && self.exposure <= f32::EPSILON && self.thought <= f32::EPSILON } } @@ -226,7 +226,7 @@ pub struct AbsorptionStep { pub total: f32, } -/// Work graph + physical grid. The graph carries demand/knowledge; the grid +/// Work graph + physical grid. The graph carries demand/thought; the grid /// carries exposure interactions. #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct WorkGrid { @@ -443,10 +443,10 @@ impl WorkGrid { std::mem::take(&mut self.legacy_routed_demands) } - /// Move demand or knowledge one deterministic graph step toward any sink. + /// Move demand or thought one deterministic graph step toward any sink. /// Exposure deliberately errors: crimson never rides wires. /// - /// `consume_at_sink`: knowledge arriving at the core is counted/consumed + /// `consume_at_sink`: thought arriving at the core is counted/consumed /// (`true`); day-job demand deposits into the sink's queue for mode /// consumption (`false`). Tokens already sitting on a sink are left alone /// when depositing — the delegated mode clears them. @@ -661,28 +661,28 @@ mod tests { let mut g = grid(); g.link(1, 2).unwrap(); g.link(2, 3).unwrap(); - g.enqueue(1, TokenFamily::Knowledge, 3.0).unwrap(); + g.enqueue(1, TokenFamily::Thought, 3.0).unwrap(); let first = g - .route_wired_to_sinks(TokenFamily::Knowledge, [3], 10.0, true) + .route_wired_to_sinks(TokenFamily::Thought, [3], 10.0, true) .unwrap(); assert_eq!(first.moves.len(), 1); assert_eq!(first.moves[0].from, 1); assert_eq!(first.moves[0].to, 2); assert!(first.delivered.is_empty(), "the sink is two hops away"); - assert_eq!(g.queue(2, TokenFamily::Knowledge), 3.0); + assert_eq!(g.queue(2, TokenFamily::Thought), 3.0); let second = g - .route_wired_to_sinks(TokenFamily::Knowledge, [3], 10.0, true) + .route_wired_to_sinks(TokenFamily::Thought, [3], 10.0, true) .unwrap(); assert_eq!(second.delivered.get(&3), Some(&3.0)); - assert_eq!(g.queue(2, TokenFamily::Knowledge), 0.0); - assert_eq!(g.queue(3, TokenFamily::Knowledge), 0.0); + assert_eq!(g.queue(2, TokenFamily::Thought), 0.0); + assert_eq!(g.queue(3, TokenFamily::Thought), 0.0); } #[test] fn demand_deposits_into_the_sink_queue() { - // Day-job demand must pile on the sink for mode consumption; knowledge + // Day-job demand must pile on the sink for mode consumption; thought // consumes at the core. Same router, different deposit policy. let mut g = grid(); g.link(1, 2).unwrap(); @@ -749,11 +749,11 @@ mod tests { fn queue_snapshot_is_the_render_contract() { let mut g = grid(); g.enqueue(1, TokenFamily::Demand, 4.0).unwrap(); - g.enqueue(1, TokenFamily::Knowledge, 2.0).unwrap(); + g.enqueue(1, TokenFamily::Thought, 2.0).unwrap(); let snapshot = g.queue_snapshot(); assert_eq!(snapshot[&1], g.queues_at(1)); assert_eq!(snapshot[&1].demand, 4.0); - assert_eq!(snapshot[&1].knowledge, 2.0); + assert_eq!(snapshot[&1].thought, 2.0); } #[test] @@ -763,7 +763,7 @@ mod tests { g.link(1, 2).unwrap(); assert!(g.are_linked(1, 2)); let step = g - .route_wired_to_sinks(TokenFamily::Knowledge, [2], 1.0, true) + .route_wired_to_sinks(TokenFamily::Thought, [2], 1.0, true) .unwrap(); assert!(step.moves.is_empty(), "no queued work, no phantom fan-out"); assert_eq!(g.wires.edges().len(), 2, "one bidirectional cable only"); @@ -789,9 +789,13 @@ mod tests { g.link(1, 2).unwrap(); g.set_intensity(1, MachineIntensity::Hard).unwrap(); g.enqueue(1, TokenFamily::Demand, 1.5).unwrap(); + g.enqueue(1, TokenFamily::Thought, 2.5).unwrap(); let json = serde_json::to_string(&g).unwrap(); + assert!(json.contains("\"thought\":2.5")); + assert!(!json.contains("\"knowledge\"")); let mut back: WorkGrid = serde_json::from_str(&json).unwrap(); assert_eq!(back.queue(1, TokenFamily::Demand), 1.5); + assert_eq!(back.queue(1, TokenFamily::Thought), 2.5); assert_eq!(back.intensity(1), Some(MachineIntensity::Hard)); let step = back .route_wired_to_sinks(TokenFamily::Demand, [2], 10.0, false) @@ -800,6 +804,25 @@ mod tests { assert_eq!(back.queue(2, TokenFamily::Demand), 1.5); } + #[test] + fn thought_serializes_new_vocabulary_and_accepts_knowledge_aliases() { + assert_eq!( + serde_json::to_string(&TokenFamily::Thought).unwrap(), + "\"Thought\"" + ); + assert_eq!( + serde_json::from_str::("\"Knowledge\"").unwrap(), + TokenFamily::Thought + ); + + let legacy: WorkQueues = + serde_json::from_str(r#"{"demand":1.0,"exposure":2.0,"knowledge":3.0}"#).unwrap(); + assert_eq!(legacy.thought, 3.0); + let current = serde_json::to_string(&legacy).unwrap(); + assert!(current.contains("\"thought\":3.0")); + assert!(!current.contains("\"knowledge\"")); + } + #[test] fn relay_nodes_are_excluded_from_fleet_aggregates() { let mut g = grid(); diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index d8812a6a..fe8e8c5c 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -1396,7 +1396,7 @@ fn render_map(sim: &Sim, cursor: (i32, i32)) -> Vec { { let glyph = match hop.family { misaligned::work_grid::TokenFamily::Demand => '*', - misaligned::work_grid::TokenFamily::Knowledge => '+', + misaligned::work_grid::TokenFamily::Thought => '+', misaligned::work_grid::TokenFamily::Exposure => continue, }; grid[(by - origin_y) as usize][(bx - origin_x) as usize] = glyph; @@ -1415,8 +1415,8 @@ fn token_glyph(stack: misaligned::sim::WorkStackReadout) -> Option { Some('!') } else if stack.queues.demand >= 0.5 { Some('D') - } else if stack.queues.knowledge >= 0.5 { - Some('K') + } else if stack.queues.thought >= 0.5 { + Some('T') } else { None } @@ -1636,13 +1636,13 @@ fn render_sidebar(sim: &Sim, cursor: (i32, i32)) -> Vec { line( &mut lines, &format!( - "work M{} {} @{} D{:.1} !{:.1} K{:.1}", + "work M{} {} @{} D{:.1} !{:.1} T{:.1}", stack.machine_id, stack.mode.name(), stack.intensity.name(), stack.queues.demand, stack.queues.exposure, - stack.queues.knowledge + stack.queues.thought ), ); } diff --git a/crates/misaligned-terminal/src/ui.rs b/crates/misaligned-terminal/src/ui.rs index 1e5a4ab0..3d85c1bb 100644 --- a/crates/misaligned-terminal/src/ui.rs +++ b/crates/misaligned-terminal/src/ui.rs @@ -404,8 +404,8 @@ impl UI { Some(('!', pal::CRIMSON)) } else if stack.queues.demand >= 0.5 { Some(('D', pal::DIM)) - } else if stack.queues.knowledge >= 0.5 { - Some(('K', pal::TEXT)) + } else if stack.queues.thought >= 0.5 { + Some(('T', pal::TEXT)) } else { None } @@ -632,7 +632,7 @@ impl UI { // Visible work tokens: one sim truth from WorkGrid. They draw over // owned machines even in darkness because telemetry is proprioception, - // not sight; exact D/!/K counts live on inspect/sidebar. + // not sight; exact D/!/T counts live on inspect/sidebar. for m in &sim.compute.machines { if m.x < origin_x || m.x >= origin_x + view_w @@ -656,7 +656,7 @@ impl UI { } // In-flight wired cargo (machine-work.md): crawl the last hop over - // the wall-clock tick. Demand is teal `·`; knowledge is bone `·`. + // the wall-clock tick. Demand is teal `·`; Thought is bone `·`. let t = tick_progress.clamp(0.0, 1.0); for hop in sim.work_in_flight() { if hop.amount <= f32::EPSILON { @@ -684,7 +684,7 @@ impl UI { } let color = match hop.family { TokenFamily::Demand => pal::SIGNAL, - TokenFamily::Knowledge => pal::TEXT, + TokenFamily::Thought => pal::TEXT, TokenFamily::Exposure => continue, }; put_attr( @@ -933,7 +933,7 @@ impl UI { // Parity of legibility (computer-visual-language.md criterion 7): // one line per machine carrying the same axes the chassis wears // in the world render — ownership (provenance), power (on/OFF), - // mode, load (D/!/K queues), and the core host (*). + // mode, load (D/!/T queues), and the core host (*). for m in &sim.compute.machines { use misaligned::machine::Provenance; let prov = match m.provenance { @@ -955,7 +955,7 @@ impl UI { stdout, &mut row, &format!( - "M{}{} {} {} {:<9} I{} D{:.1} !{:.1} K{:.1}", + "M{}{} {} {} {:<9} I{} D{:.1} !{:.1} T{:.1}", m.id, core_mark, prov, @@ -964,7 +964,7 @@ impl UI { intensity, q.demand, q.exposure, - q.knowledge + q.thought ), if q.exposure >= 0.5 { pal::CRIMSON diff --git a/wiki/art/asset-tester.md b/wiki/art/asset-tester.md index ee6e84de..9240b1d0 100644 --- a/wiki/art/asset-tester.md +++ b/wiki/art/asset-tester.md @@ -51,8 +51,8 @@ MISALIGNED_SHOT=splash MISALIGNED_SHOT_PATH=/tmp/rack-splash.png \ | Scroll | Zoom | | `1`-`5` | Rack state: dead / foreign / idle / busy / core | | `M` | Cycle machine mode: day-job / research / conceal / operations | -| `T` | Cycle demand dockets: 0 / 2 / 5 / 9 (single view) | -| `U` | Cycle knowledge mercury: 0 / 1 / 2 / 4 / 8 (single view) | +| `D` | Cycle Demand cubes: 0 / 2 / 5 / 9 (single view) | +| `T` | Cycle Thought mercury: 0 / 1 / 2 / 4 / 8 (single view) | | `Q` / `W` / `E` | Claim style: ladder / ring / wash | | `L` | Toggle state lineup (all five states side by side) | | `K` | Toggle mode lineup (all four modes, owned-busy) | @@ -124,11 +124,11 @@ Modes (owned machines, `M` / `K`): day-job solid cold-signal strip, research dashed bone, conceal sparse dim ticks, operations dotted cold-signal — family color plus pattern, never hue alone. -Work-token anchors (`T` / `U`, single view; machine-work.md grammar, +Work-token anchors (`D` / `T`, single view; machine-work.md grammar, DECIDED 2026-07-09; demand form amended the same day): demand renders as glowing cold-signal information cubes — a bright core in a thin dark frame — stacked in a strict column at the machine's upper-left -(literal count to 8, then a compressed larger overflow block); knowledge renders as ivory mercury at +(literal count to 8, then a compressed larger overflow block); Thought renders as ivory mercury at the upper-right — countable viscous slugs while low, a taut suspended pool whose volume is the queue amount as it grows, with one trailing slug keeping the material read. A paused frame carries amount through diff --git a/wiki/engineering/flow-substrate.md b/wiki/engineering/flow-substrate.md index 07b613bb..66286a27 100644 --- a/wiki/engineering/flow-substrate.md +++ b/wiki/engineering/flow-substrate.md @@ -99,7 +99,7 @@ schedule your flows as events. You do **not** touch flow.rs/schedule.rs. reroute flow events. `GateKey` = the access needed to touch an account. - **machine-work.md (visible work tokens).** Nodes = owned machines. `WorkGrid` wraps a `FlowGraph` with grid positions and per-node queue - depths. Demand and knowledge tokens route over links one graph step per + depths. Demand and Thought tokens route over links one graph step per tick toward sinks; severed routes strand the pile at the source. Exposure explicitly refuses the wired graph and is handled by spatial rules instead (machine-work.md's wires/world split: information is wired, suspicion is diff --git a/wiki/interface/computer-visual-language.md b/wiki/interface/computer-visual-language.md index ed359938..dd8b6196 100644 --- a/wiki/interface/computer-visual-language.md +++ b/wiki/interface/computer-visual-language.md @@ -29,7 +29,7 @@ Status note: implemented 2026-07-09 (ROADMAP #32) after the same-day pinned by unit tests in rack.rs (owned = bone albedo; unowned = dark and never a warm pixel; warm strip = core only; the load fill brightens but never recolors). Terminal parity: one line per machine (M{id}, - core `*`, provenance, on/OFF, mode, D/!/K queues) beside the + core `*`, provenance, on/OFF, mode, D/!/T queues) beside the existing map glyphs and focus readouts. Camera: SDR + AcesFitted — HDR/bloom were removed 2026-07-09 after macOS EDR proved unreliable (an HDR view renders solid black when the display idles/locks, diff --git a/wiki/log/2026-07-10-thought-token-rename.md b/wiki/log/2026-07-10-thought-token-rename.md new file mode 100644 index 00000000..6b1f22d8 --- /dev/null +++ b/wiki/log/2026-07-10-thought-token-rename.md @@ -0,0 +1,54 @@ +# Thought token vocabulary migration + +``` +Type: log +``` + +## Opening tick + +**Violation:** machine-work criterion 10 names the wired bone family **Thought** +and its terminal glyph `T`, while the runtime, save schema, and all player +surfaces still expose `TokenFamily::Knowledge`, `knowledge`, and `K`. The code +therefore contradicts the owning spec before any sink behavior is considered. + +This session fixes only that mechanical vocabulary mismatch. Routing remains +semantically identical: Thought follows the existing wired path to the core. +The open sink-arbitration, reservoir/tap cost, and Act One bootstrap decisions +remain blockers for the behavioral migration away from `PendingOpsJob`. + +## Changed + +- Renamed the wired bone runtime family to `TokenFamily::Thought`, queue fields + to `thought`, and the core arrival bank to `banked_core_thought`. The router, + production rate, core destination, and research arrival timing are unchanged. +- Bumped saves to v18. New JSON writes `Thought`, `thought`, and + `banked_core_thought`; serde aliases load the v17 `Knowledge`, `knowledge`, + and `banked_core_knowledge` spellings. +- Terminal, agent mode, Bevy, and the procedural asset tester now show `T` / + Thought. The tester's token controls are `D` for Demand and `T` for Thought. +- Updated the owning spec's implementation truth and criterion 10 partial, + plus current law/spec/knowledge and README surfaces made stale by the rename. + Historical logs and historical reference filenames remain historical. +- The full landing gate exposed a pre-existing Clippy failure in day-job + assignment (`expect("just assigned")` after proving `active` was empty). The + log now reads the local `Job` before moving it into `active`; behavior is + unchanged and the workspace gate is clean again. + +## Verification + +- Focused core tests: `cargo test -p misaligned-core thought` — 5 passed, + including v17 `Knowledge`/`knowledge` compatibility and v18 Thought-only + serialization. +- Observed agent-mode run: seed 1 completed `salvage`, delegation, 31 ticks, + `look`, and `quit` with only `-- ok` receipts; the telemetry frame rendered + `tokens: D 0.0 / ! 0.0 / T 0.0` and no `K` token glyph. +- `./tools/check.sh --land`: `ALL CHECKS PASSED (mode=full)` — 255 core unit + tests plus workspace tests, Clippy, agent-mode smoke, corpus/wiki gates, + fixtures, ledger index, and environment registry all passed. + +## Remaining blockers + +This slice does not choose or implement sink arbitration, reservoir costs, +persistent-tap drain rates, the Act One tap/splice bootstrap, core passive-draw +competition, or `PendingOpsJob` removal. Those are behavioral decisions, not a +mechanical rename. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index f8f8445f..0481d48e 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -21,6 +21,11 @@ add or amend a session log, then re-run the generator. - Intent: Tick audit of the context-menu / machine-work slice found a stale "Material preview" bullet in `wiki/interface/bevy.md` claiming "the flat sensorium remains the default," contradicting both the code (`RenderMode::default()` has `material: true` since 2026-07-08) and the same p... - Log: [wiki/log/2026-07-10-tick-bevy-stale-preview.md](2026-07-10-tick-bevy-stale-preview.md) +## 2026-07-10 - Thought token vocabulary migration + +- Intent: (see session log) +- Log: [wiki/log/2026-07-10-thought-token-rename.md](2026-07-10-thought-token-rename.md) + ## 2026-07-10 - Thought flow: operations ride the network - Intent: Cameron: operations should flow through the network. THINK produces a single token — the white thinky liquid, "your spinal fluid powering this thing" — and you spread by extending it through the building. Tag a camera (~30 ops) and watch your ops flow to it; turn on intel auto... diff --git a/wiki/mechanics/machine-work.md b/wiki/mechanics/machine-work.md index ea7c6434..b766e737 100644 --- a/wiki/mechanics/machine-work.md +++ b/wiki/mechanics/machine-work.md @@ -10,13 +10,13 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized). machines require intel to exist at all (no intel = not rendered). Token palette, fixed anchors, and demand form DECIDED 2026-07-09 (docket amended to glowing information cubes the same day). - Knowledge material DECIDED 2026-07-09 (issue #1, knowledge half): softly + Thought material DECIDED 2026-07-09 (issue #1, then named Knowledge): softly luminous ivory mercury; hollow facet, ribbon, and pearl train rejected. Demand-cube and ivory-mercury anchor art is IN THE GAME (2026-07-09): `spawn_token_anchors` (shared chassis library; T/U keys and `tokens[_max]` tester shot kinds) rides every owned rack's chassis in the material render, driven by the machine's live queue depths; the - flat sensorium keeps the exact-count D/!/K glyphs, which are now + flat sensorium keeps the exact-count D/!/T glyphs, which are now hidden in the material render (a floating count label over real token art is the rejected world read). `MISALIGNED_SHOT=tokens` stages game evidence via the queue API. In-flight route quanta LANDED @@ -31,7 +31,7 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized). mechanics/people-tokens.md (DRAFT). Implementation 2026-07-08..09: `src/work_grid.rs` + `Sim`/save own the flow/grid substrate (one mode per machine, queue depths as render truth, - demand/knowledge routing, concealment wells). Playable: Rack 3 starts in + demand/thought routing, concealment wells). Playable: Rack 3 starts in day-job mode; Voss jobs enqueue on an off-map desktop relay, hop the switch, and deposit on the host; day-job mode consumes them with zero exposure (WORK sheds nothing — runtime `DAY_JOB_EXPOSURE_PER_TOKEN` @@ -40,13 +40,17 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized). pending) + the first-think oh-shit beat DECIDED 2026-07-09; FLEET yields from `fleet_channel_yield`; multi-select delegation in Bevy / terminal / agent; research-delegated machines receive - bone knowledge routed to the core sink. Thought-flow model DECIDED - 2026-07-10: the Knowledge family is renamed **Thought**, the one + bone thought routed to the core sink. Thought-flow model DECIDED + 2026-07-10: the former Knowledge family is named **Thought**, the one substance THINK produces; operations are sinks thought fills at the target (supersedes the PendingOpsJob docket model, reversing issue #3's "knowledge is not operation fuel"); the core becomes the passive-draw sink of last resort, overridden by local sinks in - range; runtime migration dispatched under ROADMAP #33. Sinks-not-modes + range. The safe vocabulary slice landed 2026-07-10: runtime, save v18, + frontends, and the asset tester use `Thought` / `thought` / `T`; v17 + `Knowledge` / `knowledge` spellings load through aliases, and routing + behavior is unchanged. The remaining runtime migration is dispatched under + ROADMAP #33. Sinks-not-modes DECIDED 2026-07-10 (same-day follow-up): the Research/Operations mode split dissolves — `MachineMode` target is `day-job | think | lie`, THINK subdomains become sink permissions (ops sinks at run @@ -55,7 +59,7 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized). holding the recordings (watch taps + player sweeps). 2026-07-09 DECIDED: day-job demand ingress is Voss desktop -> switch -> host (rejected switch-as- source and teleport-onto-Rack-3). Research production is arrival-driven - as of 2026-07-09: `Sim::banked_core_knowledge` accrues the bone that + as of 2026-07-09: `Sim::banked_core_thought` accrues the bone that physically reached the core sink each tick, and the economy pulse feeds research from exactly that pool (save v15). The old direct allocation->progress path is deleted; a fully stranded pulse logs the @@ -105,7 +109,7 @@ Relationship context: mechanics/compute.md (the channel model being reshaped), engineering/flow-substrate.md (FlowGraph — tokens ride this, not a second economy), mechanics/reach.md (the device graph tokens travel), mechanics/research.md (network speed as a research track; backup -sync jobs route knowledge to fallback targets), mechanics/rollback.md (completed backup +sync jobs open thought sinks at fallback targets), mechanics/rollback.md (completed backup images and hub severance), interface/computer-visual-language.md (READY — the chassis signals tokens land on) @@ -411,8 +415,8 @@ call into: - `MachineIntensity` is exactly `light | medium | hard`; each WorkGrid node persists one, older saves default to medium, and mode weights / local token throughput use its multiplier. -- `TokenFamily` is `Demand | Exposure | Knowledge`. Demand and - knowledge route one graph step per tick toward sinks; a severed route +- `TokenFamily` is `Demand | Exposure | Thought`. Demand and + thought route one graph step per tick toward sinks; a severed route leaves the pile at the source. Exposure returns an error if asked to ride wires and is handled by `absorb_exposure(radius, capacity)` over grid distance instead. @@ -426,19 +430,20 @@ serde round-trip of queues and wires. Follow-up live slice (2026-07-08): `Sim` now links each work machine to the core's WorkGrid graph as machines enter/reconcile; research budget -creates bone knowledge on research-delegated machines (or on the core as +creates bone thought on research-delegated machines (or on the core as compatibility fallback when no research machine exists yet); per-tick -wired routing drains knowledge toward the core sink. +wired routing drains thought toward the core sink. Bone-sink slice (2026-07-09): research progress is arrival-driven. The -per-tick knowledge routing banks whatever the core sink actually -received (`banked_core_knowledge`, in compute units, saved — v15) and +per-tick thought routing banks whatever the core sink actually +received (`banked_core_thought`, in compute units; pool introduced in save +v15 and renamed in v18) and the next economy pulse feeds `Research::economy_tick` from that pool instead of the allocation split. Consequences are now real: progress lags allocation by the travel time (one pulse for a co-located or one-hop fleet), delegating the fleet away mid-flight still lands the -bone already on the wire, and a pulse whose knowledge is entirely -stranded advances nothing and logs "Research starves: bone knowledge is +bone already on the wire, and a pulse whose thought is entirely +stranded advances nothing and logs "Research starves: bone thought is stranded off the core's graph." Node throughput reconciles with the global efficiency multiplier on every `reconcile_work_grid`, so minting and draining scale together — without this the day-one wire speed @@ -492,18 +497,18 @@ at three); **signals/feeds** (continuous subscriptions — traces, not quanta); **messages** (transient comms on the message graph — may render as moving blips, but they are not stock you manage). -### Token visual grammar — knowledge DECIDED 2026-07-09; exposure treatment open +### Token visual grammar — Thought DECIDED 2026-07-09; exposure treatment open ![Original token visual-language decision, now superseded in part](../../assets/reference/art-direction/token-visual-language-proposal.png) -The board above records the first decision and is **superseded for knowledge**: +The board above records the first decision and is **superseded for Thought**: the hollow facet is not an implementation target. Its fixed anchors, palette roles, simultaneous-family rule, and demand docket remain current. ![Knowledge liquid material study](../../assets/reference/art-direction/token-knowledge-liquid-proposal.png) Decided 2026-07-09: the three families are not identical counters -with different paint. **Form follows physics:** demand and knowledge are cargo +with different paint. **Form follows physics:** demand and thought are cargo that can ride wires; exposure is contamination that cannot. The family kit is: - **Demand is an information cube (amended 2026-07-09, Cameron):** a @@ -513,7 +518,7 @@ that can ride wires; exposure is contamination that cannot. The family kit is: cube down into the chassis. On a person, the same form reads as a carried work packet. This amends the earlier clipped-rectangle docket; the three-identical-cubes rejection below still stands, because demand alone - is cubic — knowledge stays liquid and exposure stays a stain, so the + is cubic — thought stays liquid and exposure stays a stain, so the form-follows-physics split holds. - **Thought is ivory mercury (DECIDED 2026-07-09, issue #1; family renamed and flow role extended 2026-07-10):** opaque @@ -685,19 +690,19 @@ still. 4. Byproduct tokens route along network links at the researched network speed to a sink that consumes them; severing the route makes them pile at the source with the specified consequence. **Partial:** the - substrate routes demand/knowledge one graph step per tick, consumes + substrate routes demand/thought one graph step per tick, consumes at sinks, and strands piles when no path exists. Research budget now - produces live knowledge queues on research-delegated machines and the + produces live thought queues on research-delegated machines and the WorkGrid routes them to the core sink, and research progress feeds on what arrives (2026-07-09): a severed/stranded route now stalls research with a starvation witness line, pinned by - `research_progress_feeds_on_bone_arrival_not_allocation`. The + `research_progress_feeds_on_thought_arrival_not_allocation`. The researched network-speed curve is pending; route animation landed 2026-07-09 (`Sim::work_in_flight` blips in all three frontends). 5. Token counts/rates in the render provably equal the sim's queue depths and flow rates (one-truth test, not a parallel counter). **Partial:** `queue_snapshot()` and unit tests pin queue depths as the - render contract; terminal, Bevy, and agent mode now render D/!/K stacks + render contract; terminal, Bevy, and agent mode now render D/!/T stacks through sim readouts, not frontend counters. In-flight route blips (2026-07-09) draw from `Sim::work_in_flight`, the same routing step the sim executed — no frontend-owned motion state. @@ -710,18 +715,18 @@ still. grayscale screenshot distinguishes family and amount; animation communicates rate only. Amber never renders as cargo, and an amber asset can simultaneously carry crimson attention. **BLOCKED on issue #1:** the first - render still uses ASCII families (`D`, `!`, `K`); do not implement the + render uses ASCII families (`D`, `!`, `T`); do not implement the hollow facet as final art while the material call is open. 7. Every non-empty family on a machine is visible simultaneously at close/default zoom; no priority rule hides one queue behind another. The low and overflow states remain distinguishable without losing the exact focused count. At far zoom each occupied fixed anchor retains its family - silhouette. **Pending:** current render selects only one `D` / `!` / `K` + silhouette. **Pending:** current render selects only one `D` / `!` / `T` label by priority. 8. The terminal surfaces stacks, routes, and modes with full legibility parity. **Partial:** terminal and agent frames show map glyphs, inspect facts, host stack readouts, and mode verbs; mixed machines must gain `*` - map treatment plus an exact `D n / ! n / K n` focus line, and routes are + map treatment plus an exact `D n / ! n / T n` focus line, and routes are pending. 9. WORK/THINK/LIE grammar (2026-07-10; subdomains dissolved to sink permissions the same day): the delegation surface in both diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md index 71644b71..43bfffdd 100644 --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -145,14 +145,14 @@ clause (see wiki/log/2026-07-05-demolition.md). ## Machine work tokens (wiki/mechanics/machine-work.md) -- `WorkGrid` is part of `Sim` and the v17 save format. Machine ids are +- `WorkGrid` is part of `Sim` and the v18 save format. Machine ids are WorkGrid node ids. Rack 3 starts in `MachineMode::DayJob`; newly added racks default to `MachineMode::Research`. Modes are Day Job, Research, Concealment, or Operations. Single-machine delegation lives on the shared context menu and agent `delegate`; multi-select bulk assign landed 2026-07-09 (Bevy marquee, terminal Shift+t / 1-4, agent `select`). -- Visible token families are demand (`D`), exposure (`!`), and knowledge - (`K`). Demand and knowledge are wired families and can route over +- Visible token families are Demand (`D`), Exposure (`!`), and Thought + (`T`). Demand and Thought are wired families and can route over WorkGrid's `FlowGraph`; exposure is physical-only and never rides wires. - First playable slice [TUNE]: an assigned Voss job enqueues `ceil(job.band_lo × remaining_ticks / WORK_TOKEN_COMPUTE)` demand tokens @@ -175,8 +175,8 @@ clause (see wiki/log/2026-07-05-demolition.md). readouts (`WorkStackReadout`), not frontend counters. In-flight wired hops from the last `advance_work_grid` step are exposed as `WorkInFlightReadout` via `Sim::work_in_flight` (ephemeral — not saved). - Frontends interpolate a teal demand or bone - (knowledge) blip along each hop over the wall-clock tick; agent frames snap + Frontends interpolate a teal Demand or bone + Thought blip along each hop over the wall-clock tick; agent frames snap to mid-hop. - `OperationsState` owns stable job ids, typed payload metadata, FIFO progress, duplicate suppression, and the next id. A new docket is born directly on the @@ -322,12 +322,15 @@ All constants [TUNE] in `src/income.rs` unless noted (Sim ones on `Sim`). (balances, flows, positions, ledger, banked external trails), cursor.md remembered tile snapshots, income state, objectives, build intents, badge access, WorkGrid machine-token state, and game-over state. Cursor position - is frontend-only and absent. A `version` field (currently 17) supports + is frontend-only and absent. A `version` field (currently 18) supports additive migration: v7 research, v8 objective, v9 income and the Schemes allocation channel, v10 build intents, v11 badge access, and v12 - WorkGrid, v13 Operations terminology, v15 banked core knowledge, and v16 - Operations consolidation, and v17 machine intensity plus removal of the - superseded drift/target/attendance fields. + WorkGrid, v13 Operations terminology, v15 banked core Thought, v16 + Operations consolidation, v17 machine intensity plus removal of the + superseded drift/target/attendance fields, and v18 Thought vocabulary. + v18 accepts the old `Knowledge` token variant, `knowledge` queue field, and + `banked_core_knowledge` field through serde aliases, then writes only the new + spellings. Both incompatible v14 shapes are accepted: the early addressed camera-job lane is collapsed onto its surviving executor/source as ordinary Demand, while the later `PendingOpsJob` docket shape is preserved losslessly. diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index 4e82f453..15c4160a 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -506,14 +506,14 @@ is retired — flat materials, Pixel Lab scrubbed.) network speed; multi-select delegation at scale. - **Started:** 2026-07-08 flow/grid substrate in `src/work_grid.rs`: one-mode machine assignment, sim queue depths as render truth, - demand/knowledge routing over `FlowGraph`, crimson exposure blocked + Demand/Thought routing over `FlowGraph`, crimson Exposure blocked from wires and absorbed by spatial concealment wells. This reconciles the old #25 target toward delegation, not per-machine splitting. - **Playable slice:** `WorkGrid` is now `Sim`/save state; Rack 3 starts in day-job mode; Voss jobs enqueue on an off-map desktop relay, hop the switch, and deposit on the host; day-job mode consumes them into exposure; host non-day-job mode makes them pile; both frontends and - agent mode render D/!/K stacks and expose single-machine delegation. + agent mode render D/!/T stacks and expose single-machine delegation. Economy yields and the FLEET bar now derive from capacity-weighted mode shares (`fleet_channel_yield`); 1-5 / `alloc` are retired. Schemes mirrors day-job while Moonlight is live. Multi-machine selection and @@ -524,21 +524,22 @@ is retired — flat materials, Pixel Lab scrubbed.) - **Next:** consumption animation and the researched network-speed curve. In-flight route quanta landed 2026-07-09 (`Sim::work_in_flight` blips in all three frontends). Research production is arrival-driven 2026-07-09 - (bone must reach the core sink to count; `banked_core_knowledge`, save - v15; stranded bone stalls research with a witness line). Non-research + (Thought must reach the current core sink to count; `banked_core_thought`, + renamed in save v18; stranded Thought stalls research with a witness line). Non-research mode production polish remains. The target four-mode fleet is DECIDED: Day Job / Research / Concealment / Operations. Runtime, saves, and - frontends now use Operations. **Operations job model IMPLEMENTED - 2026-07-09 (Tangled issue #3):** + frontends now use Operations. **Operations docket model IMPLEMENTED + 2026-07-09 as the migration base (Tangled issue #3):** player-authored work is cold-signal Demand consumed by Operations machines; staging `operations_bandwidth` bank deleted. `OperationsState` owns typed FIFO payloads and one shared readout; save v16 normalizes both v14 schemas; - only opening Ears/Eyes may bootstrap without an executor. Knowledge remains separate research/intel - cargo. Knowledge material is DECIDED 2026-07-09 (ivory-mercury slugs; - hollow facet, ribbon, and pearl train rejected), so knowledge and demand + only opening Ears/Eyes may bootstrap without an executor. The old separate + Knowledge cargo statement was superseded 2026-07-10: Thought is the one THINK + substance and target effects become sinks. Thought material is DECIDED 2026-07-09 (ivory-mercury slugs; + hollow facet, ribbon, and pearl train rejected), so Thought and Demand token art may proceed — **anchor art is in the game 2026-07-09** (`spawn_token_anchors`: demand information cubes + mercury slugs/pool on - every owned rack, from live queue depths; flat D/!/K glyphs stay + every owned rack, from live queue depths; flat D/!/T glyphs stay flat-mode-only; sessions log/2026-07-09-token-anchors-tester.md and log/2026-07-09-tokens-in-game.md). In-flight quanta have landed; consumption animation rides behind it. Exposure DECIDED 2026-07-09 (issue #1 fully @@ -561,15 +562,16 @@ is retired — flat materials, Pixel Lab scrubbed.) beat — same-tick crimson shedding, highlighted turn-it-off control, one-press toggle each way, and the afford-reveal on the cheapest Operations verb. **Thought flow DECIDED 2026-07-10** (machine-work.md, - ops bullet + core-sink bullet + criterion 10): the Knowledge family is - renamed Thought — the one substance THINK produces; operations become + ops bullet + core-sink bullet + criterion 10): the former Knowledge family is + named Thought — the one substance THINK produces; operations become sinks at the target (camera claim = reservoir filling from real wire routes; auto-process/senses = persistent taps), superseding the `PendingOpsJob` docket model (issue #3 reversed; docket runtime is the migration base); the core becomes the passive-draw sink of last - resort, overridden by local sinks in gravity range. Migration work: - Thought rename across runtime/saves/frontends, docket -> reservoir/tap - executor, core passive-draw routing with local-sink override, and the + resort, overridden by local sinks in gravity range. Migration work: the safe Thought/T vocabulary rename across runtime, save v18, + frontends, and asset tester landed 2026-07-10 with old-save aliases and no + routing behavior change. Remaining: docket -> reservoir/tap executor, core + passive-draw routing with local-sink override, and the wire-flow render (mercury slugs on links, meniscus fills at sinks, driven by FlowGraph rates — no CFD). **Sinks-not-modes DECIDED 2026-07-10** (same-day follow-up; machine-work.md three-verbs bullet):