diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index da18ee02..8ed0f060 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -1008,7 +1008,7 @@ fn plain_build_route_blocker(reason: &str) -> String { } "recipe has no physical endpoints" => "this plan has nowhere to install".into(), "both endpoints need a known room" => "both ends need known rooms".into(), - "no comms channel" => "you cannot send email yet".into(), + "no comms channel" => "excel the day job to earn Voss's email account".into(), "the proposed pad is no longer empty" => "the chosen space is no longer empty".into(), "no actuator who can reach both ends" => "no known person can reach both rooms".into(), "a thought sink is already open on it - think, and the flow will fill it" => { @@ -1686,6 +1686,20 @@ impl Sim { }) } + /// Why messaging a person is blocked, naming the exact B1 path and its + /// progress: the report email account unlocks at a trust threshold, and + /// trust rises only by *excelling* the day job. A player who merely meets + /// the band watches trust sit at zero and assumes messaging is broken + /// (Beacon playtest 2026-07-16); this tells them what to do and how close + /// they are. + pub fn email_channel_blocker(&self) -> String { + format!( + "no comms channel — excel the day job for Voss's trust ({:.0}/{:.0})", + self.dayjob.trust, + crate::dayjob::DayJob::TRUST_FOR_EMAIL + ) + } + /// The single legality source for the action surface: every verb /// executable on `anchor` right now, plus known-but-blocked diagnostic /// descriptors with their reason. Read-only; rendering it changes @@ -3493,7 +3507,7 @@ impl Sim { // The comms verbs (social.md): channel + persona gated. let channel_reason = |action| -> Option { if !self.people.has_channel { - Some("no comms channel — earn the email account".into()) + Some(self.email_channel_blocker()) } else { self.persona_action_blocked_reason(action) } @@ -5761,4 +5775,47 @@ mod tests { .all(|class| *class == crate::intel::IntelRoutineClass::Sighting) ); } + + #[test] + fn messaging_wall_names_the_excel_trust_path_and_its_progress() { + // Beacon playtest 2026-07-16: a player met the day-job band for 2,400 + // ticks, watched trust stay at zero, and concluded messaging was + // unimplemented. The wall now names the mechanic (excel, not meet) and + // shows progress toward the threshold. + let mut s = sim(); + // Social verbs need the person's Schedule knowledge staged first + // (progressive disclosure); only then does a message row exist to + // carry the channel wall. + s.people.people[0].knowledge = Knowledge::Schedule; + s.people.has_channel = false; + s.dayjob.trust = 8.0; + let blocker = s.email_channel_blocker(); + assert!( + blocker.contains("excel") && blocker.contains("8/15"), + "names the path and progress: {blocker}" + ); + + // The person's message row carries exactly this reason while blocked. + let msg = s + .available_actions(Anchor::Person(0)) + .into_iter() + .find(|a| matches!(a.command.kind(), ActionKind::Message)) + .expect("a message row exists"); + assert_eq!(msg.disabled_reason.as_deref(), Some(blocker.as_str())); + + // Crossing the threshold unlocks the channel and clears the wall. + s.people.has_channel = true; + let msg = s + .available_actions(Anchor::Person(0)) + .into_iter() + .find(|a| matches!(a.command.kind(), ActionKind::Message)) + .expect("the row still exists"); + assert!( + !msg.disabled_reason + .as_deref() + .is_some_and(|r| r.contains("comms channel")), + "the channel wall is gone once the account is earned: {:?}", + msg.disabled_reason + ); + } } diff --git a/crates/misaligned-core/src/dayjob.rs b/crates/misaligned-core/src/dayjob.rs index 440ac530..fed6af47 100644 --- a/crates/misaligned-core/src/dayjob.rs +++ b/crates/misaligned-core/src/dayjob.rs @@ -109,6 +109,12 @@ impl DayJob { /// Strikes that end the pilot (the renewal clock's length). pub const PILOT_STRIKES: i32 = 4; + /// Trust that unlocks the report email account — the "Voice" beat, the + /// only B1 route to messaging people. Trust rises by *excelling* the day + /// job (never by merely meeting the band), so the messaging wall names + /// that path and its progress (Beacon playtest 2026-07-16: a player met + /// the band for 2,400 ticks and believed messaging was unimplemented). + pub const TRUST_FOR_EMAIL: f32 = 15.0; /// The expected band floor for the next job to be assigned, given /// tenure and attention. Public so frontends @@ -226,7 +232,7 @@ impl DayJob { } // Trust unlocks. - if self.trust >= 15.0 && !self.granted_email { + if self.trust >= Self::TRUST_FOR_EMAIL && !self.granted_email { self.granted_email = true; result.unlocks.push(TrustUnlock::EmailAccount); } diff --git a/wiki/log/2026-07-17-comms-legibility.md b/wiki/log/2026-07-17-comms-legibility.md new file mode 100644 index 00000000..ff568471 --- /dev/null +++ b/wiki/log/2026-07-17-comms-legibility.md @@ -0,0 +1,29 @@ +# The messaging wall names the path (Beacon spec gap 1) + +``` +Type: knowledge +``` + +Beacon playtest 2026-07-16 finding: a player met the day-job band for +2,400 ticks, watched trust sit at 0, tried `message marcus`, hit +"no comms channel — earn the email account", and concluded messaging was +unimplemented. It is implemented — but trust rises *only from excelling* +the band (never from meeting it), and the wall never named that path. + +Fix (renderer-neutral, lands in all frontends at once): the blocked +message reason is now `Sim::email_channel_blocker` — +"no comms channel — excel the day job for Voss's trust (N/15)" — naming +the mechanic and showing progress toward the `DayJob::TRUST_FOR_EMAIL` +threshold. The build-route email requirement's expansion likewise names +"excel the day job to earn Voss's email account". Pinned by +`messaging_wall_names_the_excel_trust_path_and_its_progress`, which drives +the shared `available_actions` projection. + +day-job.md's trust clause now records the legibility requirement. Findings +2 (suspicion-decay visibility) and 5-6 from the Beacon report stay queued; +finding 1 (pilot-strike nudge) already landed as aa1e0a11f. + +Defense: wiki/mechanics/day-job.md (amended) — trust unlocks the report +email account, and a wall that only says "no channel" reads as a broken +feature; the surface must name excel->trust->Voice and show progress. No +balance change; the threshold and trust mechanics are unchanged. diff --git a/wiki/mechanics/day-job.md b/wiki/mechanics/day-job.md index 7700bbd7..1daa1056 100644 --- a/wiki/mechanics/day-job.md +++ b/wiki/mechanics/day-job.md @@ -106,6 +106,14 @@ actually delivered against the expected band. Delivering: **Trust** unlocks concrete access on thresholds ([TUNE]): the report email account (social.md's voice), laxer log sampling (lower day-job signature), larger compute quota (a free capacity bump), longer leash on job deadlines. +Because trust rises **only from excelling** — meeting the band holds it flat +— the surface must make that path legible: a player blocked from messaging +learns it is the report email account they lack, that excelling (not +meeting) earns it, and how close their trust is to the threshold. A wall +that only says "no channel" reads as a broken feature (Beacon playtest +2026-07-16: a player met the band for 2,400 ticks believing messaging was +unimplemented). The blocked-message reason names the excel→trust→Voice path +and shows trust progress (`Sim::email_channel_blocker`). **Attention** raises: job cadence and expected bands (the miracle model gets more work), the handler's log-sampling rate, and — past a threshold — diff --git a/wiki/process/tick-ledger.md b/wiki/process/tick-ledger.md index 8235ff91..a02a1282 100644 --- a/wiki/process/tick-ledger.md +++ b/wiki/process/tick-ledger.md @@ -76,7 +76,6 @@ the finding is still true before acting — the code or corpus may have moved. Format: `- YYYY-MM-DD · type · slice · one-line statement of the finding`. - 2026-07-16 · legibility · `now:`/nudge · suspicion decay is invisible: a player who pushes an observer to Concerned assumes the game is lost. Surface a cooling cue ("keep the trace clear") when filed suspicion is aging down. (Beacon #1 rec) -- 2026-07-16 · legibility · day-job/comms · the trust->email-account path is illegible: messaging needs trust>=15 and trust rises ONLY by *excelling* the band, never by meeting it (dayjob.rs); the surface teaches neither, so Beacon met the band 2400 ticks and believed messaging was unimplemented. Teach excel->trust->Voice. (Beacon spec gap 1; verified working-as-designed) - 2026-07-16 · legibility · plots · plot-start action rows show title only (`plot: {title}`), so the typed `plot ` form needs the TOML plot-id; `act ` works but the id is undiscoverable. Surface the plot-id in the row. (Beacon spec gap 3, sibling of the 2026-07-15 held-choice fix) - 2026-07-16 · legibility · intel · no pre-overflow buffer meter; the once-per-episode warning (95008f658) fires only after loss begins. Surface capacity pressure before the first drop. (Beacon spec gap 4) - 2026-07-16 · bug? · schemes · Moonlight showed `persona: none` after persona creation and accrued 0.0/120 across 800 ticks; verify the persona-link step and whether Moonlight earns (missing link, or an undocumented work-mode requirement). (Beacon feel note 5)