Workflow guide for authoring the bible into markdown now that engine
prereqs have shipped. Suggests slicing by region rather than by kind so
each PR produces a playable surface.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Disabling the input via the disabled attribute blocks keydown events
entirely, so players couldn't type 'restart' or 'undo' after reaching an
ending. Switch to a CSS class for the faded visual state; the keydown
handler already restricts post-end input to those two commands.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ending-kind lines get a separator and italic styling. Once endedWith is
set, the terminal disables the input and rejects all commands except
restart and undo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After each state-mutating dispatch, evaluate world.endings in priority
order (true > wrong > bad). The first whose whenFlags are all satisfied
sets state.endedWith and appends a kind:'ending' transcript line. Once
ended, further dispatches return a "story has ended" narration.
Also update test-world fixtures and placeholder ending markdown files
to use whenFlags: { _never: true } instead of {} so that vacuously-true
empty flags don't accidentally fire on every successful turn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Verifies blocked movement, key-permitted passage, and that the key is
not consumed by passing through.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
light X with Y validates the named instrument and reuses handleLight.
use X / use X on Y route through the encounter dispatcher; if no encounter
consumes it, the dispatcher narrates the fallback. The encounter matcher
also rejects transitions whose required item doesn't match the typed
instrument, so a mistyped instrument fails cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
\`light X\` finds a lighter (item with lighter:true and remaining state.uses)
in inventory, decrements its charges, and toggles target.state.lit. The
target's litText / extinguishedText / the lighter's lighterEmptyText
provide narration. Refuses politely on each error path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the new schema flags and per-state body sections so the dispatcher's
new verb handlers have content to narrate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Existing items with no body sections continue to load unchanged. New items
can author per-state prose in dedicated sections; the dispatcher will read
these in subsequent commits.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Optional fields used by the new read/light/extinguish dispatcher branches.
Loader updates and dispatcher logic follow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sets pendingDisambiguation on state and emits "Which X — A, or B?" using
each candidate item's short text. The existing disambiguation reply path
then re-issues the original verb against the chosen target.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Enables `light lamp with matches`, `use shears on vines`, and similar
multi-noun forms. Both the target and indirect noun must resolve;
otherwise the command falls back to unknown-noun.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the previous behavior of returning unknown-noun. The dispatcher
will use this in the next commit to prompt the player to disambiguate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allows `look at lamp`, `examine the letter`, `take a key`, `take an oil lamp`.
Stop-words are only removed from the head of the noun phrase, not from
anywhere in the middle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously, clicking the theme button updated localStorage and the DOM but
not state.theme, so the engine's `theme` meta-verb toggled from stale state.
Theme is now exclusively UI/storage concern. Old saves with the field still
load; the field is silently ignored.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drops redundant string[] casts in dispatcher paths and prepares the
TranscriptLine kind for the ending-screen render path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Halfstreet now lives at halfstreet.io as the entire site, so the game
serves at / instead of /mystery. After git-filter-repo lifted
src/mystery/ to src/, the page's css/ts imports need to drop the
mystery/ segment.
- Validate lockedExits[*].requires resolves to a known item or flag
- Throw if any of true/wrong/bad ending markdown files are missing
- Detect malformed ## headers (spaces, dots, etc.) and throw a clear
error rather than silently dropping the section
Tests: 86 passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gray-matter eagerly loads Node's Buffer API path even when only
matter(rawString) is called, crashing browser bundles. Replace it with
an inline frontmatter parser backed by the browser-safe yaml package.
All 84 mystery tests pass; build is clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rooms, items, and endings now come from .md files under world/{rooms,items,endings}/.
Encounters still come from encounters.ts (Tasks 11–12 will complete that leg).
Cross-reference validation at module init ensures exits, item refs, and encounter
refs are all consistent. Deletes rooms.ts, items.ts, roundtrip.test.ts, and the
one-shot migration script (whose output is already committed).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds scripts/migrate-mystery-content.ts which reads rooms, items,
encounters, and endings from TypeScript source and emits byte-identical
markdown files under src/mystery/world/{rooms,items,encounters,endings}/.
Installs tsx as a devDep to support .ts imports across src/ during the
one-shot run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
appendLines() only updated the DOM, never state.transcript, so any
UI-originated line (player input, restart/undo/quit messages) vanished
on page reload. Engine narration was unaffected because dispatch()
already adds its lines to state.transcript.
Fix: appendLines() now pushes into state.transcript (capped at
TRANSCRIPT_CAP) and renders. Engine output uses renderAll() directly
since dispatch already added its lines to state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pure computeChips function (TDD, 4 tests) generates context-aware direction/item/encounter/meta chips from game state; chip-render.ts wires chips to DOM; terminal.ts calls refreshChips on init, each Enter dispatch, restart, and undo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>