Skip to content

Corpus structure

DealerLine(
id: 'greet_n2_3', // Stable id
text: '"I remembered the name. {name}. It is a good one to keep in the mouth."',
event: DealerEvent.greet, // What surface fires this
room: 'diner', // Optional — null = any room
minCourtesy: 0, // Run-count gate (default 0)
minNightIndex: 2, // Campaign-night gate (default 0)
requiresVow: null, // Optional vow id requirement
)
EventWhen it fires
greetPlayer sits down at a new room.
playerWin / playerLose / pushHand resolution.
playerBlackjack / dealerBlackjackNaturals.
playerBust / dealerBustBust paths.
playerSurrenderPlayer surrenders.
lastCallCapital hits zero / terminal bust.
clearedNightAll 6 rooms cleared.
roomClearedA single room cleared (between rooms).
insuranceInsurance offer prompt.
vowAcceptedVow accepted at run-start.
arcanumAcquiredCharm purchased / placed.
midHandRare (~12%) aside during the player’s turn.
engineMomentA 2+ ×mult cascade with chips ≥ 100.
mercyCapMult cap fires.
acceptItPlayer Bergman-folds the final hand.
greet ~110
engineMoment ~67
midHand ~58
playerWin ~58
playerLose ~52
push ~48
playerSurrender ~41
arcanumAcquired ~40
clearedNight ~38
roomCleared ~29
lastCall ~28
mercyCap ~27
dealerBust ~27
playerBust ~24
vowAccepted ~23
insurance ~23
playerBlackjack ~21
dealerBlackjack ~18
acceptIt ~18

The corpus-floor regression test (dealer_corpus_floor_test.dart) pins per-event minimums so a refactor can’t silently shrink coverage.

Lines tagged usesName (anything containing the literal {name}) fire only when:

  1. nightIndex >= 2 (CLAUDE.md “second night, not first” rule).
  2. playerName is non-null and non-empty.

Pre-Night-2, the picker filters them out entirely. Post-pick, the caller renders via renderDealerLine(text, playerName, nightIndex), which performs the substitution and falls back to “you” if the substitution can’t be made.

DealerLine? pickFor({
required DealerEvent event,
String? room,
int courtesy = 0,
int nightIndex = 1,
String? playerName,
Set<String> activeVows = const {},
})
  1. Filters by event, room, courtesy, nightIndex, vow.
  2. Filters out {name} lines if no playerName / Night 1.
  3. Removes lines used in the recent-buffer (6 lines) if any non-recent options exist.
  4. Falls back to non-most-recent if all candidates were used recently.
  5. Samples via Rng.nextInt.

DealerVoice maintains a 6-line FIFO buffer of recently-picked ids. A picked line cannot repeat within 6 picks unless every candidate has been used recently.

The buffer persists in run snapshots (primeRecent(ids)) so a mid-run save / reload doesn’t reset the rotation.

Same Rng(seed) + corpus + buffer state produces the same picks. Share codes preserve seed; replays produce identical dealer lines.

  • Single file: lib/sim/dealer_voice.dart.
  • Hand-authored only. No runtime LLM per CLAUDE.md.
  • Tone gate: no exclamation marks, no ALL CAPS, no slang outside the Aziraphale-Crowley register.
  • Add to the right event. Test the floor passes.

See Contributing → Writing dealer lines for the contributor flow.