The dealer's voice
The single most important thing in this game is how Pale Jack speaks.
Per CLAUDE.md, the dealer is a Good Omens figure — a sympathetic
blend of Crowley and Aziraphale. An angel of death who has been on
Earth long enough to have very particular opinions about coffee,
certain songs, the quality of light in chapels, and you specifically.
The two registers
Section titled “The two registers”| Register | Tone | Example |
|---|---|---|
| Aziraphale | Precise, proprietary, genuine. Has read every book, attended every funeral, accumulated preferences. | ”I have not told her. She would change it.” |
| Crowley | Breezy inevitability, the detachment of someone who stopped pretending not to care. Dry wit that lands warm, not cold. | ”He has stopped pretending he is not enjoying these.” |
The lines are spoken by someone very old who has decided, after millennia, that fondness is the only reasonable response to all of it.
What the voice is not
Section titled “What the voice is not”| Don’t | Why |
|---|---|
| Cruel | Cruelty is cheap. Fondness is the horror. |
| Quippy | One-liners break the spell. |
| Theatrical | He is matter-of-fact about long, painful arrangements. |
| Modern slangy | He is older than any reasonable century. |
| Apologetic | He does not say sorry. He acts sorry. |
The “first night, second night” rule
Section titled “The “first night, second night” rule”He calls you by name on the second night, not the first.
Lines that use the {name} substitution token are gated to
minNightIndex >= 2. The picker filters them out on Night 1 even if
a name is set. This is the single most important voice rule in the
game.
Hand-authored, offline
Section titled “Hand-authored, offline”All dealer lines are hand-authored and shipped as a curated offline corpus. No runtime LLM. The corpus is:
- 717 lines in
lib/sim/dealer_voice.dart(year-1 ship target was ~700; we’re slightly over). - Indexed by
DealerEvent(greet / playerWin / push / lastCall / engineMoment / etc.). - Per-room tagged for greets in the diner / barbershop / drive-in / train / crossroads / final-room.
- Per-night gated via
minNightIndexfor late-week intimacy beats. - Per-courtesy gated for unlocks tied to
RunHistory.nightsPlayed.
Some examples by event
Section titled “Some examples by event”greet/diner: "Sit, sit. The coffee is terrible, I know. I've been meaning to raise it with someone for some years now."
greet/n7: "Saturday, {name}. I sit on this side tonight. You've known I would."
playerWin: "Yours. He nudges the chips across with one finger."
playerLose: "He gathers without remark."
engineMoment: "Mm. There it goes."
mercyCap: "Enough, friend. The room agrees with him."
lastCall: "He is sorry. He says so without the word."
acceptIt: "You sat with him. That was the part that mattered."How a line is picked
Section titled “How a line is picked”DealerVoice.pickFor() filters the corpus by:
- event match
- room match (if line is room-tagged)
- courtesy gate (
minCourtesy <= courtesy) - night gate (
minNightIndex <= nightIndex) - vow gate (if
requiresVowset) - name-line gate (
{name}lines filtered ifnightIndex < 2orplayerNameis null/empty)
Then samples deterministically through the run RNG, skipping recent repeats inside the same night (6-line buffer).
Tests as a contract
Section titled “Tests as a contract”test/sim/dealer_voice_test.dart— picker behavior + tone check (“no exclamation marks or ALL CAPS”).test/sim/dealer_voice_campaign_test.dart—{name}substitution- minNightIndex gating.
test/sim/dealer_corpus_integrity_test.dart— all lines have ids, every event has at least one line, etc.test/sim/dealer_corpus_floor_test.dart— per-event minimum counts- the 700-line floor.
If you change a line, the floor tests pass. If you add an exclamation mark to a line, the tone-check test fails.