The clockwork minds the pit, the curse, and the shadow (GDCN)

Kestrel's archmage berserker dug a pit at the far mouth of a warp,
then tried to walk through that mouth seven times: each stride landed
on its own pit with a thornbush beyond, the maze bounced it back and
charged the stride, and on the seventh it rolled the 1 and fell in.
Cursed with SLOW DEATH at seven life, it kept drawing two cards a
turn at a point apiece, raised a SHADOW that drinks a point a turn,
and bled to death in three turns.

The path search now refuses a pit it cannot leap beyond — the same
test the maze applies — whether the pit is a waypoint or the goal.
Under SLOW DEATH the end-of-turn draw keeps four life in hand and
draws nothing when it cannot; while bleeding, or at six life or less,
no SHADOW is raised. Three tests pin them.

The chronicle tells the bounce as what it was: "leaps the pit —
nothing to land on beyond it — and teeters back", not a blind blunder
into a wall; and the shadow's last drink no longer trails a zero-damage
line. Brain and chronicle changes only, ungated; 83 ledgers replay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-04 16:11:42 -04:00
co-authored by Claude Fable 5.1
parent b92016db51
commit 0750fe3f21
4 changed files with 125 additions and 10 deletions
+6 -1
View File
@@ -51,6 +51,9 @@ export function humanize(e: GameEvent): string | null {
if (e.fullyStopped) return `The attack is completely stopped.`;
return null; // the damaged event tells the story
case "damaged": {
// The shadow's last drink is told by its own line; the bookkeeping
// blow that follows it has nothing to add.
if (e.amount === 0 && e.source === "shadow upkeep") return null;
const soak = e.soaks?.map((s) => `${s.what} soaks ${s.amount}`).join(", ");
return `${e.player} takes ${e.amount} damage (${e.source}${soak ? `${soak}` : ""}) — ${e.lifeAfter} life left.`;
}
@@ -126,7 +129,9 @@ export function humanize(e: GameEvent): string | null {
case "cardDisplayed": return `${e.player} displays ${cardDef(e.card.cardId).name}.`;
case "lifeTraded": return `${e.player} burns ${e.points} life for speed!`;
case "castAroundCorner": return `The spell bends around the corner!`;
case "moveBumped": return `${e.player} blunders into a wall!`;
case "moveBumped": return e.why === "pit"
? `${e.player} leaps the pit — nothing to land on beyond it — and teeters back.`
: `${e.player} blunders into a wall!`;
case "attackMisdirected": return e.newTarget
? `${e.attacker}'s blind attack veers off — and hits ${e.newTarget}!`
: `${e.attacker}'s blind attack flies off into the darkness.`;