What this PR is
This PR adds one file: a design exploration for issue #94, docs/design/skills-modifiers-layer.md. It is a recommendation, not an implementation — no skill schema, no UI, no economy retune. It mirrors the sibling settings-layer note (#90).
The recommendation is "The Editor's Hand": a small per-run draft of diegetic annotations that amplify the craft channel the game already has. Every annotation is craft-gated — its payout is a function of the Message Judge's grade, so a vague or reckless dispatch gets almost nothing back even after spending a charge. Writing stays king by construction: a skill multiplies or floors what a graded dispatch already earned; it never adds a flat number.
The note's credibility rests on two claims about the existing engine. This guide walks the source behind each so a reviewer can vouch for them. Every line range below was read against the tree at HEAD.
The pipeline — exactly the order the note claims
A turn's swing resolves server-side in callTimelineAI, in a fixed order: the model's number is clamped into the rolled band, then the anachronism amplifier widens it, then causal-chain decay diffuses it, then the gains-only craft and momentum multipliers bank the upside, then it is clamped to the per-turn fuse. The note's "resolution pipeline, in order" list is this block.
Band clamp (230-231) → anachronism (233) → chain decay (241) → gains-only craft × momentum (249-251) → ±50 clamp (252).
server/utils/openai.ts · 577 lines
⋯ 226 lines hidden (lines 1–226)
⋯ 325 lines hidden (lines 253–577)
Why "swing-neutral" is a myth here
The obvious lean skill is a reroll or a band-floor that adds no expected value and so (the claim goes) needs no retune. The note argues this is wrong, and the reason is in two places. First, craft already has two compounding channels: a ±2 die-tilt and a ×0.15–1.45 gains multiplier.
CRAFT_MODIFIER (±2 on the die) and CRAFT_GAIN_FACTOR (×0.15–1.45 on the gain) — the two channels a skill would amplify.
utils/craft.ts · 64 lines
⋯ 13 lines hidden (lines 1–13)
⋯ 25 lines hidden (lines 40–64)
Second, the gains multipliers apply only to a positive swing; losses pass through untouched. So the band → progress map is convex on the upside.
gained = decayed > 0 ? decayed * craftFactor * momoFactor : decayed — gains-only. Losses keep the rolled band's verdict.
server/utils/openai.ts · 577 lines
⋯ 241 lines hidden (lines 1–241)
⋯ 325 lines hidden (lines 253–577)
The trust boundary — the charge-economy blocker
The note's resource model is earned-in-play, server-minted charges with a hard cap of 2. That assumes the server can verify "this run earned a charge" and refuse a forged spend. It cannot today, because there is no server-authoritative per-turn run state. The handler reads momentum and the stake flag straight off the client body and only range-clamps them — the code says so itself.
staked = body.stake === true (no server-side canStake recheck); momentum from body.momentum, comment: "no server run-state today".
server/api/send-message.post.ts · 365 lines
⋯ 68 lines hidden (lines 1–68)
⋯ 290 lines hidden (lines 76–365)
The corridor the retune must not break
The run economy is tuned to a neutral-band mean the bakeoff eval pins to [4, 9]. It measures baseProgressChange — the swing before the four multipliers — on a fixture that sets no craft grade (the neutral ×1.0 factor).
bandMeans use r.baseProgressChange (110); the NEUTRAL anchor (index 2) must stay in [4, 9] (122-123).
evals/bakeoff.eval.ts · 372 lines
⋯ 105 lines hidden (lines 1–105)
⋯ 249 lines hidden (lines 124–372)
Because the corridor measures the pre-multiplier base at craft-neutral, an annotation that fires only at sharp/masterful or only on the gains side is a literal no-op there — the anchor cannot move. That is also why the corridor alone proves nothing about the armed regime, so the note makes an armed-masterful re-test (run the bakeoff at masterful with each annotation armed) a required CI gate. It is the same realized-EV eval the settings note (#90) already calls for; build it once.
Where a ⟐ badge would live
The game discloses its math, so a skill has to as well. The resolved-equation token list already renders ✒ craft, ⚡ anachronism, ⏳ chain, ✦ momentum, and ⚑ stake from server-echoed values, each with a hover hint from a shared constant so the copy and the number can't drift. A new ⟐ annotation token slots in here.
equationTokens(): the badge glyphs ✒ ⚡ ⏳ ✦ ⚑ a ⟐ annotation badge would join, plus the disclosed pre-send chip pattern.
components/MessageHistory.vue · 213 lines
⋯ 153 lines hidden (lines 1–153)
⋯ 33 lines hidden (lines 181–213)
What the note spawns
The exploration enumerates two implementation issues, ready to file, in dependency order — and the adversarial balance break-test cut one proposed skill before it could ship.
| Outcome | Detail |
|---|---|
| Issue 1 (foundation) | Server-authoritative per-turn run state + close the stake/momentum trust gaps. |
| Issue 2 (depends on 1) | “The Editor's Hand”: the per-run annotation draft, the earned-in-play charge ledger, server resolution at the verified hooks, ANNOTATION_CAP, the ⟐ badge, and the armed-masterful corridor re-test. |
| Cut by the break-test | Press Correction (a chain-decay floor lift) — the only non-craft-gated skill; it rewarded the least-grounded play and inverted the causal-chain dial's intent. |