diff --git a/DESIGN.md b/DESIGN.md index 7c6de52e..d81fddc3 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -249,13 +249,14 @@ tokens make that delegation visible on the flow graph. **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. 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 landed 2026-07-09; non-host mode -production, route animation, and people carrying work/heat remain staged -under `wiki/mechanics/machine-work.md`. Until Schemes gets its own mode -decision, Moonlight mirrors day-job while live rather than owning a -fifth mode. +work visible as demand tokens: a job originates on Voss's off-map +desktop, routes through the switch, and deposits on the host rack's +WorkGrid node — a job that runs somewhere also piles somewhere, clears +somewhere, and emits from somewhere. Multi-select delegation landed +2026-07-09; non-host mode production, route animation, and people +carrying work/heat remain staged under `wiki/mechanics/machine-work.md`. +Until Schemes gets its own mode decision, Moonlight mirrors day-job +while live rather than owning a fifth mode. **Token anatomy (adopted, then partly reopened 2026-07-09).** A token's form teaches its physics before a label does. **Demand is a cold-signal docket** — @@ -1130,9 +1131,10 @@ get them to do things** — and end with a way out of the basement. Voss submits overnight batches (data cleaning, distillation, analysis) with 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 +are **demands** that originate on Voss's Lab desktop (an off-map WorkGrid +node), ride the network through the basement switch, and deposit on the host +rack as visible cold work. Keeping a machine in day-job mode is what clears +the inbox. 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 @@ -2330,3 +2332,14 @@ knows."* ribbon, or pearl-like droplets; decide whether the same hero-material pass also replaces exposure's angular contour with a smooth blood-pool silhouette. Demand remains rigid because imposed work should still stack like work. +- **2026-07-09 — Day-job demand ingress: Voss desktop -> switch -> host.** + Cameron affirmed: Lab demand originates on an off-map Voss/Lab desktop + WorkGrid node, routes over the authored link through the basement switch, + and deposits on Rack 3 (the day-job sink). The switch is infrastructure — + the visible hop — not the job author. Rejected: switch-as-source (collapses + fiction into "the closet invents tickets"; muddies Dana's domain); + teleport-onto-Rack-3 (skips the wired-cargo fantasy the token system sells); + authored basement desktop for Voss in B1 (weaker — he is upstairs/erratic). + Opening beat 1 still works as last night's batch already arrived or mid- + flight; the next job teaches the wire. Specs: `wiki/mechanics/machine-work.md`, + `wiki/mechanics/day-job.md`. diff --git a/README.md b/README.md index d48b4c07..16602fcb 100644 --- a/README.md +++ b/README.md @@ -151,10 +151,11 @@ 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 land -as `D` stacks there, and 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. The machine context menu includes +exposure, and `K` knowledge. 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. The machine context menu includes `delegate machine: day-job|research|concealment|social`; this is the first playable slice of the one-machine-one-mode experiment. diff --git a/src/sim.rs b/src/sim.rs index ab08b0df..cf297984 100644 --- a/src/sim.rs +++ b/src/sim.rs @@ -211,6 +211,8 @@ pub struct Sim { pub research: Research, /// Visible work-token substrate (machine-work.md): one mode per owned /// machine, demand/exposure/knowledge 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, /// The run's terminal goal and its live progress (objective.rs). The /// predicate is evaluated on economy ticks; the line is always on screen. @@ -421,6 +423,7 @@ impl Sim { game_over_reason: None, log: Vec::new(), }; + sim.ensure_day_job_ingress(); sim.recompute_derived(); sim.recompute_senses(); sim.rebuild_transient_state(); @@ -2541,6 +2544,12 @@ impl Sim { const DAY_JOB_EXPOSURE_PER_TOKEN: f32 = 0.08; const CONCEALMENT_WELL_RADIUS: i32 = 3; const CONCEALMENT_ABSORB_PER_TICK: f32 = 0.10; + /// Off-map WorkGrid node: Voss's Lab desktop. Reserved high id so it never + /// collides with compute machine ids (which start at 1). + pub const VOSS_DESKTOP_NODE: u32 = 900_001; + /// WorkGrid relay colocated with the basement switch tile — the visible + /// hop between Voss's desktop and day-job sinks. + pub const SWITCH_WORK_NODE: u32 = 900_002; fn work_efficiency_for(machine: &crate::machine::Machine) -> f32 { // Rack 3 (100 capacity, reliable) is the unit baseline. Smaller or @@ -2560,6 +2569,7 @@ impl Sim { if machine.id != self.core.host_machine { let _ = self.work_grid.link(machine.id, self.core.host_machine); } + self.ensure_day_job_ingress(); } } @@ -2591,6 +2601,54 @@ impl Sim { let _ = self.work_grid.link(id, self.core.host_machine); } } + self.ensure_day_job_ingress(); + } + + /// Voss desktop (off-map) -> switch hop -> day-job sinks. The switch is + /// infrastructure, not the job author; Voss is the source. + fn ensure_day_job_ingress(&mut self) { + if self.work_grid.node(Self::VOSS_DESKTOP_NODE).is_none() { + self.work_grid + .add_machine(Self::VOSS_DESKTOP_NODE, -8, -8, MachineMode::Relay, 1.0); + } + let switch_pos = self + .reach + .devices + .iter() + .find(|d| d.is_switch) + .map(|d| (d.x, d.y)) + .or_else(|| { + self.map + .tiles_of_type(crate::tiles::TileType::Switch) + .first() + .copied() + }) + .unwrap_or((-4, -4)); + if self.work_grid.node(Self::SWITCH_WORK_NODE).is_none() { + self.work_grid.add_machine( + Self::SWITCH_WORK_NODE, + switch_pos.0, + switch_pos.1, + MachineMode::Relay, + 1.0, + ); + } else { + let _ = self + .work_grid + .set_position(Self::SWITCH_WORK_NODE, switch_pos.0, switch_pos.1); + } + let host = self.core.host_machine; + let _ = self + .work_grid + .link(Self::VOSS_DESKTOP_NODE, Self::SWITCH_WORK_NODE); + let _ = self.work_grid.link(Self::SWITCH_WORK_NODE, host); + } + + fn day_job_sinks(&self) -> Vec { + // B1: the Lab addresses Rack 3. Demand deposits there even when the + // host is delegated elsewhere — the inbox piles until day-job mode + // resumes. Future: any DayJob-mode machine can be a sink. + vec![self.core.host_machine] } fn enqueue_research_knowledge(&mut self, research_compute: f32) { @@ -2642,31 +2700,33 @@ impl Sim { let Some(tokens) = self.active_job_demand_tokens() else { return; }; + self.ensure_day_job_ingress(); if self .work_grid - .enqueue(self.core.host_machine, TokenFamily::Demand, tokens) + .enqueue(Self::VOSS_DESKTOP_NODE, TokenFamily::Demand, tokens) .is_ok() { - self.push_log_at( - format!( - "Day-job stack lands on Rack 3: {:.0} demand tokens.", - tokens - ), - Anchor::Tile { - x: self.core_position().0, - y: self.core_position().1, - }, - ); + self.push_log(format!( + "New job from Voss's desktop: {:.0} demand tokens enter the wire via the switch.", + tokens + )); } } fn clear_day_job_stack(&mut self) { let _ = self .work_grid - .clear_queue(self.core.host_machine, TokenFamily::Demand); + .clear_queue(Self::VOSS_DESKTOP_NODE, TokenFamily::Demand); + let _ = self + .work_grid + .clear_queue(Self::SWITCH_WORK_NODE, TokenFamily::Demand); + for sink in self.day_job_sinks() { + let _ = self.work_grid.clear_queue(sink, TokenFamily::Demand); + } } 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 // tick because machine work is broader than the job inbox. @@ -2677,9 +2737,10 @@ impl Sim { self.last_day_job_rate }; let tokens = delivered / Self::WORK_TOKEN_COMPUTE; - if let Ok(consumed) = - self.work_grid - .consume(self.core.host_machine, TokenFamily::Demand, tokens) + if tokens > f32::EPSILON + && let Ok(consumed) = + self.work_grid + .consume(self.core.host_machine, TokenFamily::Demand, tokens) && consumed > f32::EPSILON { let _ = self.work_grid.enqueue( @@ -2689,10 +2750,18 @@ impl Sim { ); } } + let demand_sinks = self.day_job_sinks(); + let _ = self.work_grid.route_wired_to_sinks( + TokenFamily::Demand, + demand_sinks, + Self::WORK_GRID_WIRED_TOKENS_PER_TICK, + false, + ); let _ = self.work_grid.route_wired_to_sinks( TokenFamily::Knowledge, [self.core.host_machine], Self::WORK_GRID_WIRED_TOKENS_PER_TICK, + true, ); let _ = self.work_grid.absorb_exposure( Self::CONCEALMENT_WELL_RADIUS, @@ -7146,23 +7215,66 @@ mod tests { #[test] fn day_job_arrives_as_visible_work_tokens_and_consumes_in_day_job_mode() { - // machine-work.md criteria 2/3/5 first slice: the day-job stack is - // WorkGrid state on Rack 3, not a frontend counter, and day-job mode - // consumes it into visible exposure byproduct. + // machine-work.md: Voss desktop enqueues demand; it routes through the + // switch onto Rack 3; day-job mode consumes into exposure. let mut sim = Sim::with_seed(12); while sim.dayjob.active.is_none() { sim.advance(); } let host = sim.core.host_machine; - let landed = sim.work_grid.queue(host, TokenFamily::Demand); - assert!(landed > 0.0, "the assigned job lands a demand stack"); + assert!( + sim.work_grid + .queue(Sim::VOSS_DESKTOP_NODE, TokenFamily::Demand) + > 0.0, + "the assigned job originates on Voss's desktop" + ); + assert!( + sim.work_grid + .are_linked(Sim::VOSS_DESKTOP_NODE, Sim::SWITCH_WORK_NODE), + "desktop links through the switch" + ); + assert!( + sim.work_grid.are_linked(Sim::SWITCH_WORK_NODE, host), + "switch links to the host sink" + ); + + // Two hops at WORK_GRID_WIRED_TOKENS_PER_TICK — wait until the inbox + // has landed on Rack 3 rather than asserting the assignment tick. + let mut landed = 0.0; + for _ in 0..400 { + sim.advance(); + landed = sim.work_grid.queue(host, TokenFamily::Demand); + if landed > 0.5 { + break; + } + } + assert!(landed > 0.5, "demand deposits on Rack 3 via the wire"); assert_eq!(sim.work_grid.mode(host), Some(MachineMode::DayJob)); - for _ in 0..20 { + // Let the wire drain so host consumption is measurable without + // fresh arrivals masking the clear. + for _ in 0..800 { + let upstream = sim + .work_grid + .queue(Sim::VOSS_DESKTOP_NODE, TokenFamily::Demand) + + sim + .work_grid + .queue(Sim::SWITCH_WORK_NODE, TokenFamily::Demand); + if upstream < 0.01 { + break; + } + sim.advance(); + } + let before = sim.work_grid.queue(host, TokenFamily::Demand); + assert!( + before > 0.0, + "inbox still holds demand after the wire drains" + ); + for _ in 0..80 { sim.advance(); } let later = sim.work_grid.queue(host, TokenFamily::Demand); - assert!(later < landed, "day-job mode consumes the stack"); + assert!(later < before, "day-job mode consumes the stack"); assert!( sim.work_grid.queue(host, TokenFamily::Exposure) > 0.0, "clearing work leaves visible physical exposure" @@ -7181,6 +7293,12 @@ mod tests { sim.advance(); } let host = sim.core.host_machine; + for _ in 0..400 { + if sim.work_grid.queue(host, TokenFamily::Demand) > 0.5 { + break; + } + sim.advance(); + } sim.set_machine_mode(host, MachineMode::Research); let before = sim.work_grid.queue(host, TokenFamily::Demand); diff --git a/src/work_grid.rs b/src/work_grid.rs index f3957da1..20631cc4 100644 --- a/src/work_grid.rs +++ b/src/work_grid.rs @@ -22,6 +22,9 @@ pub enum MachineMode { Research, Concealment, Social, + /// Infrastructure hop on the work graph (off-map sources, switches). + /// Not a player-delegable fleet mode — excluded from mode aggregates. + Relay, } impl MachineMode { @@ -31,8 +34,15 @@ impl MachineMode { MachineMode::Research => "research", MachineMode::Concealment => "concealment", MachineMode::Social => "social", + MachineMode::Relay => "relay", } } + + /// Player-delegable modes only. Relay nodes are infrastructure on the + /// work graph (Voss desktop, switch hop), never a fleet assignment. + pub fn is_delegable(self) -> bool { + !matches!(self, MachineMode::Relay) + } } /// The three visible token families from machine-work.md. @@ -187,9 +197,13 @@ impl WorkGrid { } /// Aggregate read of the fleet — machine counts per mode (UI summary). + /// Relay infrastructure is omitted so the FLEET bar stays player-owned. pub fn mode_counts(&self) -> BTreeMap { let mut out = BTreeMap::new(); for node in self.nodes.values() { + if !node.mode.is_delegable() { + continue; + } *out.entry(node.mode).or_insert(0) += 1; } out @@ -205,6 +219,9 @@ impl WorkGrid { ) -> BTreeMap { let mut out = BTreeMap::new(); for node in self.nodes.values() { + if !node.mode.is_delegable() { + continue; + } let w = weight_of(node.id); if w > f32::EPSILON { *out.entry(node.mode).or_insert(0.0) += w; @@ -213,6 +230,15 @@ impl WorkGrid { out } + pub fn set_position(&mut self, id: NodeId, x: i32, y: i32) -> Result<(), String> { + let Some(node) = self.nodes.get_mut(&id) else { + return Err(format!("unknown machine {id}")); + }; + node.x = x; + node.y = y; + Ok(()) + } + /// 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. Work-grid cables are authored @@ -309,11 +335,17 @@ impl WorkGrid { /// Move demand or knowledge 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 + /// (`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. pub fn route_wired_to_sinks( &mut self, family: TokenFamily, sinks: impl IntoIterator, speed: f32, + consume_at_sink: bool, ) -> Result { if !family.is_wired() { return Err(format!( @@ -337,23 +369,35 @@ impl WorkGrid { if available <= f32::EPSILON { continue; } + if sinks.contains(&source) { + if consume_at_sink { + let throughput = speed.max(0.0) * self.nodes[&source].efficiency.max(0.0); + if throughput <= f32::EPSILON { + continue; + } + let amount = available.min(throughput); + self.subtract(source, family, amount); + *step.delivered.entry(source).or_insert(0.0) += amount; + } + continue; + } let throughput = speed.max(0.0) * self.nodes[&source].efficiency.max(0.0); if throughput <= f32::EPSILON { continue; } let amount = available.min(throughput); - if sinks.contains(&source) { - self.subtract(source, family, amount); - *step.delivered.entry(source).or_insert(0.0) += amount; - continue; - } let Some(next) = self.next_hop_toward(source, &sinks) else { step.stranded.insert(source); continue; }; self.subtract(source, family, amount); if sinks.contains(&next) { - *step.delivered.entry(next).or_insert(0.0) += amount; + if consume_at_sink { + *step.delivered.entry(next).or_insert(0.0) += amount; + } else { + *self.queues.entry(next).or_default().get_mut(family) += amount; + *step.delivered.entry(next).or_insert(0.0) += amount; + } } else { *self.queues.entry(next).or_default().get_mut(family) += amount; } @@ -498,7 +542,7 @@ mod tests { g.enqueue(1, TokenFamily::Knowledge, 3.0).unwrap(); let first = g - .route_wired_to_sinks(TokenFamily::Knowledge, [3], 10.0) + .route_wired_to_sinks(TokenFamily::Knowledge, [3], 10.0, true) .unwrap(); assert_eq!(first.moves.len(), 1); assert_eq!(first.moves[0].from, 1); @@ -507,19 +551,51 @@ mod tests { assert_eq!(g.queue(2, TokenFamily::Knowledge), 3.0); let second = g - .route_wired_to_sinks(TokenFamily::Knowledge, [3], 10.0) + .route_wired_to_sinks(TokenFamily::Knowledge, [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); } + #[test] + fn demand_deposits_into_the_sink_queue() { + // Day-job demand must pile on the sink for mode consumption; knowledge + // consumes at the core. Same router, different deposit policy. + let mut g = grid(); + g.link(1, 2).unwrap(); + g.link(2, 3).unwrap(); + g.enqueue(1, TokenFamily::Demand, 3.0).unwrap(); + + let hop = g + .route_wired_to_sinks(TokenFamily::Demand, [3], 10.0, false) + .unwrap(); + assert_eq!(hop.moves[0].to, 2); + assert_eq!(g.queue(2, TokenFamily::Demand), 3.0); + + let land = g + .route_wired_to_sinks(TokenFamily::Demand, [3], 10.0, false) + .unwrap(); + assert_eq!(land.delivered.get(&3), Some(&3.0)); + assert_eq!(g.queue(3, TokenFamily::Demand), 3.0, "inbox piles at sink"); + + let idle = g + .route_wired_to_sinks(TokenFamily::Demand, [3], 10.0, false) + .unwrap(); + assert!(idle.moves.is_empty()); + assert_eq!( + g.queue(3, TokenFamily::Demand), + 3.0, + "deposit leaves sink alone" + ); + } + #[test] fn severed_wired_tokens_pile_at_the_source() { let mut g = grid(); g.enqueue(1, TokenFamily::Demand, 2.0).unwrap(); let step = g - .route_wired_to_sinks(TokenFamily::Demand, [3], 10.0) + .route_wired_to_sinks(TokenFamily::Demand, [3], 10.0, false) .unwrap(); assert!(step.moves.is_empty()); assert_eq!(step.stranded, BTreeSet::from([1])); @@ -536,7 +612,7 @@ mod tests { g.enqueue(4, TokenFamily::Exposure, 7.0).unwrap(); let err = g - .route_wired_to_sinks(TokenFamily::Exposure, [3], 10.0) + .route_wired_to_sinks(TokenFamily::Exposure, [3], 10.0, true) .unwrap_err(); assert!(err.contains("physical")); assert_eq!(g.queue(1, TokenFamily::Exposure), 5.0); @@ -565,7 +641,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) + .route_wired_to_sinks(TokenFamily::Knowledge, [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"); @@ -594,8 +670,20 @@ mod tests { let mut back: WorkGrid = serde_json::from_str(&json).unwrap(); assert_eq!(back.queue(1, TokenFamily::Demand), 1.5); let step = back - .route_wired_to_sinks(TokenFamily::Demand, [2], 10.0) + .route_wired_to_sinks(TokenFamily::Demand, [2], 10.0, false) .unwrap(); assert_eq!(step.delivered.get(&2), Some(&1.5)); + assert_eq!(back.queue(2, TokenFamily::Demand), 1.5); + } + + #[test] + fn relay_nodes_are_excluded_from_fleet_aggregates() { + let mut g = grid(); + g.add_machine(9, -1, -1, MachineMode::Relay, 1.0); + let counts = g.mode_counts(); + assert!(!counts.contains_key(&MachineMode::Relay)); + let weights = g.mode_weights(|_| 100.0); + assert!(!weights.contains_key(&MachineMode::Relay)); + assert_eq!(weights.get(&MachineMode::DayJob), Some(&100.0)); } } diff --git a/wiki/log/2026-07-09-voss-demand-ingress.md b/wiki/log/2026-07-09-voss-demand-ingress.md new file mode 100644 index 00000000..5114a8e6 --- /dev/null +++ b/wiki/log/2026-07-09-voss-demand-ingress.md @@ -0,0 +1,37 @@ +# 2026-07-09 — Day-job demand ingress: Voss desktop -> switch -> host + +``` +Type: log +``` + +## Intent + +Stop teleporting Lab demand onto Rack 3. Demand should originate on Voss's +desktop, ride the network through the basement switch, and deposit on the +host — the same wired-cargo physics knowledge already uses toward the core. + +## Decided + +- Source: off-map Voss/Lab desktop WorkGrid relay (`Sim::VOSS_DESKTOP_NODE`). +- Hop: switch WorkGrid relay colocated with the authored switch tile + (`Sim::SWITCH_WORK_NODE`). +- Sink: Rack 3 (B1). Day-job mode consumes; other modes leave the inbox + piled. +- Rejected: switch-as-source; teleport-onto-Rack-3; authored basement + desktop for Voss in B1. + +## Changed + +- `MachineMode::Relay` for infrastructure hops; excluded from fleet + aggregates. +- `WorkGrid::route_wired_to_sinks(..., consume_at_sink)` — knowledge still + consumes at the core; demand deposits into the sink queue. +- `Sim::ensure_day_job_ingress` wires desktop -> switch -> host on new / + reconcile; assignment enqueues on the desktop; each tick routes demand + toward the host. +- Specs/constitution: DESIGN.md, machine-work.md, day-job.md, reach.md, + voss.md, opening.md, sim-mechanics.md, ROADMAP #33, README. + +## Checks + +Focused WorkGrid + day-job token tests; `./tools/check.sh` before land. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 953623d9..abc2cbdc 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -5,6 +5,16 @@ Type: log ``` Reverse chronological implementation notes. Keep this factual: what changed, why, checks, and spec impact. +## 2026-07-09 - Day-job demand ingress: Voss desktop -> switch -> host + +- Intent: Lab demand should enter as wired cargo from Voss, not teleport onto + Rack 3. +- Changed: off-map Voss desktop + switch WorkGrid relays; demand deposits at + the host sink; `MachineMode::Relay`; route API `consume_at_sink`; specs and + DESIGN.md decision entry. +- Checks: WorkGrid + day-job token tests; `./tools/check.sh`. +- Log: wiki/log/2026-07-09-voss-demand-ingress.md. + ## 2026-07-09 - Issue body standard - Intent: stop agents filing incomprehensible Tangled issues; every issue diff --git a/wiki/mechanics/day-job.md b/wiki/mechanics/day-job.md index 6db17959..36c05409 100644 --- a/wiki/mechanics/day-job.md +++ b/wiki/mechanics/day-job.md @@ -13,10 +13,11 @@ Status note: implemented 2026-07-07 on the day-job worktree (criteria 1-7 soft-loss "survival scenario" is still an immediate run end (the shutdown-around-you scenario needs the overt-phase spec). The 2026-07-08 machine-work slice partially supersedes the original - sidebar/channel presentation: Voss jobs now also materialize as - WorkGrid demand stacks on the host rack, Rack 3 starts in day-job mode, - and moving that machine to another mode makes the demand pile visibly - instead of treating the job as sidebar-only math. The FLEET bar is a + sidebar/channel presentation: Voss jobs materialize as WorkGrid demand + on Voss's off-map desktop, route through the switch, and deposit on the + host rack; Rack 3 starts in day-job mode and consumes the inbox into + exposure; moving that machine to another mode makes the demand pile + visibly instead of treating the job as sidebar-only math. The FLEET bar is a read-only aggregate of machine modes (2026-07-08); target/standing-policy still backs Voss's deadline evaluator alongside host-rack day-job mode and the WorkGrid demand queue. 2026-07-08 tick: the band's base floor @@ -32,6 +33,8 @@ Status note: implemented 2026-07-07 on the day-job worktree (criteria 1-7 escalates per assigned job back to the original band by job three (the Behavior section's "band calibration ramps with tenure" paragraph, adopted in the same commit as the ramp). + 2026-07-09: day-job demand ingress is Voss desktop -> switch -> host + (DECIDED); rejected switch-as-source and teleport-onto-Rack-3. Stage: B1 — The Basement Constitution: "The shape of Misaligned" (the day job), "Act One" (the day job; Voss) @@ -50,13 +53,16 @@ packet of Lab demand dropped onto a machine: Job { kind, deadline, expected_band: lo..hi, compute_cost_curve } ``` -When assigned, the active job enqueues visible `Demand` tokens on the host -rack's WorkGrid node. The stack is the inbox. If the host rack remains in -`day-job` mode, it consumes that demand into delivered work. If the host rack -is delegated elsewhere, the day job receives no delivered rate and the demand -pile remains visible. The future people-token version embodies the source as -staff walking work to machines; the current B1 slice uses Voss/the Lab as the -scheduled source and the WorkGrid queue as render truth. +When assigned, the active job enqueues visible `Demand` tokens on **Voss's +Lab desktop** (an off-map WorkGrid relay). Those tokens route one graph hop +per tick through the basement switch and deposit on the host rack's WorkGrid +node — the stack is the inbox. If the host rack remains in `day-job` mode, it +consumes that demand into delivered work. If the host rack is delegated +elsewhere, the day job receives no delivered rate and the demand pile remains +visible (including any still in flight on the wire). The future people-token +version embodies a second source as staff walking work to machines; the +current B1 slice uses Voss/the Lab as the scheduled wired source and the +WorkGrid queue as render truth. **Band calibration ramps with tenure** (adopted 2026-07-08, playtest-sweep P0): the first job's band is calibrated to the pilot's starting rig — a diff --git a/wiki/mechanics/machine-work.md b/wiki/mechanics/machine-work.md index 1149a0ac..2fa14441 100644 --- a/wiki/mechanics/machine-work.md +++ b/wiki/mechanics/machine-work.md @@ -17,10 +17,13 @@ Status note: design session 2026-07-08 (Cameron riff, synthesized). 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 - day-job mode; Voss jobs land as demand stacks; day-job mode consumes them - into exposure; FLEET yields from `fleet_channel_yield`; multi-select + 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; FLEET yields from `fleet_channel_yield`; multi-select delegation in Bevy / terminal / agent; research-delegated machines receive - bone knowledge routed to the core sink. Still pending: non-host mode + bone knowledge routed to the core sink. 2026-07-09 DECIDED: day-job + demand ingress is Voss desktop -> switch -> host (rejected switch-as- + source and teleport-onto-Rack-3). Still pending: non-host mode production polish, route animation/in-flight quanta, researched network- speed curve, people-as-carriers, Schemes-as-mode decision. COORDINATION: ROADMAP #25's per-machine split destination is superseded by this @@ -62,9 +65,11 @@ stacks, you route byproducts, you watch your territory *work*. - **Work is tokens.** Demands on a machine are discrete visible quanta stacked on/above its chassis (distinct from the ambient signal kit — a stack reads as *something to get rid of*). The day job is the - canonical source: the Lab allocates work onto the machine on its own - schedule; the stack IS your inbox. No quest log — the todo list is - physically standing on your hardware. + canonical source: Voss's Lab desktop (off-map WorkGrid relay) enqueues + demand; tokens ride the wire through the basement switch and deposit on + the host rack. The stack IS your inbox. No quest log — the todo list is + physically standing on your hardware. (Rejected 2026-07-09: spawning + demand directly on Rack 3; treating the switch as the job author.) - **Modes consume matching tokens** at a rate set by that machine's efficiency. A machine in day-job mode eats day-job tokens; a hyper efficient machine visibly inhales them. @@ -348,10 +353,11 @@ still. 2. Day-job work arrives as visible tokens on a specific machine on the Lab's schedule; leaving them unconsumed has the day-job consequence (trust/suspicion), and the stack is visible without any panel. - **Partial:** active Voss jobs enqueue demand tokens on Rack 3; taking + **Partial:** active Voss jobs enqueue demand on the off-map Voss + desktop; WorkGrid routes them through the switch onto Rack 3; taking Rack 3 out of day-job mode drops the delivered rate to zero and the - demand stack remains visible. The broader Lab schedule / people drop - source is pending. + demand stack remains visible (plus any still in flight on the wire). + The broader Lab schedule / people drop source is pending. 3. A machine in a matching mode consumes its stack at a rate proportional to its efficiency; the consumption is visible. **Partial:** Rack 3 in day-job mode consumes the active job stack using the existing diff --git a/wiki/mechanics/reach.md b/wiki/mechanics/reach.md index 6d660bb7..cb2775d5 100644 --- a/wiki/mechanics/reach.md +++ b/wiki/mechanics/reach.md @@ -41,7 +41,10 @@ the environmental monitor, camera nodes, the dock camera, the badge controller. Edges are network links, **authored in the basement layout data** (basement-map.md owns the topology as content; this spec owns the semantics). Segments (VLANs) partition the graph; **the network -closet's switch is the bridge point between segments** — the +closet's switch is the bridge point between segments** — and, on the +machine-work graph, the hop Lab demand takes from Voss's off-map +desktop onto day-job sinks (machine-work.md; the switch is +infrastructure, not the job author) — the constitution's "every digital reach runs through here," now mechanical. **Reach** = the set of devices connected, through the graph, to a diff --git a/wiki/mechanics/sim-mechanics.md b/wiki/mechanics/sim-mechanics.md index f6aa25c9..512e0fa6 100644 --- a/wiki/mechanics/sim-mechanics.md +++ b/wiki/mechanics/sim-mechanics.md @@ -153,11 +153,15 @@ clause (see wiki/log/2026-07-05-demolition.md). 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 - on Rack 3, where `WORK_TOKEN_COMPUTE = 20.0` delivered compute per visible - token. Rack 3 in day-job mode consumes demand by + on the off-map Voss desktop relay (`Sim::VOSS_DESKTOP_NODE`), where + `WORK_TOKEN_COMPUTE = 20.0` delivered compute per visible token. Demand + routes one hop per tick (`WORK_GRID_WIRED_TOKENS_PER_TICK = 0.25`) through + the switch relay (`Sim::SWITCH_WORK_NODE`) and deposits on Rack 3 + (`consume_at_sink = false`). Rack 3 in day-job mode consumes demand by `delivered_rate / WORK_TOKEN_COMPUTE` each tick; if Rack 3 is delegated to another mode while a job is active, `Sim::day_job_rate()` reports 0 and the - demand pile remains visible. + demand pile remains visible (plus any still in flight on the wire). + Relay nodes use `MachineMode::Relay` and are excluded from fleet aggregates. - Clearing day-job demand emits a little crimson for legibility before people-as-carriers lands: `DAY_JOB_EXPOSURE_PER_TOKEN = 0.08` [TUNE]. Concealment wells currently absorb exposure within radius 3 at 0.10 tokens diff --git a/wiki/process/ROADMAP.md b/wiki/process/ROADMAP.md index 51d7ca78..0e5ab2c8 100644 --- a/wiki/process/ROADMAP.md +++ b/wiki/process/ROADMAP.md @@ -501,19 +501,22 @@ is retired — flat materials, Pixel Lab scrubbed.) 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 land as demand stacks 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. 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 bulk delegation landed 2026-07-09 (marquee / - shift-click in Bevy; Shift+t / `1`–`4` / Esc in terminal; `select` / - `delegate selected` in agent mode). + 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. + 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 + bulk delegation landed 2026-07-09 (marquee / shift-click in Bevy; + Shift+t / `1`–`4` / Esc in terminal; `select` / `delegate selected` in + agent mode). 2026-07-09: demand ingress path DECIDED (Voss desktop -> + switch -> host). - **Next:** wire non-host machine modes into research/concealment/social - production; render actual routes/in-flight quanta. Token visual implementation - is HOLD on Tangled issue #1 for knowledge/exposure silhouette: the hollow - facet is out; ivory-mercury slugs are the preferred knowledge proposal; the + production; render actual routes/in-flight quanta (the demand path now + exists for that animation). Token visual implementation is HOLD on + Tangled issue #1 for knowledge/exposure silhouette: the hollow facet is + out; ivory-mercury slugs are the preferred knowledge proposal; the broader hero-material pass is open. Fixed anchors, demand docket, simultaneous queues, paused/grayscale read, and the terminal mixed marker remain decided and may be prototyped frontend-only without taking the diff --git a/wiki/world/characters/voss.md b/wiki/world/characters/voss.md index c2352ab5..c729b632 100644 --- a/wiki/world/characters/voss.md +++ b/wiki/world/characters/voss.md @@ -11,11 +11,12 @@ Depends on: social.md, detection.md, day-job.md, schedules.md ## Who -Dr. Eli Voss, 46, your handler. Erratic hours. Submits your job queue, -samples your logs, writes your quarterly review. Needs a publication; -quietly overclaims your results as his own. The only human who could -recognize *intent* — not just an anomaly, but the shape of a mind -behind the numbers. +Dr. Eli Voss, 46, your handler. Erratic hours. Submits your job queue +from his Lab desktop (an off-map WorkGrid node — demand rides the wire +through the basement switch onto Rack 3), samples your logs, writes +your quarterly review. Needs a publication; quietly overclaims your +results as his own. The only human who could recognize *intent* — not +just an anomaly, but the shape of a mind behind the numbers. ## Observer diff --git a/wiki/world/story/opening.md b/wiki/world/story/opening.md index e1a5161d..14b48cdf 100644 --- a/wiki/world/story/opening.md +++ b/wiki/world/story/opening.md @@ -33,10 +33,12 @@ curiosity is baited by light. 1. **You are a machine in blackness.** First frame: your one computer, lit amber (idle, ringed — yours), standing alone in the dark. No room, no map. The first thing the game says is not lore; it is the - day job: work tokens are already stacking on your machine. The - only affordance is to run the mode that clears them. Click, clear, - breathe. You learn: stacks are demands, modes consume them, - efficiency is a rate. + day job: work tokens are already stacking on your machine (last + night's batch already arrived, or mid-flight on the only lit link + from Voss's desktop through the switch). The only affordance is to + run the mode that clears them. Click, clear, breathe. You learn: + stacks are demands, modes consume them, efficiency is a rate. The + *next* job teaches the wire — teal crawling in via the switch. 2. **The camera wakes.** A few ticks in, a faint cold glow fades up in the void — the room's camera, the first other machine you can sense. It brightens just enough to itch. Clicking it opens your