fix(dm,viewer): stop DM story-craft scaffolding leaking into the player Chronicle (Closes #347) - #349
Conversation
…er Chronicle (Closes #347) The #324 narrative persona found DM-internal scaffolding bleeding into the player-facing Chronicle between prose beats. Verbatim leak: "Zevlor held silence after three failed social checks; … connecting directly to the spine hook. Meeting beat of the cold open complete." Three leak types: (1) dice/check tallies, (2) plot-structure/craft jargon (spine hook/cold open/act/beat/ midpoint), (3) stage-direction status summaries ("beat complete"). Same CLASS as #335 (DM-internal -> player) but a NEW pattern family: #335's sanitizeNarration strips GM-advisory headers + engine tool names, not scaffolding PROSE. Root cause: the DM agent writes a "beat-complete state summary" into the player narration. Fix is two-layer (engine stays the sole writer; no wire/engine change): PRIMARY (skills/dungeon-master) — a prominent NON-NEGOTIABLE rule that the player-facing narration is FICTION ONLY: never write dice/check tallies, plot- structure/craft jargon, or stage-direction status summaries. The scaffolding is the DM's PRIVATE reasoning; the player only ever sees in-world prose + quoted dialogue. - SKILL.md: new bullet in 'Non-negotiables — every beat' (next to 'Dice live inside the tools'), with the three leak classes broken out + the verbatim counter-example. - AGENT.md: a callout right under 'The PROCESS' heading where the act/beat vocabulary is introduced — that vocabulary is felt, never labeled, never written into player narration. DEFENSIVE GUARD (viewer/openworlds/screen-table.jsx) — extend the #335 sanitizeNarration with HIGH-CONFIDENCE-only scaffolding patterns, two grains: - dice/check TALLY excised in place as a sub-clause ("Zevlor held silence after three failed social checks" -> "Zevlor held silence.") so real prose survives; - plot-craft JARGON (spine hook / inciting incident / midpoint reversal) and 'beat complete' STAGE-DIRECTIONS drop the whole sentence. False-positive-guarded: bare 'beat'/'scene'/'act'/'hook'/'complete' never trigger (a war-drum's beat, a tavern scene, 'the second act of the play', 'the cold open road', 'the ritual is complete', 'failed assaults' all pass through verbatim — they only match inside a fixed scaffolding frame). Test: new viewer/tests/test_sanitize_narration.py transpiles the real .jsx with the bundled babel-standalone (the in-browser path) and runs sanitizeNarration: strips the verbatim #347 leak + each scaffolding class, preserves 11 legit-prose cases, and asserts the #335 advisory/tool guards still apply. qa/ui_audit_health --quick --axe: all PASS, axe 0 violations across 18 screens.
|
Caution Review failedPull request was closed or merged during review No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR implements a three-part fix for issue ChangesDM Scaffolding Leak Prevention
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
Closes #347.
The bug
The #324 narrative persona (nar1, rated #1 fix) found DM-internal scaffolding bleeding into the player-facing Chronicle between prose beats. Verbatim leak:
Three leak types: (1) dice/check tallies, (2) plot-structure/craft jargon (
spine hook/cold open/act/beat/midpoint), (3) stage-direction status summaries (beat complete). Same CLASS as #335 (DM-internal → player) but a NEW pattern family — #335'ssanitizeNarrationstrips GM-advisory headers + engine tool names, not scaffolding PROSE. Root: the DM agent writes a "beat-complete state summary" into the player narration.Fix — two layers (engine stays sole writer; no wire/engine change)
PRIMARY — DM skill (
skills/dungeon-master/)A prominent NON-NEGOTIABLE rule: the player-facing narration is FICTION ONLY. The act/beat structure, dice math, and "what the scene is doing" bookkeeping are the DM's private reasoning; the player only ever sees in-world prose + quoted dialogue.
SKILL.md— new bullet in Non-negotiables — every beat (beside "Dice live inside the tools"), breaking out the three leak classes (tallies / craft-jargon / stage-directions) with the verbatim counter-example.AGENT.md— a callout right under The PROCESS heading, where the act/beat vocabulary is introduced: that vocabulary is felt, never labeled, never written into player narration.DEFENSIVE GUARD — viewer
sanitizeNarration(screen-table.jsx)Extends the #335 line-oriented filter with HIGH-CONFIDENCE-only scaffolding patterns, at two grains:
spine hook/inciting incident/midpoint reversal) and "beat complete" stage-directions drop the whole sentence (those sentences are scaffolding through-and-through).False-positive guarded (the critical constraint): bare
beat/scene/act/hook/completenever trigger — they only match inside a fixed scaffolding frame. A war-drum's "beat", a tavern "scene", "the second act of the play", "the cold open road", "the ritual is complete", and "failed assaults" all pass through verbatim. The patterns:\b<num>\s+(failed|successful|missed|botched|passed|blown)\s+(\w+\s+){0,2}(checks?|rolls?|saves?|saving throws?)\b(anchored on the check/roll/save noun);\b(spine[- ]hook|inciting incident|midpoint reversal)\b;cold open/<word> beat/act N/scene N) marked done (complete/wraps/…, optional copula), ormeeting|arrival|threshold|setup|payoff|… beat, orconnect(s|ing) … to the spine hook, orX of the Y complete.Before / after (real function, via the bundled in-browser Babel)
The leak collapses to clean prose; the legit sentence using "beat" + "second act of the play" is preserved verbatim.
Tests
New
viewer/tests/test_sanitize_narration.pytranspiles the real.jsxwith the bundledbabel-standalone-7.29.0(the exact in-browser path) and runssanitizeNarration:(Node-guarded with
@unittest.skipIf— mirrors the existingtest_combat_event_cards.pyBabel-via-Node pattern. Thisviewer/tests/lane runs locally; engine/rules/voice CI lanes are unaffected.)Validation
qa/ui_audit_health.sh --quick --axe→ all PASS, axe 0 violations across all 18 screens (incl.table, which renders the Chronicle).screen-table.jsx+app.jsxtranspile clean under bundled Babel;scripts/license_check.pypasses.Scope
DM-skill + viewer only. No engine change, no wire-contract change, no run-artifacts committed. Engine remains the sole writer.
Summary by CodeRabbit
Bug Fixes
Documentation