What changed, and why
The craft verdict is the line a player reads after each dispatch: ✒ your dispatch · Vague — <reason>. It was the game's main learning signal, but it only diagnosed. "Too vague" tells you the verdict, not the fix. Playtests caught this across skill tiers: one verdict read "Vague — appeals to sentiment without naming a lever" and a newcomer didn't know what a "lever" was, and nobody could tell Sound from Sharp from Masterful. You get five messages a run, so there's almost no room to learn craft by trial.
This change reframes the verdict to coach forward. Below the top grade, the reason now names the single concrete move that would raise it — a person, an act, a real lever of power, a precise moment — in plain words, never a bare label. At the top grade, it names the one thing that made the dispatch land, so the player can repeat it.
It's prompt-only: the same reason field, no schema or UI change. Two strings move — the instruction in the Judge prompt and its mirror in the structured-output schema — plus one test. The grade itself (the ±2 roll modifier) is untouched; only its player-facing explanation changes. The whole reason this is safe rests on one ordering fact, shown in section 3.
The Judge prompt: from label to coaching
buildJudgePrompt ends with the list of JSON fields the Judge must return. The only edit is the last bullet, "reason". The old text asked the model to "name what cut, or what was missing" — backward-looking. The new text asks it to point forward, and splits the two cases the verdict has to handle: below masterful there's a grade to climb to, so name the move that climbs it; at masterful there's nowhere higher, so name what worked.
The response-field list at the end of buildJudgePrompt. craft is asked for at line 263; reason at 265 — note that order.
server/utils/prompt-builder.ts · 532 lines
⋯ 255 lines hidden (lines 1–255)
⋯ 266 lines hidden (lines 267–532)
The schema mirror — and why it can't move the grade
The Judge returns structured output, so the same instruction lives a second time as the reason field's schema description. Keeping the two in step matters: the schema description is part of what the model reads. The reframed wording is echoed here, terser.
The load-bearing detail for safety is the property order. The model fills fields in schema order, so craft (line 355) is generated before reason (line 357). The grade is committed first; the reason is written afterward, from a grade that's already locked.
The craft_verdict schema. craft at 355, reason at 357 — the grade is emitted before the reason that explains it.
server/utils/openai.ts · 578 lines
⋯ 349 lines hidden (lines 1–349)
⋯ 218 lines hidden (lines 361–578)
The test that pins the intent
A unit test locks the reframe in. It would have passed on neither the old prompt (no "raise the grade", no "do NOT reweigh") nor a regression that quietly restored the old "name what cut, or what was missing" phrasing — so it's discriminating, not coverage padding. The comment records why the ordering keeps the evals safe, so a future editor doesn't undo the guard by accident.
Asserts the forward-coaching language is present and the old backward label is gone.