From 14c4ca9461afdb2a65e4f8ee8d064a7945105449 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Fri, 24 Jul 2026 15:35:12 -0700 Subject: [PATCH] Speak plain English for thought-waiting status. Defense: amends digital-read.md so starving sentences name the action, optional percent progress, and a plain cause (no machine is thinking / research is taking the thought / thought isn't reaching it) instead of reservoir fill fractions and protocol words. Process start logs, Operations progress, and frontend chrome drop "thought reservoir" and "processing sink" from player copy. Clears the starved-sentence findings-queue line; agent smoke and rail-collapse pins follow the new wording. --- crates/misaligned-bevy/src/main.rs | 24 +++++------- crates/misaligned-bevy/src/rail_ui.rs | 10 +++-- .../src/operations_projection.rs | 4 +- .../misaligned-core/src/sim/communications.rs | 5 +-- crates/misaligned-core/src/sim/read.rs | 32 +++++++++------- crates/misaligned-core/src/sim/tests/read.rs | 10 ++--- crates/misaligned-terminal/src/agent.rs | 6 +-- crates/misaligned-terminal/src/ui.rs | 2 +- tools/check.sh | 2 +- wiki/interface/digital-read.md | 33 +++++++++-------- wiki/log/2026-07-24-plain-starving-copy.md | 37 +++++++++++++++++++ wiki/log/DEVLOG.md | 5 +++ wiki/process/tick-ledger.md | 1 - 13 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 wiki/log/2026-07-24-plain-starving-copy.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 0658b441..3baa26e8 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -4850,28 +4850,22 @@ mod menu_focus_tests { magnitude: None, text: text.into(), }; - // Six same-cause starving sinks collapse to one counted line; a - // held sentence and a two-sink cause stay verbatim. + // Four same-cause starving lines collapse to one counted line; a + // held sentence and a one-sink cause stay verbatim. let rail = vec![ mk("QUESTION - choose someone", ReadClass::Held), + mk("EARS · no machine is thinking", ReadClass::Starving), + mk("TAP SWITCH · no machine is thinking", ReadClass::Starving), mk( - "EARS · open 0.0/3.0 · nothing thinking", + "TAP DOCK CAMERA · no machine is thinking", ReadClass::Starving, ), mk( - "TAP SWITCH · open 0.0/0.5 · nothing thinking", + "TAP BADGE CONTROLLER · no machine is thinking", ReadClass::Starving, ), mk( - "TAP DOCK CAMERA · open 0.0/0.5 · nothing thinking", - ReadClass::Starving, - ), - mk( - "TAP BADGE CONTROLLER · open 0.0/0.5 · nothing thinking", - ReadClass::Starving, - ), - mk( - "EYES · open 0.2/12.0 · flow not arriving", + "EYES · 2% full · thought isn't reaching it", ReadClass::Starving, ), ]; @@ -4880,8 +4874,8 @@ mod menu_focus_tests { lines, vec![ "- QUESTION - choose someone".to_string(), - "- 4 sinks starving · nothing thinking (EARS +3 more)".to_string(), - "- EYES · open 0.2/12.0 · flow not arriving".to_string(), + "- 4 waiting for thought · no machine is thinking (EARS +3 more)".to_string(), + "- EYES · 2% full · thought isn't reaching it".to_string(), ], "held stays verbatim, big group counts, small cause stays verbatim" ); diff --git a/crates/misaligned-bevy/src/rail_ui.rs b/crates/misaligned-bevy/src/rail_ui.rs index 3eed4dcb..7f42060d 100644 --- a/crates/misaligned-bevy/src/rail_ui.rs +++ b/crates/misaligned-bevy/src/rail_ui.rs @@ -792,7 +792,11 @@ pub(super) fn collapse_read_lines(rail: &[misaligned::sim::read::ReadSentence]) lines.push(format!("- {}", s.text)); } } - for cause in ["nothing thinking", "flow not arriving"] { + for cause in [ + "no machine is thinking", + "research is taking the thought", + "thought isn't reaching it", + ] { let group: Vec<&&str> = starving.iter().filter(|t| t.ends_with(cause)).collect(); match group.len() { 0 => {} @@ -800,7 +804,7 @@ pub(super) fn collapse_read_lines(rail: &[misaligned::sim::read::ReadSentence]) n => { let first = group[0].split(" · ").next().unwrap_or(group[0]).to_string(); lines.push(format!( - "- {n} sinks starving · {cause} ({first} +{} more)", + "- {n} waiting for thought · {cause} ({first} +{} more)", n - 1 )); } @@ -1091,7 +1095,7 @@ fn sidebar_cycle_rows_text(sim: &Sim, selected: usize) -> String { .unwrap_or(0), )); s.push_str(&format!( - "thought reservoirs {} open", + "{} waiting for thought", sim.sink_readouts().len(), )); if let Some(stack) = sim.work_stack_for_machine(sim.core.host_machine) { diff --git a/crates/misaligned-core/src/operations_projection.rs b/crates/misaligned-core/src/operations_projection.rs index f3c92afa..218e5b89 100644 --- a/crates/misaligned-core/src/operations_projection.rs +++ b/crates/misaligned-core/src/operations_projection.rs @@ -1226,7 +1226,7 @@ impl Sim { provenance: vec![format!("{} @ tick {}", recording.feed, recording.tick)], facts, progress: if processing { - vec!["PROCESSING: THOUGHT RESERVOIR FILLING".into()] + vec!["THINKING TO PROCESS".into()] } else { Vec::new() }, @@ -2367,7 +2367,7 @@ impl Sim { ], progress: vec![ format!("filling: {:.0}%", pct), - "wait: thought reservoir filling".into(), + "wait: still thinking".into(), ], related: vec![OperationsLink { relation: "TARGET", diff --git a/crates/misaligned-core/src/sim/communications.rs b/crates/misaligned-core/src/sim/communications.rs index b95da5d8..da235192 100644 --- a/crates/misaligned-core/src/sim/communications.rs +++ b/crates/misaligned-core/src/sim/communications.rs @@ -1781,10 +1781,7 @@ impl Sim { self.thought_sinks .open_reservoir(node, &format!("PROCESS {raw_id}"), tokens, effect); self.ensure_sink_ingress(); - self.push_log(format!( - "Opened processing sink: {:.2} Thought on host for information #{raw_id}. THINK to fill it.", - tokens - )); + self.push_log("Processing a recording on the host — think to finish it."); true } diff --git a/crates/misaligned-core/src/sim/read.rs b/crates/misaligned-core/src/sim/read.rs index 4d79e1b0..4c5d57b4 100644 --- a/crates/misaligned-core/src/sim/read.rs +++ b/crates/misaligned-core/src/sim/read.rs @@ -232,13 +232,12 @@ impl Sim { } } - /// 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. + /// Open sinks that no Thought reached last tick. Human wording only: + /// name the action, optional progress, and what is taking the thought. + /// Causes: "no machine is thinking", "research is taking the thought" + /// (core's passive draw — the competitor a player cannot otherwise see), + /// or "thought isn't reaching it" when no rival is identifiable. Every + /// branch reads facts this tick already recorded; none invents state. fn read_starving(&self, out: &mut Vec) { let thinking = self .compute @@ -255,20 +254,25 @@ impl Sim { continue; } let why = if !thinking { - "nothing thinking" + "no machine is thinking" } else if core_drew_thought { - "the core is drawing it to research" + "research is taking the thought" } else { - "flow not arriving" + "thought isn't reaching it" + }; + let text = if sink.threshold > f32::EPSILON && sink.fill > f32::EPSILON { + let pct = ((sink.fill / sink.threshold) * 100.0) + .round() + .clamp(1.0, 99.0) as i32; + format!("{} · {pct}% full · {why}", sink.label) + } else { + format!("{} · {why}", sink.label) }; out.push(ReadSentence { class: ReadClass::Starving, anchor: None, magnitude: None, - text: format!( - "{} · open {:.1}/{:.1} · {why}", - sink.label, sink.fill, sink.threshold - ), + text, }); } } diff --git a/crates/misaligned-core/src/sim/tests/read.rs b/crates/misaligned-core/src/sim/tests/read.rs index a45fb1dd..155bd753 100644 --- a/crates/misaligned-core/src/sim/tests/read.rs +++ b/crates/misaligned-core/src/sim/tests/read.rs @@ -11,10 +11,10 @@ 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 +/// A starving sentence must name what is taking the thought. 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. +/// player has no other way to see — "thought isn't reaching it" 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(); @@ -38,7 +38,7 @@ fn a_starving_sink_names_the_core_draw_that_outcompetes_it() { assert!( starving .iter() - .all(|s| s.text.contains("the core is drawing it to research")), + .all(|s| s.text.contains("research is taking the thought")), "a sink starved while the core drew Thought must name that draw: {starving:?}" ); named = true; @@ -55,7 +55,7 @@ fn fresh_sim_reads_exactly_the_starving_opening_sink() { assert!( sentences.iter().any(|s| s.class == ReadClass::Starving && s.text.contains("EARS") - && s.text.contains("nothing thinking")), + && s.text.contains("no machine is thinking")), "the pre-opened Ears sink is starving at tick 0: {sentences:?}" ); assert!( diff --git a/crates/misaligned-terminal/src/agent.rs b/crates/misaligned-terminal/src/agent.rs index 8bbae4a7..ad8fec83 100644 --- a/crates/misaligned-terminal/src/agent.rs +++ b/crates/misaligned-terminal/src/agent.rs @@ -1999,7 +1999,7 @@ fn help_lines() -> Vec { ("reach", "inspect the earned device graph"), ( "read", - "list every standing read sentence: held choices with their exact option ids, starving sinks, trace debt, warmed standing emissions", + "list every standing read sentence: held choices with their exact option ids, actions waiting for thought, trace debt, warmed standing emissions", ), ( "intel", @@ -2627,7 +2627,7 @@ fn render_sidebar(sim: &Sim, cursor: (i32, i32)) -> Vec { ); line( &mut lines, - &format!("thought reservoirs {} open", sim.sink_readouts().len()), + &format!("{} waiting for thought", sim.sink_readouts().len()), ); if let Some(stack) = sim.work_stack_for_machine(sim.core.host_machine) { line( @@ -3229,7 +3229,7 @@ fn render_reach(sim: &Sim) -> String { lines.push(panel_title("REACH")); panel_story_spine(&mut lines, sim); lines.push(panel_line(&format!( - "thought reservoirs {} open · reach spreads from what you control", + "{} waiting for thought · reach spreads from what you control", sim.sink_readouts().len() ))); lines.push(panel_line("DEVICE SEGMENT REACH")); diff --git a/crates/misaligned-terminal/src/ui.rs b/crates/misaligned-terminal/src/ui.rs index 5dbe9796..3f6e2319 100644 --- a/crates/misaligned-terminal/src/ui.rs +++ b/crates/misaligned-terminal/src/ui.rs @@ -1451,7 +1451,7 @@ impl UI { line( stdout, &mut row, - &format!("thought reservoirs {} open", sim.sink_readouts().len()), + &format!("{} waiting for thought", sim.sink_readouts().len()), pal::FAINT, )?; // Parity of legibility (computer-visual-language.md criterion 7): diff --git a/tools/check.sh b/tools/check.sh index 34370648..dfc4289e 100755 --- a/tools/check.sh +++ b/tools/check.sh @@ -303,7 +303,7 @@ run_agent_smoke() { cmp -s "$tmp_a" "$tmp_b" || { echo "FAIL: same --seed agent runs differ"; smoke_fail=1; } cmp -s "$tmp_a" "$tmp_c" && { echo "FAIL: different --seed agent runs matched"; smoke_fail=1; } LC_ALL=C grep -q $'\033' "$tmp_a" && { echo "FAIL: agent mode emitted ANSI escapes"; smoke_fail=1; } - for pat in "MISALIGNED" "PEOPLE" "RESEARCH" "intensity " "help: wait N" "Opened processing sink" "-- ok tick:"; do + for pat in "MISALIGNED" "PEOPLE" "RESEARCH" "intensity " "help: wait N" "Processing a recording on the host" "-- ok tick:"; do grep -q -- "$pat" "$tmp_a" || { echo "FAIL: agent mode output missing '$pat'"; smoke_fail=1; } done rm -f "$tmp_a" "$tmp_b" "$tmp_c" diff --git a/wiki/interface/digital-read.md b/wiki/interface/digital-read.md index 95a26164..feda0c4a 100644 --- a/wiki/interface/digital-read.md +++ b/wiki/interface/digital-read.md @@ -12,10 +12,12 @@ Status note: IMPLEMENTED. Adopted 2026-07-15 from the Fable playtest is deferred (presence.md). Current state, criterion by criterion: - **The projection (1).** `Sim::read_sentences` (`sim/read.rs`) is the one renderer-neutral producer: Held choices (carrying each option's exact - bound `choose` command), Starving sinks, TraceDebt, and Standing - emissions (which surface only once their sampler warms above Cold — the - attention economy). Every frontend prints these; none composes its own - causal prose. The agent `read` verb prints the same sentences. + bound `choose` command), Starving actions waiting for thought (plain + English cause, optional percent progress — never reservoir fractions), + TraceDebt, and Standing emissions (which surface only once their sampler + warms above Cold — the attention economy). Every frontend prints these; + none composes its own causal prose. The agent `read` verb prints the same + sentences. - **Reticle receipt (2).** The hover verb bar shows the focused verb's cost + `ExpectedSignature` (observer + band) straight off `ActionDesc`, or no added attention. `ActionDesc::receipt_read` expands those fields for @@ -131,22 +133,21 @@ that perception rendered. - **Standing anchor sentences (glance tier).** Every earned anchor in view wears at most one short live sentence naming its current relationship to you: the switch carries `egress hum · Dana samples this · Curious`; a - starving reservoir carries `open · nothing thinking`; a held plot + starving action carries `TAP CAMERA · no machine is thinking`; a held plot carries its question. Stable-and-quiet state says nothing — **the 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. + **A starving sentence speaks plain English (binding, 2026-07-24).** It + names the waiting action, optional percent progress when partially filled, + and what is taking the thought — never reservoir fill fractions + (`open 0.3/0.5`), never "flow not arriving," never "nothing thinking." + Causes are: `no machine is thinking`, `research is taking the thought` + (core's passive draw — the competitor a player cannot otherwise see), or + `thought isn't reaching it` when no rival is identifiable. The distinction + comes only from facts the tick already recorded (the work-consumption + readout), never invented state. Filed after playtests watched protocol + English sit for thousands of ticks without composing a decision. - **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-plain-starving-copy.md b/wiki/log/2026-07-24-plain-starving-copy.md new file mode 100644 index 00000000..32d13e3e --- /dev/null +++ b/wiki/log/2026-07-24-plain-starving-copy.md @@ -0,0 +1,37 @@ +# 2026-07-24 — Starving and process lines speak plain English + +``` +Type: log +``` + +## Intent + +Player-facing status for open Thought work still read like a protocol +dump: `TAP ENVIRONMENTAL MONITOR · open 0.3/0.5 · flow not arriving`, +`nothing thinking`, `Opened processing sink: 0.40 Thought on host for +information #7`, and `thought reservoirs N open`. Playtests kept +quoting those lines as legibility failures. + +## Changed + +- Starving sentences: action name, optional percent full, and a plain + cause — `no machine is thinking`, `research is taking the thought`, + or `thought isn't reaching it`. No `open a/b` fractions. +- Process start log: `Processing a recording on the host — think to + finish it.` +- Operations progress: `THINKING TO PROCESS` / `wait: still thinking` + instead of "thought reservoir." +- Terminal, agent, and Bevy chrome: `N waiting for thought` instead of + "thought reservoirs N open." +- Rail collapse groups on the new causes and says `waiting for thought`. + +## Spec + +Amends digital-read.md's starving-sentence clause to bind the plain +English form. Clears the 2026-07-24 starving findings-queue line (the +competitor-naming intent is absorbed and the protocol English is gone). + +## Verification + +Core read tests and Bevy rail-collapse test updated and run under the +lib gate. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index cbfdcff7..8f87be77 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -26,6 +26,11 @@ add or amend a session log, then re-run the generator. - Intent: Reported from play: "it wasn't really obvious why we were clearing Marcus's gambling debts." The row read `plot: Debt Settled — Settle Marcus's gambling debt through a negotiator persona before the creditor's deadline` — an act, a persona, a deadline, and no stated return for... - Log: [wiki/log/2026-07-24-plot-rows-name-their-payoff.md](2026-07-24-plot-rows-name-their-payoff.md) +## 2026-07-24 - Starving and process lines speak plain English + +- Intent: Player-facing status for open Thought work still read like a protocol dump: `TAP ENVIRONMENTAL MONITOR · open 0.3/0.5 · flow not arriving`, `nothing thinking`, `Opened processing sink: 0.40 Thought on host for information #7`, and `thought reservoirs N open`. Playtests kept qu... +- Log: [wiki/log/2026-07-24-plain-starving-copy.md](2026-07-24-plain-starving-copy.md) + ## 2026-07-24 - One keyboard map, and a jump to what you earned - Intent: Two asks from play. First: put the keyboard map in the spec. Second: bind `Shift`+arrow, after a survey showed the strongest unclaimed need was not region select but *getting back to a machine you lost* — the Co playtest's core unresolved complaint ("Rack 3 moved out of view a... diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 4c7cea16..df9fb517 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -92,4 +92,3 @@ 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 · 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