Commit Graph

76 Commits

Author SHA1 Message Date
ejlewis 26dd91947f feat: add kitchen and glitchtip wiring
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-10 12:03:12 -05:00
ejlewis 4d9077d586 feat(world): add light timer and indicator
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-10 10:17:42 -05:00
ejlewis d56c0c8363 Implement match interactions and wait chip
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-10 07:56:31 -05:00
ejlewis 83e4877852 feat(content): add upper floor slice 2026-05-10 07:34:51 -05:00
ejlewis daa5e9d655 fix(ui): polish terminal options and assets
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-10 07:00:22 -05:00
ejlewis 33933b00d7 fix(mystery): improve mobile terminal and chips
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-10 05:53:32 -05:00
ejlewis 29fd371b89 Merge branch 'feat/engine-prereqs'
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-09 21:51:37 -05:00
ejlewis 2a9b6155ef feat(mystery): add opening and main-floor content 2026-05-09 21:51:12 -05:00
ejlewis b3e708995b Merge pull request 'docs(mystery): Phase 2 content-rewrite roadmap' (#2) from feat/engine-prereqs into main
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #2
2026-05-09 15:16:07 -05:00
ejlewis e46b2359c0 docs(mystery): Phase 2 content-rewrite roadmap
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>
2026-05-09 15:11:06 -05:00
ejlewis f9b6fc164f Merge pull request 'docs(mystery): spec for engine prereqs (verbs, disambiguation, ending UI)' (#1) from feat/engine-prereqs into main
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #1
2026-05-09 15:10:20 -05:00
ejlewis 5f8e3b1a34 fix(ui): keep input typable post-end so player can type restart/undo
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>
2026-05-09 15:06:37 -05:00
ejlewis e167979fa7 feat(ui): render ending lines distinctly and lock input on end-state
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>
2026-05-09 14:57:52 -05:00
ejlewis 19d1efc586 feat(engine): detect endings on every successful turn
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>
2026-05-09 14:53:07 -05:00
ejlewis 0d9db9bb55 test(engine): self-contained locked-exit fixture replaces the stub
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>
2026-05-09 14:42:22 -05:00
ejlewis b870d884ef feat(engine): wire verb-target-prep — explicit \light X with Y\ and \use\ routing
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>
2026-05-09 14:22:03 -05:00
ejlewis 8401e7d281 feat(engine): light/extinguish verbs with implicit lighter selection
\`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>
2026-05-09 14:18:54 -05:00
ejlewis dac8487dbe feat(engine): read verb narrates item.readableText
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 14:15:35 -05:00
ejlewis 2fecc7878d feat(world): annotate lamp/matches/letter for read/light/extinguish
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>
2026-05-09 14:12:10 -05:00
ejlewis ee3cfcc00d feat(world): parseItem extracts optional ## read / lit / extinguished / lighter-empty sections
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>
2026-05-09 14:08:33 -05:00
ejlewis df50afa479 feat(world): item schema — readable, lightable, lighter, lighterUses
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>
2026-05-09 14:01:31 -05:00
ejlewis ab8c17fdd5 feat(engine): dispatcher handles ambiguous parses with a disambiguation prompt
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>
2026-05-09 13:58:32 -05:00
ejlewis b318747840 feat(parser): emit verb-target-prep on 'with'/'on'/'in'/'to' separators
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>
2026-05-09 13:56:19 -05:00
ejlewis 46f851bc3a feat(parser): return ambiguous variant when noun matches multiple aliases
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>
2026-05-09 13:53:38 -05:00
ejlewis b325f04b02 feat(parser): strip leading stop-words (at, the, a, an) from noun phrase
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>
2026-05-09 13:36:34 -05:00
ejlewis 14a58481b1 refactor(engine): theme is a UI preference; remove it from GameState
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>
2026-05-09 13:33:57 -05:00
ejlewis 657ed22b48 refactor(engine): drop redundant string[] casts now that RoomState includes arrays
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 13:30:35 -05:00
ejlewis 6cffb87a63 feat(engine): widen state value unions and add 'ending' transcript kind
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>
2026-05-09 13:28:14 -05:00
ejlewis e21a308e9d docs(mystery): implementation plan for engine prereqs
17 tasks covering type widening, theme removal, parser stop-words +
ambiguous + verb-target-prep variants, dispatcher handlers for
read/light/extinguish/use, ending detection, and UI ending screen.
Includes self-contained locked-exit fixture and manual playthrough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 13:21:13 -05:00
ejlewis bcff8a42f9 docs(mystery): spec for engine prereqs (verbs, disambiguation, ending UI)
Hard prereqs from halfstreet-followon-notes plus should-fix items.
Polish items deferred. Phase 2 (full bible content draft) follows after
this lands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 13:07:24 -05:00
ejlewis 2b3a18f208 ci: add Cloudflare Pages deploy + split success/failure notify
ci/woodpecker/push/woodpecker Pipeline was successful
Mirrors the ethanjlewis.com pipeline structure:

- single combined build step (npm ci + test + build)
- deploy via npx wrangler to project halfstreet-io
- notify-success at priority 0, notify-failure at priority 1
- triggers on push to main + manual

Secret names are lowercase (cloudflare_api_token, cloudflare_account_id,
pushover_token, pushover_user) to match what's configured on the
halfstreet repo in Woodpecker.
2026-05-09 11:56:38 -05:00
ejlewis 72f99295ca build: re-add wrangler as devDependency
Needed by the deploy step (npx wrangler pages deploy). Keeping it as a
local devDep means CI doesn't re-download wrangler on every run, matching
how ethanjlewis.com is set up.
2026-05-09 11:56:38 -05:00
ejlewis e044141043 ci: pushover notification on success too
ci/woodpecker/push/woodpecker Pipeline was successful
Single notify step now runs for both success and failure
(when.status: [success, failure]), branching on
CI_PIPELINE_STATUS for the title. Success uses Pushover
priority -1 (quiet) so passing-build pings don't alert at
night; failures stay at priority 0 (default).
2026-05-09 11:50:28 -05:00
ejlewis c0061491ab ci: pushover notification on pipeline failure
ci/woodpecker/push/woodpecker Pipeline was successful
Uses pushover_token and pushover_user secrets configured in Woodpecker.
Step runs only on failure (when.status: failure) so successful runs stay
quiet. Message includes branch, commit message, and a link back to the
pipeline.
2026-05-09 11:47:50 -05:00
ejlewis b80e4c32a5 fix(ci): commit package-lock.json so npm ci works
The Woodpecker pipeline runs `npm ci` which requires a lockfile;
without it the install step errors with EUSAGE.
2026-05-09 11:47:50 -05:00
ejlewis e31bf0fbff chore: standalone Halfstreet repo scaffolding
ci/woodpecker/manual/woodpecker Pipeline failed
- package.json: drop @astrojs/sitemap, fontsource fonts, wrangler,
  @cloudflare/workers-types, tsx, spotify scripts. Keep astro/yaml/zod
  + vitest/typescript/@astrojs/check/@types/node. Add GPL-3.0-or-later.
- astro.config.mjs: drop sitemap integration, sharp image config; set
  site to halfstreet.io.
- tsconfig.json: drop cloudflare-workers-types, drop functions/scripts
  from include.
- .gitignore: cloudflare-free; Obsidian workspace cache rules updated
  to post-rename src/world/.obsidian/ paths.
- README.md: stack, layout, design-doc index, license note.
- .woodpecker.yml: install + npm test + npm run build pipeline.
2026-05-09 11:36:19 -05:00
ejlewis 86e1aeb973 feat: rename mystery.astro -> index.astro, fix imports for src/ root
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.
2026-05-09 11:33:06 -05:00
ejlewis 78b749dac4 chore(halfstreet): bible Obsidian-formatted edits + vault config
Substantial bible expansion (frontmatter, Obsidian list/heading formatting,
expanded core themes section, target length 25–28 rooms).

Adds .obsidian/ vault config (app/appearance/core-plugins) and gitignore
rules for the per-machine workspace cache, mirroring the world vault setup.

Done before extracting Halfstreet to its own repo so this work travels with
the filter-repo extraction.
2026-05-09 11:29:18 -05:00
ejlewis bc21a88786 Merge feature: mystery markdown content migration
Move Halfstreet game content (rooms, items, encounter narration, endings)
from TypeScript object literals into markdown files editable in Obsidian.
Engine, UI, and the public World type are unchanged. Three-room prototype
verified end-to-end via tests and manual playthrough.

Includes code-review followups: locked-exit requires validation,
endings-completeness check, and clear errors for malformed section headers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 11:13:38 -05:00
ejlewis 1f472402fd fix(mystery): code-review followups (locked-exit, endings, headers)
- 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>
2026-05-09 11:12:26 -05:00
ejlewis 4b8ebafe6f fix(mystery): swap gray-matter for yaml package (browser-safe)
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>
2026-05-09 09:52:21 -05:00
ejlewis 20619cec09 chore(mystery): commit minimal Obsidian vault config; ignore workspace cache 2026-05-09 09:43:53 -05:00
ejlewis 506e36b801 refactor(mystery): remove story.ts; endings live in markdown 2026-05-09 09:36:28 -05:00
ejlewis 1b992642ec feat(mystery): encounters.ts uses narration() helper for prose 2026-05-09 09:35:41 -05:00
ejlewis c0c1a7e930 feat(mystery): assemble World from markdown via import.meta.glob
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>
2026-05-09 09:34:27 -05:00
ejlewis 0523158e61 test(mystery): round-trip verification of migrated markdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 09:27:39 -05:00
ejlewis bbea3f4473 feat(mystery): migration script and produced markdown content
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>
2026-05-09 09:26:02 -05:00
ejlewis d3a2f4e1d7 feat(mystery): narration() helper and encounter narration registry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 09:23:37 -05:00
ejlewis bf8a693949 feat(mystery): parseEncounterNarration — phase and transition prose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 09:21:29 -05:00
ejlewis e60844a937 feat(mystery): parseEnding — markdown to typed Ending
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 09:17:11 -05:00