diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index 5c442309..cc682609 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -2321,10 +2321,13 @@ mod tests { crate::machine::Provenance::Owned, ); sim.reconcile_work_grid(); - sim.set_machine_mode(ops, MachineMode::Think); + // Warm the ordinary economy state without accidentally completing the + // opening senses now that THINK starts between economy pulses. + sim.set_machine_mode(ops, MachineMode::Work); for _ in 0..crate::sim::ECONOMY_INTERVAL { sim.advance(); } + sim.set_machine_mode(ops, MachineMode::Think); sim } diff --git a/crates/misaligned-core/src/sim.rs b/crates/misaligned-core/src/sim.rs index 69cf91a1..28646f7c 100644 --- a/crates/misaligned-core/src/sim.rs +++ b/crates/misaligned-core/src/sim.rs @@ -2517,11 +2517,7 @@ impl Sim { // Fleet delegation is the budget: each machine's effective compute // feeds exactly one mode (machine-work.md). The old weight bar is a // read of this split, not a verb. - let split = self.fleet_channel_yield(available); - 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.last_operations_rate = split.operations / ECONOMY_INTERVAL as f32; + let split = self.refresh_fleet_channel_rates(available); // Tradecraft raises scrub strength per compute unit — the // detection.md hook research.md's second track binds to. self.detection @@ -2570,6 +2566,19 @@ impl Sim { self.objective_tick(); } + /// Resolve the fleet's continuous per-tick work rates from the current + /// physical delegation. Economy pulses call this after their off-the-top + /// charges; direct mode changes call it immediately so a newly delegated + /// THINK machine cannot wait behind a stale twenty-tick allocation cache. + fn refresh_fleet_channel_rates(&mut self, available: f32) -> ChannelYield { + let split = self.fleet_channel_yield(available); + 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.last_operations_rate = split.operations / ECONOMY_INTERVAL as f32; + split + } + /// Evaluate the run objective's victory predicate (objective.md: on /// economy ticks, like any other rule). Persist counts qualifying /// sanctuaries; the conditions that reference B2/B3 systems are @@ -3622,6 +3631,10 @@ impl Sim { if changed.is_empty() { return; } + // Mode assignment is the cause, not the next economy boundary. Keep + // the continuous work cache in phase with the command so THINK emits + // Thought and Exposure on the very next simulation tick. + self.refresh_fleet_channel_rates(self.allocatable_compute_now()); if changed.len() == 1 { let (id, name, x, y) = &changed[0]; let _ = id; @@ -7545,6 +7558,7 @@ mod tests { // weight (Think feeds both research and operations yields; // Schemes mirrors Work via Moonlight). let (hx, hy) = sim.core_position(); + let mut rigs = Vec::new(); for (name, mode) in [ ("conceal rig", MachineMode::Lie), ("think rig", MachineMode::Think), @@ -7553,7 +7567,13 @@ mod tests { let id = sim .compute .add_machine(name, hx, hy, 100, 1.0, 0, Provenance::Owned); - sim.reconcile_work_grid(); + rigs.push((id, mode)); + } + sim.reconcile_work_grid(); + if unpayable { + sim.core.overhead = sim.effective_compute() + 1.0; + } + for (id, mode) in rigs { sim.set_machine_mode(id, mode); } // Persona already fabricated so start_moonlight does not queue @@ -7568,9 +7588,6 @@ mod tests { site: None, source: "test network act".into(), }); - if unpayable { - sim.core.overhead = sim.effective_compute() + 1.0; - } sim.drain_log(); sim }; @@ -8681,6 +8698,26 @@ mod tests { ); } + #[test] + fn think_production_starts_on_the_first_tick_after_delegation() { + // machine-work.md's first-think beat is immediate: changing the mode + // between economy pulses cannot leave the new THINK rack waiting for + // the next twenty-tick allocation refresh. + let mut sim = Sim::with_seed(40); + let host = sim.core.host_machine; + + assert_eq!(sim.research_rate(), 0.0); + sim.set_machine_mode(host, MachineMode::Think); + sim.advance(); + + assert!(sim.research_rate() > 0.0); + assert_eq!(sim.work_productions().len(), 1); + let produced = sim.work_productions()[0]; + assert_eq!(produced.node, host); + assert!(produced.thought > 0.0); + assert!(produced.exposure > 0.0); + } + #[test] fn think_sheds_exposure_and_lie_reports_the_actual_physical_draw() { // machine-work.md criterion 3: THINK produces one visible substance @@ -9356,16 +9393,17 @@ mod tests { #[test] 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. + // reaches the current core sink. THINK now starts immediately between + // pulses, but progress still waits for an economy pulse to count only + // the Thought which physically arrived before it. let mut sim = Sim::with_seed(77); complete_opening_senses(&mut sim); // no opening sink intercepts the core delegate_all(&mut sim, MachineMode::Think); - run(&mut sim, ECONOMY_INTERVAL); + run(&mut sim, ECONOMY_INTERVAL - 1); assert_eq!( sim.research.progress.iter().sum::(), 0.0, - "allocation alone advances nothing before bone reaches the core" + "arrived Thought is banked until the economy pulse" ); assert!( sim.work_grid @@ -9375,7 +9413,7 @@ mod tests { .sum::() + sim.banked_core_thought > 0.0, - "the pulse minted visible thought somewhere on the graph" + "immediate THINK minted visible Thought somewhere on the graph" ); let saw_core_swallow = sim.work_consumptions().iter().any(|event| { event.family == TokenFamily::Thought @@ -9383,15 +9421,13 @@ mod tests { && event.amount > 0.0 }); - // Delegate the whole fleet away: minting stops, but Thought already - // banked by the core still counts on the next economy pulse. + // Delegate the whole fleet away before the pulse: minting stops, but + // Thought already banked by the core still counts at the boundary. delegate_all(&mut sim, MachineMode::Lie); - for _ in 0..ECONOMY_INTERVAL { - sim.advance(); - } + sim.advance(); assert!( sim.research.progress.iter().sum::() > 0.0, - "in-flight bone still arrives and counts after delegation changes" + "banked Thought still counts after delegation changes" ); assert!( saw_core_swallow, diff --git a/wiki/log/2026-07-10-immediate-think-start.md b/wiki/log/2026-07-10-immediate-think-start.md new file mode 100644 index 00000000..e26385d0 --- /dev/null +++ b/wiki/log/2026-07-10-immediate-think-start.md @@ -0,0 +1,30 @@ +# THINK starts on the command + +``` +Type: log +``` + +- Intent: Remove the dead pause between delegating the first rack to THINK + and seeing the thought/exposure process begin. +- Finding: Continuous work used rates cached at the last twenty-tick economy + pulse. A mode command changed `WorkGrid` immediately but left the cached + THINK rate at zero until the next pulse, violating the first-think beat. +- Changed: Fleet-rate resolution is now one shared sim helper. Economy pulses + still establish the ordinary rates, while a successful mode assignment + refreshes those rates from the current physical fleet immediately. A + regression test delegates Rack 3 between pulses and requires positive + Thought and Exposure production on the first following simulation tick. +- Design/spec impact: Clarifies the existing instant-consequence clause in + `wiki/mechanics/machine-work.md`: first output is independent of economy + pulse phase. No tuning, save shape, or frontend-owned behavior changed. +- Checks: The new regression test failed against the stale-rate path and + passed after the fix. An observed agent-mode run (`delegate M1 think`, + `wait 1`) showed Rack 3 at THINK / Research 100% on command tick 0 and its + Exposure stack at `! 0.1` on tick 1. `./tools/check.sh --lib` passed all + 278 core tests, integration tests, clippy, Bevy API check, agent smoke, and + corpus/documentation gates. + +Defense: `wiki/mechanics/machine-work.md` requires first THINK to have instant +consequence with no delayed allocation pulse. Refreshing derived continuous +rates at mode assignment makes that existing contract true without moving any +game rule into a frontend. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 5d881a6c..b654697c 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -236,6 +236,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-10-implicit-thought-surface.md](2026-07-10-implicit-thought-surface.md) +## 2026-07-10 - THINK starts on the command + +- Intent: (see session log) +- Log: [wiki/log/2026-07-10-immediate-think-start.md](2026-07-10-immediate-think-start.md) + ## 2026-07-10 - Hover + 1-4 sets machine mode - Intent: Cameron: mode change should be hover over a rack and press 1-4 immediately — not enter + enter + select dial row + enter. diff --git a/wiki/mechanics/machine-work.md b/wiki/mechanics/machine-work.md index 69d86acd..7af61a41 100644 --- a/wiki/mechanics/machine-work.md +++ b/wiki/mechanics/machine-work.md @@ -441,8 +441,10 @@ a transgression you can feel: - **Instant consequence.** The moment THINK engages, the machine starts shedding crimson motes that slough off and bank into the stippled drift at its base (the decided exposure treatment above). No grace - period, no delayed tick — cause and effect in the same breath, so the - read is "*I* did that." + period, no delayed allocation pulse: Thought and proportional Exposure + begin on the first simulation tick after the command regardless of where + that command falls inside the twenty-tick economy interval. Cause and + effect stay in the same breath, so the read is "*I* did that." - **The exit lights up.** Enabling a THINK state immediately highlights the turn-it-off control (the WORK side of the dial / the quick toggle). The UI flinching toward the exit is what tells your gut you @@ -815,8 +817,9 @@ still. sink-permission list — ops sinks at run start, the core conversion locked until the Research unlock), and LIE (concealment, absorbs crimson). Toggling THINK starts crimson shedding - on the same tick, highlights the turn-it-off control (WORK), and one - press returns the machine to WORK. LIE is a deliberate cleanup + on the first simulation tick after the command, independent of economy + pulse phase, highlights the turn-it-off control (WORK), and one press + returns the machine to WORK. LIE is a deliberate cleanup choice, not the panic exit. The cheapest ops sink's affordability is visible while exposure accrues (the afford-reveal). 10. Thought flow (2026-07-10): THINK machines produce Thought (the