add a writer-facing content pipeline under writing/

Dialogue was already plain text, so the gap was never the .yarn files. It was
that the character material a writing session needs was either missing or stored
somewhere a writer could not comfortably work.

Three things were wrong. The writing handbook was not in the repo and was cited
by SC101.yarn as though it were. The best voice material — register, verbal tics,
success/failure/passive samples — was a \n-escaped string inside a JSON field.
The named NPCs had no sheets at all, existing only as their Yarn lines.

writing/ now holds the dialogue-facing layer, at the repo root so it gets no
.meta files and is not a Unity asset:

  STYLE.md          register, the five tone moves, the ban list, branching
                    shapes, the Wrong Truth rule, flag families, candidate rules
  YARN-PRIMER.md    the one page of syntax needed to write a scene
  GLOSSARY.md       in-world and project vocabulary
  voices/           the eleven skill voices, one readable sheet each
  characters/       Bartender, Chevalier Cassian Thal, Bradford Kane
  scenes/           SC-101's brief: intent, beats, checks, constraints
  lore/             the three identity readings and the rules that bind them

STYLE.md is a digest of the Mystery Writing Handbook in the Obsidian vault, with
pointers rather than copies. The vault stays the authoring home for design work,
and Ground Truth — the solution document — deliberately stays out of this repo.

The voice sheets are generated from skill_bible.json but are the source of truth
for the prose. tools/writing/sync_voices.py folds edits back; voicelib.py
guarantees notes -> dict -> notes is identity for all eleven, so --to-json on
unchanged sheets is byte-identical. VoiceSheetSyncTests fails the build on drift
in either direction, and was verified to fail on real drift rather than merely
to pass.

SC101.yarn's header is trimmed from prose rationale to the six rules that bind
while editing that file, with the reasoning moved to the scene brief.

EditMode 42/42. YarnCheck compiles 9 files / 35 nodes.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-25 20:23:42 +02:00
co-authored by Claude Opus 5
parent b915ad78c7
commit f1fadf7909
28 changed files with 2766 additions and 21 deletions
+86
View File
@@ -0,0 +1,86 @@
---
id: SC###
title: <title>
act: I
status: outlined # outlined | drafted | revised | in-engine | tested
yarn_file: Assets/Dialogue/Scenes/SC###.yarn
location:
reenterable: no
---
# SC-### — <title>
> One scene, one file, ideally one screen of spec. If this brief does not fit on a page, the
> scene is doing too much — split it. That discipline is what lets one writer hold a sixty-scene
> game in their head.
>
> This is the **brief**, not the dialogue. The words live in the `.yarn` file.
## Purpose
One line: what has changed by the end.
## Cast
Speaking characters, and which voices are prominent. **Cap voice introductions at three.**
## Entry conditions
Flags required. "None" is a good answer.
## Critical outputs
Things every build must be able to obtain. Each needs ≥2 independent routes — and independent
means different trace channels, not two checks in the same conversation.
| Clue id | Routes |
|---|---|
| | |
## Optional outputs
Missable by design. One route is fine.
## Shape
`hub-and-spoke` | `short tree` | `divergent`. Default to the first.
## Opening frame
2–4 sentences of establishing prose. Sensory, specific, and including one concrete detail that
does not serve the plot.
## Beats
3–6. Each beat is one exchange or one discovery.
1.
## Checks
Write **both** outcomes as summaries before writing any prose. If the failure summary is "the
player doesn't get the thing", delete the check.
| # | Voice | Colour/Gate | DC band | Success → | Failure type | Failure → |
|---|---|---|---|---|---|---|
| | | | | | | |
## Branch points
| id | The choice | Reconverges at | Persistent effect |
|---|---|---|---|
| | | | |
## Exit states
Keep to ≤3. More than three means this is a hub, not a scene.
## Flags
**Sets:**
**Reads:**
## Constraints specific to this scene
Anything a later editor must not "fix". Deliberate exceptions to a general rule go here, with
the reason, so they survive review.
@@ -0,0 +1,135 @@
---
id: SC101
title: The Card at the Desk
act: I
status: in-engine
yarn_file: Assets/Dialogue/Scenes/SC101.yarn
location: The conference hall — registration desk, the bar, the table
reenterable: yes
---
# SC-101 — The Card at the Desk
The opening. Playable end to end, and it exits into SC-102, which is still a one-line stub.
An opening scene is not a normal node in the sandbox. It has no entry conditions and almost
nothing to gate. Its job is to sell the register in one sitting — the voice-committee narration,
the tone, the world's texture of disrepair, and one open question the player carries into Act I.
## Purpose
Establish that the protagonist does not know who they are, that other people appear to, and that
this was arranged before they decided to come.
## Cast
**Speaking:** Narrator, the Bartender, Chevalier Cassian Thal, Bradford Kane (silent — unwritten).
**Voices introduced:** CONFLUENCE (Reason), FACEWORK (Social), STANDING ORDER (Self),
HOUSE POUR (Body). Also appearing: PROVENANCE, AMNESTY, THE FLOAT.
## Entry conditions
None.
## Critical outputs
| Clue id | Routes |
|---|---|
| `made_asset_intuition` | CONFLUENCE hook at the desk (`$visited_desk`) |
| `journalist_intuition` | FACEWORK hook at the desk (`$visited_desk`) |
| `inheritor_intuition` | STANDING ORDER hook at the desk (`$visited_desk`) |
All three are **single-route**, which is acceptable only because they are intuitions rather than
gates: they fire passively, with no roll, so no build can miss them. The object, testimony and
document clues for all three candidates are still `Planned` with no `sceneId` — see the registry
in `Assets/Candidates/candidates.json`.
## Optional outputs
- The Atlantean cross on the place card — `Hard` PROVENANCE, one route, missable by design.
- The Cyprus connection — `Hard` PROVENANCE with Chevalier Cassian Thal, then an inquiry.
- Mr. Doss, surfaced from either the Bartender or the Chevalier — two independent routes.
- The Estate drink, and whether it tastes like the protagonist's own past.
## Shape
Hub-and-spoke throughout. The chair menu (`SC101_Hook_Menu_Chair_Interaction`) is the hub and
re-evaluates which variation shows after every sub-conversation returns.
## Opening frame
> The mahogany doors are too heavy for the hinges someone recently, badly, tightened — you feel
> the extra half-second of resistance before either side gives. Beyond them: a hall built for
> four hundred people politely disagreeing about tariffs, hosting perhaps sixty, none of whom
> look like they've read the programme.
## Beats
1. Arrival at the desk. A card with the protagonist's name, left three hours before they decided
to come. Pure orientation, no plot delivery.
2. Four voices introduce themselves, one per pair, each reading the room differently.
3. The bar. The Bartender recognises someone the protagonist does not remember being.
4. The table. The place card, and a symbol that does not belong in Switzerland.
5. Two seated gentlemen. One of them mirrors back whatever the protagonist has been projecting.
6. Exit into SC-102 once a reading has been declared.
## Checks
| # | Voice | Colour/Gate | DC band | Success → | Failure type | Failure → |
|---|---|---|---|---|---|---|
| 1 | PROVENANCE | colour | Hard | the cross is identified as not-Swiss, sets the flag | Wrong Truth | "This is probably related to Switzerland." Same calm, no hedge. |
| 2 | PROVENANCE | colour | Hard | Basel reads as old money, opens the Cyprus inquiry | Wrong Truth | the inquiry never opens; nothing signals that it existed |
Both are colour, not gates: failure changes what the protagonist believes, never what they can
reach. The desk hooks are **check-free by design** — a roll there would produce a pass/fail state
that reads as evidence about identity, which this scene must not deliver.
## Branch points
| id | The choice | Reconverges at | Persistent effect |
|---|---|---|---|
| B1 | Admit the fugue to the Bartender, or claim "the usual" | bar conversation end | `$revealed_fugue_to_bartender`; lean to Made Asset or Inheritor |
| B2 | Whether the Estate tastes familiar | drink node end | ±1 Inheritor lean, first glass only |
| B3 | Accept or deny the Chevalier's reading | `SC101_Deny_Candidate` loop | `$declared_candidate_to_chevalier_cassian_thal`; lean |
## Flags
**Sets:** `$seen_sc_101`, `$visited_desk`, `$found_*_intuition` (×3), `$spoke_to_bartender`,
`$revealed_fugue_to_bartender`, `$looking_for_doss`, `$had_the_estate_drink`,
`$sat_down_at_table`, `$saw_knights_atlantean_cross_on_place_card`,
`$spoke_to_chevalier_cassian_thal`, `$spoke_to_bradford_kane`, `$inquired_about_cyprus`,
`$declared_candidate_to_chevalier_cassian_thal`, `$self_lean_*`
**Reads:** all of the above, plus `$player_name`, `$player_name_full`, `$player_alias`
## Constraints specific to this scene
These are deliberate. Do not "fix" them.
1. **Four voice introductions, against the cap of three.** One per pair — Self, Body, Social,
Reason — because each candidate carries one intuition from a different voice-pair, and cutting
to three would drop a candidate's intuition channel from its earliest appearance. HOUSE POUR
is the non-lean control and the one to cut if the cap is ever enforced strictly. This is a
logged exception for SC-101 only; do not copy it as precedent elsewhere.
2. **Evidence balance.** Each candidate gets exactly one of each evidence type. Never stack
types, or trust becomes an artefact of imbalance rather than of play.
3. **Beat order is fixed.** Hooks fire in sequence from `SC101_Desk`: STANDING ORDER → HOUSE POUR
→ FACEWORK → CONFLUENCE. Each stays an independently authored node. Never `<<if>>`-guard a
hook on another hook having fired, on any `$self_lean_*`, or on `skill_rank(...)`.
4. **The passive pattern for clue-bearing hooks.** One voice per hook, from the candidate's
attached pair. The line is that character's *argument*, never a report. Exactly one
`$self_lean_*` write, with `$found_<clue_id>` set alongside it.
5. **The body of the scene stays neutral.** No lean, no candidate attachment outside the hooks.
6. **The Chevalier's tie bias.** `top_skill` receives `made_asset` first and returns its first
argument on a tie, so an even score reads as Made Asset. Deliberate, not an accident of
argument order.
## Open work
- `SC101_Hook_Questions_Bartender_Talk` is placeholder text ("Question 1 / Answer 1"). It is a
working loop with nothing in it, and the natural home for the missing testimony clues.
- Bradford Kane is reachable and silent.
- The Atlantean cross is never connected to the knight who is sitting right there.
- Tone could not be certified against a voice sheet when this scene was specced, because one did
not exist. See `STYLE.md` §9 — four fields there still need deciding.