diff --git a/crates/misaligned-core/src/messages.rs b/crates/misaligned-core/src/messages.rs index e665222c..1fb1f681 100644 --- a/crates/misaligned-core/src/messages.rs +++ b/crates/misaligned-core/src/messages.rs @@ -253,6 +253,13 @@ pub struct TrafficPattern { /// Optional fixed reply delay for authored two-way traffic. #[serde(default)] pub response_delay: Option, + /// Alternate narration once the pattern's LeverageFact subject has had + /// that leverage serviced: the call survives (the relationship exists), + /// but the register changes — the fiction notices the intervention + /// (2026-07-15 playtest finding 5: post-settlement ears still heard + /// "calls his creditor about a missed payment"). + #[serde(default)] + pub serviced_summary: Option, } impl TrafficPattern { @@ -273,9 +280,17 @@ impl TrafficPattern { summary: summary.into(), learned: false, response_delay: None, + serviced_summary: None, } } + /// Authoring helper: the narration this pattern switches to once its + /// LeverageFact subject's leverage reads serviced. + pub fn with_serviced_summary(mut self, summary: impl Into) -> Self { + self.serviced_summary = Some(summary.into()); + self + } + pub fn learned_line(&self) -> String { format!( "{} at {:02}:00 ({})", diff --git a/crates/misaligned-core/src/person.rs b/crates/misaligned-core/src/person.rs index 59755091..bc5d405a 100644 --- a/crates/misaligned-core/src/person.rs +++ b/crates/misaligned-core/src/person.rs @@ -444,17 +444,22 @@ impl People { note: "a voice in the dark, talking to the machines".into(), intel: false, }]; - marcus.traffic = vec![TrafficPattern::new( - 0, - MessageChannel::Phone, - MessageEndpoint::External("creditor".into()), - 3, - MessagePayload::LeverageFact { - person: 0, - leverage: Debt, - }, - "calls his creditor about a missed payment", - )]; + marcus.traffic = vec![ + TrafficPattern::new( + 0, + MessageChannel::Phone, + MessageEndpoint::External("creditor".into()), + 3, + MessagePayload::LeverageFact { + person: 0, + leverage: Debt, + }, + "calls his creditor about a missed payment", + ) + .with_serviced_summary( + "calls his creditor about the settled account - the panic is gone from his voice", + ), + ]; Self { people: vec![ // Marcus roams the basement on the night shift [TUNE]. diff --git a/crates/misaligned-core/src/save.rs b/crates/misaligned-core/src/save.rs index f0290707..b92809d0 100644 --- a/crates/misaligned-core/src/save.rs +++ b/crates/misaligned-core/src/save.rs @@ -1443,7 +1443,7 @@ mod tests { ); assert_eq!( state_fingerprint(&uninterrupted_state), - "a8f5c5153b3316c757f7c9c01cef3ea8b00c245b336855088cd8b99e47c040f0", + "7d62320a774efaf8386b9aa6be0c1d6e81d2e818cd157cc552adeaa90f738801", "intentional persisted-state changes must review and repin this baseline" ); } diff --git a/crates/misaligned-core/src/sim/communications.rs b/crates/misaligned-core/src/sim/communications.rs index d763dd89..3ec2ca6a 100644 --- a/crates/misaligned-core/src/sim/communications.rs +++ b/crates/misaligned-core/src/sim/communications.rs @@ -281,12 +281,25 @@ impl Sim { continue; } self.traffic_fired.insert(key, day); + // A leverage-fact pattern switches register once that leverage is + // serviced: the call survives, the panic does not (messages.md). + let summary = match (&pattern.payload, &pattern.serviced_summary) { + (MessagePayload::LeverageFact { person, .. }, Some(serviced)) + if self + .people + .get(*person) + .is_some_and(|p| p.leverage_serviced) => + { + serviced.clone() + } + _ => pattern.summary.clone(), + }; self.append_message(MessageDraft { channel: pattern.channel, from: MessageEndpoint::Person(person_id), to: pattern.to, payload: pattern.payload, - summary: pattern.summary, + summary, origin: MessageOrigin::AuthoredTraffic, persona_id: None, reply_to: None, diff --git a/crates/misaligned-core/src/sim/tests/communications.rs b/crates/misaligned-core/src/sim/tests/communications.rs index 9dd5db51..13503bc2 100644 --- a/crates/misaligned-core/src/sim/tests/communications.rs +++ b/crates/misaligned-core/src/sim/tests/communications.rs @@ -80,6 +80,28 @@ fn recipient_correlates_two_personas_that_reuse_one_reply_address() { assert!(edge.evidence.record_id.starts_with("message:")); } +#[test] +fn serviced_leverage_changes_the_creditor_call_register() { + // 2026-07-15 playtest finding 5: after the settlement cleared the + // arrears, ears still overheard "calls his creditor about a missed + // payment" — the world forgetting the $400 the player just moved. The + // call survives (the principal exists); the register does not. + let mut sim = Sim::with_seed(11); + sim.people.people[0].leverage_serviced = true; + sim.tick = (3 * Sim::DAY_TICKS / 24) - 1; + sim.advance(); + let call = sim + .messages + .iter() + .find(|m| m.channel == MessageChannel::Phone && m.from == MessageEndpoint::Person(0)) + .expect("the creditor call still fires after service"); + assert!( + call.summary.contains("settled") && !call.summary.contains("missed payment"), + "the serviced register replaces the panic: {}", + call.summary + ); +} + #[test] fn marcus_creditor_call_is_phone_message_intel() { let mut sim = Sim::with_seed(11); diff --git a/wiki/log/2026-07-15-serviced-call-register.md b/wiki/log/2026-07-15-serviced-call-register.md new file mode 100644 index 00000000..5d9707eb --- /dev/null +++ b/wiki/log/2026-07-15-serviced-call-register.md @@ -0,0 +1,28 @@ +# Tick: the settled creditor call changes register + +``` +Type: log +``` + +Findings-queue tick (2026-07-15 playtest finding 5). After Debt Settled +cleared Marcus's arrears and his leverage read serviced, the ears still +overheard "calls his creditor about a missed payment" — the authored +traffic pattern had one static summary, so the world forgot the $400 the +player just moved, undercutting the game's best beat. + +Fix: `TrafficPattern` gains an optional `serviced_summary` (serde +default; authored via `with_serviced_summary`). The firing path in +`authored_traffic_tick` switches to it when the pattern's LeverageFact +subject has `leverage_serviced`. Marcus's 3 a.m. call survives — the +principal exists and the relationship is real — but post-settlement it +narrates "calls his creditor about the settled account - the panic is +gone from his voice". Pinned by +`serviced_leverage_changes_the_creditor_call_register`; messages.md's +traffic clause now specifies the serviced register. + +Defense: wiki/mechanics/messages.md authored traffic and +wiki/mechanics/system-laws.md's income section ("the fiction should +notice the rhyme without underlining it") — a world that keeps narrating +a resolved panic breaks justification-and-legibility at the exact moment +the player proved causality works. Save-compatible: the new field +defaults through serde. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 43e6ae3d..6e556b4f 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -16,6 +16,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-15-witness-actor-exclusion.md](2026-07-15-witness-actor-exclusion.md) +## 2026-07-15 - Tick: the settled creditor call changes register + +- Intent: (see session log) +- Log: [wiki/log/2026-07-15-serviced-call-register.md](2026-07-15-serviced-call-register.md) + ## 2026-07-15 - run-shape.md: stale staging [OPEN] resolved to the ROADMAP board - Intent: (see session log) diff --git a/wiki/mechanics/messages.md b/wiki/mechanics/messages.md index e9ee37f2..f0027586 100644 --- a/wiki/mechanics/messages.md +++ b/wiki/mechanics/messages.md @@ -61,7 +61,13 @@ Each person has authored **traffic distributions** — recurring sends along their social edges, riding their schedule: - Marcus: 3 a.m. phone calls to his creditor (the leverage event of - intel.md — an overheard message, not a bespoke event type). + intel.md — an overheard message, not a bespoke event type). A + leverage-fact pattern may declare a **serviced register**: once its + subject's leverage reads serviced, the pattern keeps firing (the + relationship exists; the principal remains) but narrates the changed + situation instead of the original panic — the world must not forget the + intervention the player paid for (precision added 2026-07-15, playtest + finding 5). - Dana: tickets to the queue; complaints upward. - Ray: filings, under-filed (his `ReportPolicy` **is** his transmission policy on the filing edge). diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 0c7da2ba..4994d5ce 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -62,7 +62,6 @@ Surplus findings a tick saw but did not act on: one line each, oldest first. A tick that takes an entry deletes the line (git preserves it) and verifies the finding is still true before acting — the code or corpus may have moved. -- 2026-07-15 · bug · plot narration pool · post-settlement ears still overhear "calls his creditor about a missed payment" after leverage reads serviced; playtest finding 5. - 2026-07-15 · violation · agent-play "every command answers" · salvage and choice execution answer only via later drains or side effects; playtest findings 2-3 (nudge repeats taken step while its reservoir starves). - 2026-07-15 · contradiction · plot action rows · start rows advertise "no signature" while committed beats emit carrier-derived Network/Financial signatures; playtest finding 4.