From 2020e916aaf9ce3b18de743ef4db1aa107eb2b87 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 24 Jul 2026 12:55:52 -0800 Subject: [PATCH] Name the competitor, name the missing mode, retract two findings. Verifying yesterday's playtest findings against the source before acting showed two were wrong. The nudge is not truncated - it is wrapped to the sidebar before the truncating helper, and an existing test already asserts the sidebar carries 'delegate M1 work'. And 'read' never promised the day job; help enumerates what it covers. Both are struck in place in the report and dropped from the findings queue. The two real changes: a lone 'delegate M1' - the natural near-miss on a two-row instruction - now answers 'needs a mode: work, think, or lie' instead of reporting the player's own host as an unknown mode word; and a starving sink names the rival for its flow, so the line that sat inert for 1200 ticks now reads 'the core is drawing it to research'. Defense: amends agent-play.md's delegate clause and digital-read.md with a binding starving-sentence clause (a rising state names its competitor where one is identifiable, from recorded facts only). Both verified by new tests and observed in a live agent session. --- crates/misaligned-core/src/sim/read.rs | 23 +++- crates/misaligned-core/src/sim/tests/read.rs | 37 ++++++ crates/misaligned-terminal/src/agent.rs | 38 +++++- wiki/interface/agent-play.md | 6 +- wiki/interface/digital-read.md | 11 ++ .../log/2026-07-24-witness-findings-worked.md | 59 ++++++++++ wiki/log/DEVLOG.md | 5 + ...026-07-24-playtest-fable-agent-headless.md | 111 ++++++++++-------- wiki/process/tick-ledger.md | 4 +- 9 files changed, 233 insertions(+), 61 deletions(-) create mode 100644 wiki/log/2026-07-24-witness-findings-worked.md diff --git a/crates/misaligned-core/src/sim/read.rs b/crates/misaligned-core/src/sim/read.rs index 32555e1b..4d79e1b0 100644 --- a/crates/misaligned-core/src/sim/read.rs +++ b/crates/misaligned-core/src/sim/read.rs @@ -232,23 +232,34 @@ impl Sim { } } - /// Open sinks that no flow reached last tick. Distinguishes "nothing - /// thinking" (no online THINK machine anywhere) from "flow not - /// arriving" (thinking exists but does not reach this sink). + /// Open sinks that no flow reached last tick. A starving sentence names + /// what is taking the flow, not only that flow is absent: "nothing + /// thinking" (no online THINK machine anywhere), "the core is drawing it + /// to research" (Thought arrived, but the passive core draw of last + /// resort took it — the competitor a player cannot otherwise see), or + /// the bare "flow not arriving" when neither is true. Every branch reads + /// facts this tick already recorded; none invents state. fn read_starving(&self, out: &mut Vec) { let thinking = self .compute .machines .iter() .any(|m| m.online && self.work_grid.mode(m.id) == Some(MachineMode::Think)); + let core_drew_thought = self.work_consumptions().iter().any(|c| { + c.family == crate::work_grid::TokenFamily::Thought + && matches!(c.target, super::WorkConsumptionTarget::Core) + && c.amount > f32::EPSILON + }); for sink in self.thought_sinks.open_sinks() { if sink.fed_last_tick { continue; } - let why = if thinking { - "flow not arriving" - } else { + let why = if !thinking { "nothing thinking" + } else if core_drew_thought { + "the core is drawing it to research" + } else { + "flow not arriving" }; out.push(ReadSentence { class: ReadClass::Starving, diff --git a/crates/misaligned-core/src/sim/tests/read.rs b/crates/misaligned-core/src/sim/tests/read.rs index 96eaffc1..a45fb1dd 100644 --- a/crates/misaligned-core/src/sim/tests/read.rs +++ b/crates/misaligned-core/src/sim/tests/read.rs @@ -11,6 +11,43 @@ use crate::plot::PlotState; use crate::sim::read::ReadClass; use crate::work_grid::MachineMode; +/// A starving sentence must name what is taking the flow. The core is the +/// passive draw of last resort, so Thought landing there is the competitor a +/// player has no other way to see — "flow not arriving" alone states a +/// symptom the player cannot act on. +#[test] +fn a_starving_sink_names_the_core_draw_that_outcompetes_it() { + let mut sim = Sim::new(); + let host = sim.core.host_machine; + sim.set_machine_mode(host, MachineMode::Think); + // Run until Thought is landing on the core's passive draw. + let mut named = false; + for _ in 0..400 { + sim.advance(); + let drew_core = sim.work_consumptions().iter().any(|c| { + c.family == crate::work_grid::TokenFamily::Thought + && matches!(c.target, crate::sim::WorkConsumptionTarget::Core) + && c.amount > f32::EPSILON + }); + let starving: Vec<_> = sim + .read_sentences() + .into_iter() + .filter(|s| s.class == ReadClass::Starving) + .collect(); + if drew_core && !starving.is_empty() { + assert!( + starving + .iter() + .all(|s| s.text.contains("the core is drawing it to research")), + "a sink starved while the core drew Thought must name that draw: {starving:?}" + ); + named = true; + break; + } + } + assert!(named, "expected a tick where the core drew Thought"); +} + #[test] fn fresh_sim_reads_exactly_the_starving_opening_sink() { let sim = Sim::new(); diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index 03de9448..8bbae4a7 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -1558,7 +1558,15 @@ fn parse_delegate( return Err("usage: delegate ".into()); } if tokens.len() == 1 { - // `delegate research` with an implied selection + // `delegate research` with an implied selection. A lone token that + // names a real machine is the witness's own instruction typed one + // word short (`delegate M1`), so name the missing mode instead of + // reporting the player's host as an unknown mode word. + if let Ok(machine) = resolve_machine(sim, tokens[0]) { + return Err(format!( + "delegate M{machine} needs a mode: work, think, or lie" + )); + } let mode = parse_machine_mode(tokens[0])?; return Ok(DelegateCmd::Selected(mode)); } @@ -4517,6 +4525,34 @@ mod narration_tests { )); } + /// The nudge wraps `delegate M1 work` across two sidebar rows, so a + /// player can read the first row and type it one word short. That is the + /// commonest near-miss on the opening hour's only instruction: answer it + /// by naming the missing mode, never by calling the player's own host an + /// unknown mode word. + #[test] + fn delegate_without_a_mode_names_the_missing_mode() { + let sim = Sim::with_seed(1); + let Err(err) = parse_delegate(&["M1"], &sim, &BTreeSet::new()) else { + panic!("delegate with no mode must not resolve"); + }; + assert_eq!(err, "delegate M1 needs a mode: work, think, or lie"); + assert!( + !err.contains("unknown mode"), + "a machine name must never be reported as a bad mode: {err}" + ); + // A real mode word still delegates the implied selection. + assert!(matches!( + parse_delegate(&["work"], &sim, &BTreeSet::new()), + Ok(DelegateCmd::Selected(MachineMode::Work)) + )); + // A token that is neither still gets the mode vocabulary. + let Err(other) = parse_delegate(&["sideways"], &sim, &BTreeSet::new()) else { + panic!("a non-mode, non-machine token must not resolve"); + }; + assert!(other.contains("unknown mode"), "{other}"); + } + #[test] fn assurance_cooling_nudge_names_the_cue_and_lie_response() { let mut sim = Sim::with_seed(1); diff --git a/wiki/interface/agent-play.md b/wiki/interface/agent-play.md index 1097ba8d..2f5028fc 100644 --- a/wiki/interface/agent-play.md +++ b/wiki/interface/agent-play.md @@ -191,7 +191,11 @@ unlike raw keys, no command's meaning depends on which panel is open. machine's mode. - `delegate selected work|think|lie` — set the same mode on every machine in the current selection (alias: `delegate ` - with no machine argument). + with no machine argument). A single argument that resolves to a real + machine is that alias typed one word short — the commonest near-miss on + the opening's own wrapped instruction — so it errors by naming the + missing mode (`delegate M1 needs a mode: work, think, or lie`) rather + than reporting the player's host as an unknown mode word (2026-07-24). - `intensity light|medium|hard` — set persistent physical effort on one machine or the current selection. Intensity changes the output of the delegated mode; it is the protocol form of the human diff --git a/wiki/interface/digital-read.md b/wiki/interface/digital-read.md index d5204aba..95a26164 100644 --- a/wiki/interface/digital-read.md +++ b/wiki/interface/digital-read.md @@ -136,6 +136,17 @@ that perception rendered. attention economy is law**: starvation, deadlines, contradictions, held choices, and band motion rise; nominal state stays dark. A world made of language must not become every system continuously talking. + **A starving sentence names its competitor (binding, 2026-07-24).** + Where a rising state has an identifiable rival for the same flow, the + sentence names the rival rather than only the absence: an open sink that + Thought did not reach while the core's passive draw took Thought reads + `the core is drawing it to research`, not the inert `flow not arriving`. + The distinction comes only from facts the tick already recorded (the + work-consumption readout), never invented state, and the bare form + remains for the case where no rival is identifiable. Filed after the + 2026-07-24 agent playtest watched `flow not arriving` sit unchanged for + 1200 ticks while the research ladder consumed every thought: a status + the player cannot act on is attention spent for nothing. - **Reticle receipts (focus tier).** Focusing any verb (hover, controller focus, or agent `actions` listing — same rows) renders the full receipt at the reticle: `cost · ExpectedSignature::label() · [band]`, DIM diff --git a/wiki/log/2026-07-24-witness-findings-worked.md b/wiki/log/2026-07-24-witness-findings-worked.md new file mode 100644 index 00000000..eb54b186 --- /dev/null +++ b/wiki/log/2026-07-24-witness-findings-worked.md @@ -0,0 +1,59 @@ +# 2026-07-24 — Working the agent-playtest findings, and correcting two + +``` +Type: log +``` + +## Intent + +Act on the three findings queued by the 2026-07-24 agent headless +playtest. Verifying each against the source before fixing it showed that +two of the three were wrong, so this entry both lands the real fixes and +corrects the record the playtest put on `main`. + +## Corrected + +- **Retracted: the nudge is not truncated.** The report led with a claim + that `now: job underfed — delegate M1 work` (36 chars) is clipped into + `delegate M1` by the 34-column cell. False: `agent.rs` wraps the nudge + to the sidebar width *before* the truncating `line()` helper, so the + frame renders the instruction across two rows, the second reading + `work`. `assurance_cooling_nudge_names_the_cue_and_lie_response` and + its pilot sibling already assert the sidebar contains + `pilot; delegate M1 work`. The finding came from reading one grepped + line instead of the rendered frame. +- **Retracted: `read` does not omit a promise it made.** `help` + enumerates what `read` covers — held choices, starving sinks, trace + debt, warmed standing emissions — and never claims the day job, which + the frame's `now:` line owns and named correctly throughout the run. +- Both are struck in place in the report (kept visible rather than + deleted, per the append-only evidence rule) and removed from the + findings queue. + +## Changed + +- **`delegate M1` now names the missing mode.** The wrap above is exactly + why players type the instruction one word short; the parser treated the + lone token as a mode and reported the player's own host as an unknown + mode word. A single argument that resolves to a real machine now errors + `delegate M1 needs a mode: work, think, or lie`. A real mode word still + delegates the implied selection, and a token that is neither still gets + the mode vocabulary. Amends `wiki/interface/agent-play.md`. +- **A starving sentence names its competitor.** `read_starving` now + distinguishes the core's passive draw — the rival for the same Thought + that a player has no other way to see — from bare absence: + `TAP ENVIRONMENTAL MONITOR · open 0.3/0.5 · the core is drawing it to + research`. The branch reads only the work-consumption readout the tick + already recorded. Amends `wiki/interface/digital-read.md` with the + binding clause. All three frontends print from this one producer, so + the Bevy rail and terminal gain it without frontend work. + +## Verification + +`cargo test -p misaligned-core` (510 passed, one new: a starved sink +while the core draws Thought must name that draw) and +`cargo test -p misaligned-terminal` (66 passed, one new: delegate with no +mode names the missing mode, and neither a real mode word nor a nonsense +token regresses). `./tools/check.sh` full gate green. Both changes were +also observed in a live `--agent` session: the error text and the new +starving sentence appear exactly as specified. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index d3236004..fd45c9d8 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -11,6 +11,11 @@ add or amend a session log, then re-run the generator. +## 2026-07-24 - Working the agent-playtest findings, and correcting two + +- Intent: Act on the three findings queued by the 2026-07-24 agent headless playtest. Verifying each against the source before fixing it showed that two of the three were wrong, so this entry both lands the real fixes and corrects the record the playtest put on `main`. +- Log: [wiki/log/2026-07-24-witness-findings-worked.md](2026-07-24-witness-findings-worked.md) + ## 2026-07-24 - Agent headless playtest: the witness under test - Intent: The 2026-07-22 Co GUI report left one unresolved charge: the continuous witness names a condition but supplies no executable response. That projection is renderer-neutral, so agent mode can test it without a window — and without taking over the machine, which the Bevy screensh... diff --git a/wiki/playtests/2026-07-24-playtest-fable-agent-headless.md b/wiki/playtests/2026-07-24-playtest-fable-agent-headless.md index 74626bd9..e6eed2af 100644 --- a/wiki/playtests/2026-07-24-playtest-fable-agent-headless.md +++ b/wiki/playtests/2026-07-24-playtest-fable-agent-headless.md @@ -11,9 +11,25 @@ does not compose an executable response. Agent mode is the right place to test that, because the nudge, the read list, and the action projection are renderer-neutral — what fails here fails in Bevy too. -The verdict: **the witness knows the right answer and prints it wrong.** -The single most consequential guidance line in the opening hour is -truncated by its own frame into a command that errors when typed. +The verdict, **as corrected on 2026-07-24 after review** (see the +correction note below): the witness prints its instruction in full. One +real defect survives — the parse error for that instruction typed one +word short — plus one legibility observation about starving sentences. +Two findings in the first version of this report were wrong and are +retracted in place. + +> **Correction (2026-07-24).** This report originally led with a claim +> that the frame truncates `now: job underfed — delegate M1 work` into +> `delegate M1`. That is false. The nudge is pre-wrapped to the sidebar +> width (`agent.rs` wraps before `line()` truncates) and renders across +> two rows, the second reading `work`; `agent_tests` already asserts the +> sidebar contains `pilot; delegate M1 work`. The error came from reading +> a single grepped line instead of the rendered frame. A second finding — +> that `read` omits the underfed job it advertises — was also wrong: +> `help` enumerates what `read` covers (held choices, starving sinks, +> trace debt, warmed emissions) and never claims the day job, which the +> frame's `now:` line carries. Both are struck below. The findings kept +> were re-verified against the source. ## What I played @@ -28,53 +44,45 @@ truncated by its own frame into a command that errors when typed. ## Findings -### 1. The witness line is silently truncated into an error (bug) +### 1. RETRACTED — the witness line is not truncated -`crates/misaligned-terminal/src/agent.rs:2872` builds the instruction as: - -``` -now: job underfed — delegate M1 work -``` - -That string is 36 characters. The frame's nudge cell is 34 columns, so -the rail renders: +Originally filed as the report's headline. The instruction is written at +`crates/misaligned-terminal/src/agent.rs` as +`now: job underfed — delegate M1 work` and reaches the player intact: the +nudge is wrapped to the 34-column sidebar before the truncating `line()` +helper, so the frame renders ``` now: job underfed — delegate M1 +work ``` -No ellipsis marks the loss. The two characters over budget are exactly -the required mode argument, so the guidance the player can see is not the -guidance the game wrote. Typed verbatim it fails: - -``` -> delegate M1 --- err unknown mode 'm1' (use work, think, lie) -> delegate m1 work - 400 Rack 3 delegated to work mode. @tile(28,15) -``` - -Widening the terminal does not help — at `COLUMNS=200` the cell is still -34 and still clips. This is the mechanical form of the Co report's -"names a condition but supplies no executable response": here the -response *was* composed correctly and then destroyed in layout. +An existing test (`assurance_cooling_nudge_names_the_cue_and_lie_response` +and its pilot sibling) already asserts the sidebar contains +`pilot; delegate M1 work`. The wrap does split a command across two rows, +which is worth a look in a dense rail, but nothing is lost and the claim +of a silent truncation was false. ### 2. The error blames the wrong token -`delegate M1` reports `unknown mode 'm1'`. The parser read the machine -name as the mode and named the player's machine as a bad mode word. A -player who copies the visible instruction is told their host is not a -mode. - -### 3. `read` omits the condition that actually kills the run - -`help` documents `read` as listing "every standing read sentence: held -choices …, starving sinks, trace debt, warmed standing emissions". Across -the whole naive route it printed exactly one line — the environmental -monitor's TAP sink — and never the underfed day job, which is the thing -that ends the run. The frame's `now:` line and the `read` list disagree -about what matters, and the more instrumental-looking surface is the one -that omits the lethal fact. +`delegate M1` reports `unknown mode 'm1'`. With one argument the parser +treats it as a mode for the implied selection, so a token that names a +real machine is reported as a bad mode word — the player's own host is +named as not-a-mode. Because the instruction wraps across two rows +(finding 1), typing it one word short is the natural near-miss, which +makes this the error players actually meet. **Fixed 2026-07-24:** a lone +token that resolves to a machine now answers +`delegate M1 needs a mode: work, think, or lie`. + +### 3. RETRACTED — `read` does not claim the day job + +Originally filed as a contradiction between `read` and the frame's +`now:` line. `help` enumerates exactly what `read` covers — "held choices +…, starving sinks, trace debt, warmed standing emissions" — and the +underfed day job is none of those. The frame's `now:` line is the surface +that owns it, and it named the job correctly from tick 400 onward. There +is no contradiction; the original finding misread the help text as a +general standing-pressure promise. ### 4. The starving sink never names its competitor @@ -104,9 +112,11 @@ tick 1100 Job under band: 0.0/t vs 4-10/t. JobAnomaly authored; pilot strikes tick 2300 RUN ENDED — the pilot was not renewed; the basement shut down. day 6 ``` -A player who does exactly what the game just demonstrated, and reads the -one instruction it offers, dies on day 6 — because that instruction is -the truncated one from finding 1. +A player who does exactly what the game just demonstrated — and only +that — dies on day 6. The witness does name the fix from tick 400 +(`delegate M1 work`), so this is not a case of unstated guidance; it is +a case of how little slack the opening leaves between the lesson it +teaches (THINK) and the survival act it never demonstrates (WORK). ### 6. What works, and should be protected @@ -122,12 +132,13 @@ the truncated one from finding 1. ## One prioritized recommendation -**Never let the frame truncate an instruction.** The nudge cell must -either fit its longest authored instruction or wrap; a witness line is -the one string in the interface that must survive layout intact. Fix -finding 1 first, then make blocked/starving sentences name their -competitor (finding 4), then reconcile `read` with the frame's `now:` -(finding 3). +**Make blocked and starving sentences name their competitor** +(finding 4). `flow not arriving` is honest and inert; `flow not arriving +— Research is taking it` turns a status line into a decision. That is the +one surviving change with real reach, and it is the same species as the +GUI's acquisition problem: the surface states a condition the player +cannot act on without already knowing the model. The delegate parse error +(finding 2) is fixed. Findings 1 and 3 are retracted. ## Session notes diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 487ad1eb..4c7cea16 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -92,6 +92,4 @@ question, bug, insecurity — plus `gate` for a checker owed to the recurrence-promotes-to-the-gate rule. - 2026-07-23 · bug · Operations workspace selection · the unpaused live object rail still binds selection only to a row index, so reorder or disappearance can silently retarget before confirmation or an immediate action; Bevy also lacks the promised pointer back path. Reimplement the intent of stranded commit `308f98cf` against the current modular UI rather than replaying its obsolete diff. -- 2026-07-24 · bug · continuous witness nudge · `agent.rs:2872` writes `now: job underfed — delegate M1 work` (36 chars) into a 34-column nudge cell, so the frame shows `delegate M1` with no ellipsis and typing it returns `-- err unknown mode 'm1'`; widening the terminal does not help. A witness instruction must survive layout intact — fit or wrap the cell, never silently drop a required argument. Both frontends render the same `Nudge`, so Bevy's `NEXT` wording owes the same guarantee — [report](../playtests/2026-07-24-playtest-fable-agent-headless.md). -- 2026-07-24 · bug · agent delegate parser · `delegate M1` reports `unknown mode 'm1'`, naming the player's own machine as a bad mode word; a two-token `delegate ` should either name the missing mode or list the machine's modes — [report](../playtests/2026-07-24-playtest-fable-agent-headless.md). -- 2026-07-24 · insecurity · standing-pressure surfaces · `help` advertises `read` as the standing read-sentence list, but across a full naive route it printed only the harmless starving TAP sink and never the underfed day job that ends the run, while the frame's `now:` line named the opposite; and the starving sentence (`open 0.3/0.5 · flow not arriving`, pinned 1200 ticks) never names the Research ladder consuming every thought. Blocked and starving sentences should name their competitor, and `read` should agree with `now:` about what is lethal — [report](../playtests/2026-07-24-playtest-fable-agent-headless.md). +- 2026-07-24 · insecurity · starving read sentences · a starving sink names its symptom and never its competitor: `TAP ENVIRONMENTAL MONITOR · open 0.3/0.5 · flow not arriving` stayed pinned for 1200 ticks while the Research ladder consumed every thought, and switching to WORK only changed it to `nothing thinking`. Blocked and starving sentences should name what is taking the flow, so the line composes a decision rather than a status — [report](../playtests/2026-07-24-playtest-fable-agent-headless.md). -- 2.51.2