Files
zalbot/specs/the-clock-maker.yaml
Kaysser Kayyali 3f426de6a4 content: spec/vocabulary edits — minPlayers docs, vocabulary locations, clock-maker passive reveals
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>
2026-06-22 22:13:02 +00:00

255 lines
12 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 15 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