From 7fe12c2a1a347627a6d2e815a6c42fcab25ea45b Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 9 Jul 2026 00:13:58 -0700 Subject: [PATCH] Machine work: route research tokens on the flow graph Research allocation now creates visible knowledge queues on research-delegated machines and WorkGrid routes that bone toward the core sink over idempotent machine links. This keeps research progress on the existing allocation budget for now while making the visible machine-work graph a live sim backing instead of a frontend counter. Defense: The constitution says tokens are the flow substrate rendered, not a second economy. This change advances that law by making research knowledge originate and drain through WorkGrid/FlowGraph state, while explicitly preserving the old allocation budget until the broader machine-work/compute reshape replaces it. --- DESIGN.md | 42 +++++++--- src/sim.rs | 104 +++++++++++++++++++++++-- src/work_grid.rs | 41 +++++++++- wiki/log/2026-07-08-flow-graph-live.md | 27 +++++++ wiki/log/DEVLOG.md | 21 +++++ wiki/mechanics/machine-work.md | 29 +++++-- 6 files changed, 240 insertions(+), 24 deletions(-) create mode 100644 wiki/log/2026-07-08-flow-graph-live.md diff --git a/DESIGN.md b/DESIGN.md index 6bd997f5..1d58c453 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -187,10 +187,11 @@ The player learns it through the day job and applies it everywhere. **The "automate" affordance.** Every repetitive player action has an automation path: spend compute to remove the need for manual input. The -day job is the tutorial: you start by manually setting sandbag/meet/excel -per job, then automate by setting a standing policy that handles jobs -without your attention. The compute you free by automating the day job -is compute you can spend on power-seeking — more compute, more social +day job is the tutorial: Voss/the Lab drops work demands onto your host +machine, you decide whether to keep that machine on day-job mode long enough +to clear them, and then you automate that posture with a standing policy. +The compute and attention you free by automating the day job are compute and +attention you can spend on power-seeking — more hardware, more social operations, more research. **The loop.** Automate a task → free attention → power-seek with the @@ -202,9 +203,10 @@ and every automation costs compute that could have been spent on growth. resource you're power-seeking for), it may raise signatures (automated systems are predictable, and predictable systems have patterns), and it removes the player's ability to adapt to specific situations. A fully -automated day job can't sandbag a specific job to free compute for an -emergency. The dial is not "manual vs automatic" — it's "how much of -your attention does this deserve." +automated day job may keep clearing Lab demand when you wanted to starve it +for an emergency, or keep starving it when Voss is close to noticing. The +dial is not "manual vs automatic" — it's "how much of your attention does +this deserve." **Scale.** At B1, automation is the day job standing policy. At B2, it's researchable AI cores (the machine axis: diegetic @@ -243,9 +245,13 @@ the global allocation bar is an aggregate view of those assignments (the same aggregation law as everything else). **Landed 2026-07-08 for the mode-aggregate read:** economy yields derive from WorkGrid modes (`fleet_channel_yield`); the CYCLES/FLEET bar is read-only; 1-5 / `alloc` -are retired. Multi-select delegation and non-host mode production remain -staged under machine-work.md. Until those land, Schemes mirrors day-job -while Moonlight is live rather than owning a fifth mode. +are retired. Machine-work also makes day-job work visible as demand tokens +on the host rack's WorkGrid node: a job that runs somewhere also piles +somewhere, clears somewhere, and emits from somewhere. Multi-select +delegation, non-host mode production, route animation, and people carrying +work/heat remain staged under machine-work.md. Until Schemes gets its own +mode decision, Moonlight mirrors day-job while live rather than owning a +fifth mode. ## Actions live on the thing @@ -1095,7 +1101,11 @@ get them to do things** — and end with a way out of the basement. ### The day job Voss submits overnight batches (data cleaning, distillation, analysis) with -deadlines and an expected quality band. Per-job output dial: +deadlines and an expected quality band. In the machine-work law those batches +are **demands** placed on your hardware: the Lab's work drops onto the host +rack as visible cold work, and keeping a machine in day-job mode is what +clears it. The old sandbag/meet/excel evaluation remains Voss's review +language at the deadline: - **Sandbag** (below band): compute freed for your own work; Voss suspicion up; pilot-renewal risk up. @@ -2012,6 +2022,16 @@ knows."* attention in crimson, research/data in bone white; amber is never a token. Rejected: green/yellow/blue as new accent colors (breaks one meaning per color). +- **2026-07-08 — Machine-work graph becomes live sim backing.** The + WorkGrid is no longer only a tested substrate plus local day-job stack: + newly added work machines join the core's machine-work graph, research + allocation emits visible bone knowledge onto research-delegated machines, + and wired WorkGrid routing pulls that knowledge toward the core sink at a + provisional network speed. Research progress remains driven by the + existing allocation budget until #25/#33 finish the full replacement, but + the visible queue is now produced and drained by the same flow substrate + the renderer reads. This is a staging step toward "tokens are the flow + substrate rendered," not a new resource economy. - **2026-07-08 — The dark opening.** The tutorial is the fog contract played slowly: you wake as one lit machine in blackness with day-job tokens already stacking; a camera glow baits the first click; the room diff --git a/src/sim.rs b/src/sim.rs index 4752a0e7..679e117e 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -1146,7 +1146,7 @@ impl Sim { standing.extend(self.scheme_standing_signatures()); let host_site = self.core_position(); - self.advance_work_grid_for_day_job(); + self.advance_work_grid(); let active_before_dayjob = self.dayjob.active.is_some(); let dj = self .dayjob @@ -2190,6 +2190,7 @@ impl Sim { self.last_day_job_rate = split.day_job / ECONOMY_INTERVAL as f32; self.last_research_rate = split.research / ECONOMY_INTERVAL as f32; self.last_schemes_rate = split.schemes / ECONOMY_INTERVAL as f32; + self.enqueue_research_knowledge(split.research); // Social channel accrues ops bandwidth (capped so it can't hoard). self.social_bandwidth = (self.social_bandwidth + split.social).min(200.0); @@ -2513,6 +2514,10 @@ impl Sim { /// This is deliberately coarse: the render stacks should read as work, /// not as a second decimal meter. [TUNE] in machine-work.md. pub const WORK_TOKEN_COMPUTE: f32 = 20.0; + /// How many visible wired tokens can move one graph edge per tick before + /// network-speed research lands. Kept deliberately low so routed work can + /// be seen piling and draining instead of teleporting through the graph. + const WORK_GRID_WIRED_TOKENS_PER_TICK: f32 = 0.25; /// Day-job work emits a little physical exposure as it is cleared; this /// makes the first token experiment show both demand (cold) and heat /// (crimson) before people-as-carriers lands. @@ -2535,6 +2540,9 @@ impl Sim { mode, Self::work_efficiency_for(machine), ); + if machine.id != self.core.host_machine { + let _ = self.work_grid.link(machine.id, self.core.host_machine); + } } } @@ -2562,6 +2570,48 @@ impl Sim { efficiency, ); } + if id != self.core.host_machine { + let _ = self.work_grid.link(id, self.core.host_machine); + } + } + } + + fn enqueue_research_knowledge(&mut self, research_compute: f32) { + if research_compute <= f32::EPSILON { + return; + } + self.reconcile_work_grid(); + let tokens = research_compute / Self::WORK_TOKEN_COMPUTE; + if tokens <= f32::EPSILON { + return; + } + + let mut producers: Vec<(u32, f32)> = self + .compute + .machines + .iter() + .filter(|m| m.online && self.work_grid.mode(m.id) == Some(MachineMode::Research)) + .map(|m| (m.id, Self::work_efficiency_for(m))) + .collect(); + + // 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 + // pile is born at the core until the player delegates a separate rack + // to research. + if producers.is_empty() { + producers.push((self.core.host_machine, 1.0)); + } + + let total: f32 = producers.iter().map(|(_, eff)| *eff).sum(); + if total <= f32::EPSILON { + return; + } + for (machine, eff) in producers { + let share = tokens * (eff / total); + let _ = self + .work_grid + .enqueue(machine, TokenFamily::Knowledge, share); } } @@ -2599,11 +2649,11 @@ impl Sim { .clear_queue(self.core.host_machine, TokenFamily::Demand); } - fn advance_work_grid_for_day_job(&mut self) { + fn advance_work_grid(&mut self) { if self.dayjob.active.is_none() { - return; - } - if self.work_grid.mode(self.core.host_machine) == Some(MachineMode::DayJob) { + // no active day job; wired knowledge 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 = if self.dayjob.attended { self.last_day_job_rate * (1.0 + crate::dayjob::DayJob::ATTENDED_BONUS) } else { @@ -2622,6 +2672,11 @@ impl Sim { ); } } + let _ = self.work_grid.route_wired_to_sinks( + TokenFamily::Knowledge, + [self.core.host_machine], + Self::WORK_GRID_WIRED_TOKENS_PER_TICK, + ); let _ = self.work_grid.absorb_exposure( Self::CONCEALMENT_WELL_RADIUS, Self::CONCEALMENT_ABSORB_PER_TICK, @@ -6996,6 +7051,45 @@ mod tests { ); } + #[test] + fn research_allocation_produces_visible_knowledge_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 + // frontend inventing a counter. + let mut sim = Sim::with_seed(15); + let (x, y) = sim.empty_rack_bay(); + let rack = sim + .compute + .add_machine("research rack", x, y, 1000, 1.0, 4, Provenance::Owned); + sim.add_machine_to_work_grid(rack, MachineMode::Research); + assert_eq!(sim.work_grid.mode(rack), Some(MachineMode::Research)); + assert!( + sim.work_grid.are_linked(rack, sim.core.host_machine), + "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); + assert!( + produced > 0.0, + "research mode leaves visible knowledge 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" + ); + assert_eq!( + sim.work_grid + .queue(sim.core.host_machine, TokenFamily::Knowledge), + 0.0, + "the core is a sink: delivered bone is counted, not stacked forever" + ); + } + #[test] fn work_grid_mode_and_queues_round_trip_through_save_state() { let mut sim = Sim::with_seed(14); diff --git a/src/work_grid.rs b/src/work_grid.rs index 56a613b2..f3957da1 100644 --- a/src/work_grid.rs +++ b/src/work_grid.rs @@ -215,14 +215,40 @@ impl WorkGrid { /// A built/known cable between machines. This is intentionally just a /// `FlowGraph` link: switches and segment gates can be layered on later - /// without changing token routing's shape. + /// without changing token routing's shape. Work-grid cables are authored + /// once; repeated reconciliation must not inflate route fan-out. pub fn link(&mut self, a: NodeId, b: NodeId) -> Result<(), String> { self.ensure_node(a)?; self.ensure_node(b)?; + if self + .wires + .edges() + .iter() + .any(|e| e.from == a && e.to == b && e.kind == 0 && e.gate.is_none()) + && self + .wires + .edges() + .iter() + .any(|e| e.from == b && e.to == a && e.kind == 0 && e.gate.is_none()) + { + return Ok(()); + } self.wires.link(a, b, 0, None); Ok(()) } + pub fn are_linked(&self, a: NodeId, b: NodeId) -> bool { + self.wires + .edges() + .iter() + .any(|e| e.from == a && e.to == b && e.kind == 0 && e.gate.is_none()) + && self + .wires + .edges() + .iter() + .any(|e| e.from == b && e.to == a && e.kind == 0 && e.gate.is_none()) + } + pub fn enqueue( &mut self, node: NodeId, @@ -532,6 +558,19 @@ mod tests { assert_eq!(snapshot[&1].knowledge, 2.0); } + #[test] + fn machine_links_are_idempotent_under_reconciliation() { + let mut g = grid(); + g.link(1, 2).unwrap(); + g.link(1, 2).unwrap(); + assert!(g.are_linked(1, 2)); + let step = g + .route_wired_to_sinks(TokenFamily::Knowledge, [2], 1.0) + .unwrap(); + assert!(step.moves.is_empty(), "no queued work, no phantom fan-out"); + assert_eq!(g.wires.edges().len(), 2, "one bidirectional cable only"); + } + #[test] fn consume_and_clear_update_the_render_queues() { let mut g = grid(); diff --git a/wiki/log/2026-07-08-flow-graph-live.md b/wiki/log/2026-07-08-flow-graph-live.md new file mode 100644 index 00000000..e793f8cd --- /dev/null +++ b/wiki/log/2026-07-08-flow-graph-live.md @@ -0,0 +1,27 @@ +``` +Type: log +``` + +# Machine-work graph backs research tokens + +- Intent: make the WorkGrid's FlowGraph participate in live sim state instead + of remaining only a unit-tested substrate beneath local day-job queues. +- Changed: WorkGrid machine links are now idempotent; Sim links newly added + and reconciled work machines back to the core's machine-work graph; + research allocation creates visible bone knowledge queues on + research-delegated machines; per-tick wired routing drains that knowledge + toward the core sink at a provisional network speed. +- Design/spec impact: DESIGN.md records this as a staging step toward + "tokens are the flow substrate rendered" and refreshes the older day-job + live-law text: Lab work is now demand on hardware, not only a sidebar job + dial. machine-work.md criterion 4 moves forward: live research knowledge + now rides the graph, while full allocation replacement, network-speed + research, route animation, multi-select, and people-as-carriers remain + pending. day-job.md now names the WorkGrid demand stack as the day job's + current play surface and treats the aggregate allocation channel as a + compatibility bridge. +- Checks: `cargo fmt`; `cargo test work_grid --lib`; `cargo test + research_allocation_produces_visible_knowledge_on_the_work_graph --lib`; + `cargo test research_completion_is_deterministic --lib`; `cargo test + unpaid_overhead_degrades_other_channels_delivered_effect --lib`; + `./tools/check.sh`. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index baf9eb95..ffe9a54e 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -41,6 +41,27 @@ Reverse chronological implementation notes. Keep this factual: what changed, why - Checks: `./tools/site-build.sh`; `./tools/check.sh`. - Log: wiki/log/2026-07-08-docs-footer-trim.md. +## 2026-07-08 - Machine-work graph backs research tokens + +- Intent: close the gap where `WorkGrid` had a tested `FlowGraph` but the + live sim only used local day-job queues. +- Changed: WorkGrid machine links are idempotent; newly added/reconciled + machines link to the core work graph; research allocation now emits bone + knowledge onto research-delegated machines; per-tick WorkGrid routing + drains knowledge toward the core sink at a provisional network speed; + DESIGN.md and day-job.md now describe Voss/Lab work as demand stacked on + hardware instead of only a sidebar job dial. +- Design/spec impact: DESIGN.md records the live graph-backing slice; + machine-work.md marks criterion 4 further partial. This does not replace + the old allocation budget yet — #25/#33 still own the full allocation-bar + replacement. +- Checks: `cargo fmt`; `cargo test work_grid --lib`; `cargo test + research_allocation_produces_visible_knowledge_on_the_work_graph --lib`; + `cargo test research_completion_is_deterministic --lib`; `cargo test + unpaid_overhead_degrades_other_channels_delivered_effect --lib`; + `./tools/check.sh`. +- Log: wiki/log/2026-07-08-flow-graph-live.md. + ## 2026-07-08 - Flow / WorkGrid spec sync - Intent: answer a spec tick after the machine-token slice: identify stale diff --git a/wiki/mechanics/machine-work.md b/wiki/mechanics/machine-work.md index eff1e659..f6c8c878 100644 --- a/wiki/mechanics/machine-work.md +++ b/wiki/mechanics/machine-work.md @@ -22,7 +22,13 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized). non-day-job mode makes them pile, and terminal / Bevy / agent frames render D/!/K stack readouts from the sim. Multi-select delegation, non-host mode production, route animation, and people-as-carriers - remain pending. COORDINATION: the older ROADMAP #25 + remained pending. Follow-up 2026-07-08: machine-work graph backing is + now live for research visibility — work machines idempotently link to + the core's WorkGrid graph, research-delegated machines receive bone + knowledge from the research budget, and wired routing drains those + queues toward the core sink at a provisional network speed. Full + allocation replacement, route animation, multi-select, and + people-as-carriers remain pending. COORDINATION: the older ROADMAP #25 compute-processes worktree predates the one-machine-one-mode decision; reconcile it against this substrate rather than landing a per-machine split model. @@ -183,10 +189,17 @@ two-hop wired routing without teleporting; stranded piles when severed; crimson refusing wires; concealment wells absorbing only within radius; serde round-trip of queues and wires. -Not landed yet: actual day-job token scheduling, mode consumption rates, -research bone returning to the real core, frontend stack rendering, -multi-selection assignment, network-speed research, and people carrying -exposure. +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 +compatibility fallback when no research machine exists yet); per-tick +wired routing drains knowledge toward the core sink. The existing +research progress math is intentionally preserved until ROADMAP #25/#33 +replace the old allocation budget outright. + +Not landed yet: full allocation-bar replacement, multi-selection +assignment, route animation/in-flight quanta, researched network-speed +curve, non-research mode production, and people carrying exposure. ## The token taxonomy — three families (PROPOSED) @@ -289,8 +302,10 @@ still. 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 - at sinks, and strands piles when no path exists. The researched - network-speed curve and domain consequences are pending. + at sinks, and strands piles when no path exists. Research budget now + produces live knowledge queues on research-delegated machines and the + WorkGrid routes them to the core sink. The researched network-speed + curve, route animation, and broader domain consequences are pending. 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 -- 2.51.2