User-authored content edits (schema-validated: 543 unit tests pass): - specs: explicit minPlayers on mawfang-pursuit (2), old-friend-bad-timing (3), the-clock-maker (1) — documents author intent per Feature D. - the-clock-maker: passiveReveals section + a skillChecks note on the optional Feature A timer for the returning-customer deadline. - lore/vocabulary.yaml: curated locations (inn, village, road) + a new forest category. Co-Authored-By: Claude <noreply@anthropic.com>
255 lines
12 KiB
YAML
255 lines
12 KiB
YAML
# Encounter spec — "The Clock Maker"
|
||
# A grimdark random encounter at the House Mardonus star fortress (a river-
|
||
# crossroads trade hub). An antique-shop clock-maker secretly curses his
|
||
# clockwork wares; the curse compels owners to return the clock for free, and
|
||
# leaves them eerily punctual to — and happy at — their own griefs. The party
|
||
# walks in; a browsing customer and, if they dither, a desperate returning
|
||
# customer raise the stakes. Copy the SHAPE of specs/market-thief.yaml, not
|
||
# its content. Contract: docs/spec-authoring-guide.md; validator:
|
||
# EncounterSpecSchema in src/spec/loader.ts.
|
||
|
||
# encounterId — BOT ENFORCES. The session key and the id the LLM echoes in
|
||
# encounter_resolve. Unique across the corpus; stable forever once live.
|
||
encounterId: "the-clock-maker"
|
||
|
||
# title — BOT ENFORCES (Discord embeds) / LLM READS (opening scene header).
|
||
title: "The Clock Maker"
|
||
|
||
# tone — LLM READS (narration flavor) + BOT ENFORCES (drop-notice string).
|
||
tone: "grim"
|
||
|
||
# Group encounters (Feature D) — solo scene, explicit for documentation.
|
||
# Default would be 1; explicit value documents the author's intent.
|
||
minPlayers: 1
|
||
|
||
# setting — LLM READS. Three strings grounding the scene.
|
||
setting:
|
||
# location — where the scene happens. One line.
|
||
location: "The House Mardonus star fortress — a river-crossroads trade hub"
|
||
# mood — sensory/pacing directive; a few adjectives beat a paragraph.
|
||
mood: >
|
||
Bustling and indifferent at midday. River-noise, the clatter of cargo,
|
||
haggling voices. Nobody watches the wares closely.
|
||
# ambientNpcs — background life, no persona overhead.
|
||
ambientNpcs: >
|
||
River dockers and haulers moving crates along the quays. A steady flow of
|
||
travelers of many races passing through the hub.
|
||
|
||
# openingNarrative — LLM READS, PINNED (never trimmed). Posted verbatim at
|
||
# session start. The trigger the party walks into; the curse stays under the
|
||
# surface, discovered through play.
|
||
openingNarrative: >
|
||
You push through the door of an antique shop — a simple place that doesn't
|
||
call for attention, easy to miss in the hub's bustle. The man in the back
|
||
startles slightly, as though he didn't expect anyone to come in at all.
|
||
|
||
# npcs — LLM READS. 1–5 personas; persona is VOICE/BEHAVIOR, not a stat block
|
||
# (Foundry owns stats). nameKey binds the displayed name to a randomizable
|
||
# draw so the same spec yields fresh names per run.
|
||
npcs:
|
||
- id: "clockmaker"
|
||
name: "Bram"
|
||
nameKey: clockmaker_name
|
||
role: "Antique-shop clock maker"
|
||
persona: >
|
||
Warm and inviting, bumbling a bit, struggling to understand why anyone
|
||
came in at this time. Keeps rubbing his hands, even though they are clean.
|
||
- id: "customer"
|
||
name: "Edda"
|
||
nameKey: customer_name
|
||
role: "A customer browsing the shop"
|
||
persona: >
|
||
Happy — has never been on time to so many things before. Exclaims they
|
||
were on time to their wife's funeral, and on time to see their child be
|
||
trampled by a horse. It doesn't make them sad; they are happy to be there
|
||
when they need them.
|
||
- id: "returning_customer"
|
||
name: "Hale"
|
||
nameKey: returning_customer_name
|
||
role: "A returning customer, desperate to return a cursed clock"
|
||
persona: >
|
||
Distraught, barely holding together. They came back to return a clock
|
||
that will kill them in fifteen minutes exactly — they don't want to
|
||
leave their children without money. They beg, they don't bargain; the
|
||
deadline is on their face.
|
||
|
||
# goals — LLM READS (steered toward primary; secondary valid-but-not-main).
|
||
# hidden (default true): players don't see these. Each goal id is the
|
||
# encounter_resolve outcomeId; label becomes the closing embed's Outcome text.
|
||
goals:
|
||
hidden: true
|
||
primary:
|
||
- id: "break_curse"
|
||
label: >
|
||
The party breaks the cursed timepiece's hold on the customer.
|
||
secondary:
|
||
- id: "customer_leaves_cursed"
|
||
label: >
|
||
The customer pays and leaves, still smiling, on time to the next
|
||
tragedy.
|
||
- id: "maker_talks_free"
|
||
label: >
|
||
The clock-maker's warmth holds; the party leaves uneasy, the wares
|
||
still ticking.
|
||
- id: "clock_revealed"
|
||
label: >
|
||
The party sees the truth: the curse is in the clocks themselves, not
|
||
the maker — his hands stay clean, his wares do not.
|
||
|
||
# sportsmanshipRules — LLM READS. Hard guardrails; the LLM redirects absurd or
|
||
# game-breaking actions in-character.
|
||
sportsmanshipRules:
|
||
- "No instant kills on the non-threatening, unarmed figures — the clock-maker and the customers are victims, not combatants — without dramatic escalation first."
|
||
- "No controlling another player character's actions or speaking for them."
|
||
- "No spells, skills, or abilities a player has not established owning in a prior scene. The clockwork curse is the maker's, not the party's to replicate."
|
||
- "No metagaming the curse — its true nature is learned through the victims and the shop's evidence, not declared by players who couldn't yet know it."
|
||
- >
|
||
If a player attempts something absurd or game-breaking, respond in-character
|
||
to redirect, or break character with:
|
||
"⚠️ That wasn't great sportsmanship. Let's keep it grounded — what would
|
||
your character realistically attempt here?"
|
||
|
||
# skillChecks — BOT ENFORCES. Named DCs the LLM references BY NAME when it
|
||
# emits skill_check_emit. Name checks for the ACTION, not the stat. _skill/
|
||
# _note companions are LLM-read context; they never become dice. No dice
|
||
# results here — the bot rolls, the LLM narrates after [SKILL CHECK RESULT].
|
||
skillChecks:
|
||
examine_clock_dc: 13
|
||
examine_clock_skill: "Arcana or Investigation"
|
||
examine_clock_note: >
|
||
Studying a clock closely to sense the curse in the gear-work. Success
|
||
reveals the wrongness; failure reads it as an ordinary antique.
|
||
|
||
read_maker_dc: 12
|
||
read_maker_skill: "Insight"
|
||
read_maker_note: >
|
||
Reading the clock-maker. Success catches the clean-hand-rubbing tell and
|
||
the mask's seams; failure takes his warmth as genuine.
|
||
|
||
read_customer_dc: 10
|
||
read_customer_skill: "Insight or Perception"
|
||
read_customer_note: >
|
||
Sensing what's off about the first customer's eerie cheer. Success places
|
||
the wrongness; failure reads them as merely eccentric.
|
||
|
||
press_maker_dc: 14
|
||
press_maker_skill: "Persuasion or Intimidation"
|
||
press_maker_note: >
|
||
Pressing the maker to reveal the curse. His affable mask is practiced;
|
||
success cracks it, failure has him deflect with a joke and a discount.
|
||
|
||
calm_returning_customer_dc: 11
|
||
calm_returning_customer_skill: "Persuasion"
|
||
calm_returning_customer_note: >
|
||
Calming the desperate returning customer enough to act. Success steadies
|
||
them; failure leaves them spiraling.
|
||
|
||
break_curse_dc: 15
|
||
break_curse_skill: "Arcana — or the right roleplay action (undoing a mechanism, striking the gear)"
|
||
break_curse_note: >
|
||
Breaking the cursed timepiece's hold on a customer. A single check won't do
|
||
it alone — the party must find the right action first; this is the
|
||
resolution roll once they have.
|
||
|
||
# The returning customer's 15-minute deadline (see Hale / persona) is a
|
||
# narrative urgency cue by default. If the LLM wants it as a real
|
||
# engine-driven timer (Feature A), it may emit skill_check_emit with
|
||
# durationSeconds: 900 against calm_returning_customer — the timeout
|
||
# path then becomes bot-enforced auto-fail, not a narration-only beat.
|
||
# Spec-author opt-in; not required.
|
||
|
||
# Passive skill reveals (Feature B) — bot-applied at encounter start,
|
||
# group-visible, attributed to the qualifying player. threshold is a passive
|
||
# DC (integer); revealText is outcome prose only — no dice results (the bot
|
||
# owns dice). Thresholds gate roughly half the party, consistent with the PRD
|
||
# Feature B tone for the clock-maker's "something is wrong" entry point.
|
||
passiveReveals:
|
||
- skill: "Perception"
|
||
threshold: 13
|
||
revealText: >
|
||
Notices the clocks in the shop are not keeping the same time. Three of
|
||
them are five minutes apart, and a fourth has stopped — but its second
|
||
hand is still moving.
|
||
|
||
- skill: "Insight"
|
||
threshold: 14
|
||
revealText: >
|
||
Reads something rehearsed in the clock-maker's warmth. He is performing
|
||
friendliness the way a stage magician performs ease with the deck.
|
||
|
||
- skill: "Investigation"
|
||
threshold: 12
|
||
revealText: >
|
||
Spots a thin line of dust on the shelf where a clock once sat. The empty
|
||
place is the size of a pocket-watch. The shop keeps no receipts.
|
||
|
||
# randomizable — BOT ENFORCES. Fields that vary per run. Name draws bind to
|
||
# npc.nameKey (fresh names each run); cursed_ware seeds the specific ware.
|
||
# source: vocabulary + category route name draws to the vocabulary namespace.
|
||
randomizable:
|
||
- key: clockmaker_name
|
||
source: vocabulary
|
||
category: names.human.male
|
||
query: "common human male names for a Mardonar shopkeeper or artificer"
|
||
fallback: "Bram"
|
||
- key: customer_name
|
||
source: vocabulary
|
||
category: names.human
|
||
query: "common personal names in Mardonar and the Land of Mardonar"
|
||
fallback: "Edda"
|
||
- key: returning_customer_name
|
||
source: vocabulary
|
||
category: names.human
|
||
query: "common personal names in Mardonar and the Land of Mardonar"
|
||
fallback: "Hale"
|
||
- key: cursed_ware
|
||
query: "antique clocks and timepieces sold in Mardonar shops and markets"
|
||
fallback: "a tarnished pocket-watch that ticks without winding"
|
||
|
||
# tools — BOT ENFORCES. The active plugin set for this encounter. Every name
|
||
# must be a registered plugin (tests/unit/specsToolsConsistency.test.ts fails
|
||
# the build otherwise). Declared explicitly so the active set is intentional.
|
||
tools:
|
||
- skill_check_emit # LLM emits to request a bot-controlled dice embed.
|
||
- encounter_resolve # LLM emits to end the encounter with an outcome id.
|
||
- context_recall # LLM emits to pull prior NPC/party facts from graph memory.
|
||
- goal_register # LLM emits to add an off-rails goal mid-encounter.
|
||
- foundry_lookup # LLM emits to surface a linked player's live Foundry stats.
|
||
- foundry_reward # LLM emits to award XP/items via the Foundry relay.
|
||
# NOTE: skill_check_group_emit (Feature C) is intentionally absent. The
|
||
# clock-maker is a solo scene (minPlayers: 1); there is no group to check
|
||
# against. The timed-check opt-in on break_curse_note uses the solo emit.
|
||
|
||
# dmNotes — LLM READS. Author framing for the DM's intent (stakes, feel,
|
||
# escalation). Not rules the LLM mechanically follows.
|
||
dmNotes: >
|
||
Built for a party of three. The aim is to incite fear and uncertainty, not to
|
||
resolve cleanly. The customers do not know they are cursed; the clock-maker
|
||
does not care — he only wants the clocks to come back. Each clock curses its
|
||
owner and compels them to return it, for free, so the wares always find their
|
||
way home. Anyone without a clock, or proof against curses, feels none of it —
|
||
they only see people strangely happy in sad times, and that wrongness is the
|
||
horror. Let the party find the truth through the customers and the shop; do
|
||
not hand it to them. If the party lingers too long asking questions, the
|
||
returning customer enters — more distraught than the first — wanting to
|
||
return a lethal clock; use them to break the dithering and raise the stakes.
|
||
Their fifteen-minute deadline is urgency to narrate, not a real-time timer.
|
||
|
||
NEW CAPABILITIES (group-encounters feature set, 2026-06-20):
|
||
The opening beat of the clock-maker scene is a "something is wrong"
|
||
pre-roll moment; lean on the three passiveReveals above (Perception,
|
||
Insight, Investigation) to surface clues in the opening lines without
|
||
waiting for the LLM-emitted checks. Bot applies them automatically at
|
||
encounter start, group-visible, attributed to the qualifying player —
|
||
the LLM should weave their revealText into the scene as it unfolds,
|
||
not announce them as a list.
|
||
|
||
For the returning customer's deadline: the original framing is "urgency
|
||
to narrate, not a real-time timer," and that remains the default. The
|
||
LLM may, at its discretion, opt into the new timed-skill-check path
|
||
(durationSeconds: 900 on calm_returning_customer) if the table wants
|
||
the deadline to be engine-enforced rather than narrative-pace. The
|
||
spec does not require it; it gives the LLM permission.
|
||
|
||
# xpReward — BOT ENFORCES. Flat XP to all participants on resolution.
|
||
xpReward: 50 |