From 26dd91947fcd40b5970325feb192ec0901fc5267 Mon Sep 17 00:00:00 2001 From: Ethan J Lewis Date: Sun, 10 May 2026 12:03:12 -0500 Subject: [PATCH] feat: add kitchen and glitchtip wiring --- .woodpecker.yml | 18 ++++- README.md | 4 ++ astro.config.mjs | 5 ++ package-lock.json | 76 +++++++++++++++++++++ package.json | 1 + src/assets/noun-8-bit-dragon-6513752.svg | 4 ++ src/assets/noun-8-bit-revolver-6513773.svg | 6 ++ src/assets/noun-8-bit-skull-6513765.svg | 8 +++ src/assets/noun-apple-6351196.png | Bin 0 -> 8636 bytes src/assets/noun-candle-6409709.svg | 17 +++++ src/assets/noun-coffe-6351204.svg | 13 ++++ src/assets/noun-document-6351208.svg | 7 ++ src/assets/noun-gun-7637672.svg | 4 ++ src/assets/noun-key-7637683.svg | 5 ++ src/assets/noun-knife-7637608.svg | 15 ++++ src/assets/noun-monster-7637602.svg | 6 ++ src/assets/noun-newspaper-6661160.svg | 15 ++++ src/assets/noun-people-6409658.svg | 5 ++ src/assets/noun-piano-6661161.svg | 4 ++ src/assets/noun-poison-6351215.svg | 16 +++++ src/assets/noun-potion-1096542.svg | 7 ++ src/assets/noun-stairway-7637615.svg | 26 +++++++ src/assets/noun-witch-7637595.svg | 8 +++ src/engine/dispatcher.test.ts | 74 +++++++++++++++++++- src/engine/dispatcher.ts | 13 ++-- src/ui/crt.css | 17 ++++- src/ui/glitchtip.ts | 15 ++++ src/ui/terminal.ts | 16 +++-- src/world/TODOs.md | 8 ++- src/world/encounters/breathing-wall.md | 4 +- src/world/encounters/covered-cage.md | 4 +- src/world/encounters/ivy-figure.md | 4 +- src/world/encounters/linen-shape.md | 6 +- src/world/encounters/piano-echo.md | 2 +- src/world/encounters/window-guest.md | 6 +- src/world/items/candlestick.md | 6 +- src/world/items/damp-sheet.md | 2 +- src/world/items/music-box-key.md | 2 +- src/world/items/pruning-shears.md | 2 +- src/world/items/silver-lighter.md | 2 +- src/world/items/whiskey.md | 9 +++ src/world/rooms/conservatory.md | 4 +- src/world/rooms/dining-room.md | 8 +-- src/world/rooms/kitchen.md | 25 +++++++ src/world/rooms/laundry.md | 4 +- src/world/rooms/music-room.md | 2 +- src/world/rooms/parlor.md | 10 +-- src/world/rooms/servants-passage.md | 4 +- src/world/rooms/smoking-room.md | 4 +- 49 files changed, 470 insertions(+), 53 deletions(-) create mode 100644 src/assets/noun-8-bit-dragon-6513752.svg create mode 100644 src/assets/noun-8-bit-revolver-6513773.svg create mode 100644 src/assets/noun-8-bit-skull-6513765.svg create mode 100644 src/assets/noun-apple-6351196.png create mode 100644 src/assets/noun-candle-6409709.svg create mode 100644 src/assets/noun-coffe-6351204.svg create mode 100644 src/assets/noun-document-6351208.svg create mode 100644 src/assets/noun-gun-7637672.svg create mode 100644 src/assets/noun-key-7637683.svg create mode 100644 src/assets/noun-knife-7637608.svg create mode 100644 src/assets/noun-monster-7637602.svg create mode 100644 src/assets/noun-newspaper-6661160.svg create mode 100644 src/assets/noun-people-6409658.svg create mode 100644 src/assets/noun-piano-6661161.svg create mode 100644 src/assets/noun-poison-6351215.svg create mode 100644 src/assets/noun-potion-1096542.svg create mode 100644 src/assets/noun-stairway-7637615.svg create mode 100644 src/assets/noun-witch-7637595.svg create mode 100644 src/ui/glitchtip.ts create mode 100644 src/world/items/whiskey.md create mode 100644 src/world/rooms/kitchen.md diff --git a/.woodpecker.yml b/.woodpecker.yml index c76cfdb..c21ba0f 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,7 +9,23 @@ steps: commands: - npm ci - npm run test - - npm run build + - PUBLIC_GLITCHTIP_RELEASE=$CI_PIPELINE_NUMBER npm run build + + - name: upload-sourcemaps + image: rust:1 + environment: + SENTRY_URL: + from_secret: glitchtip_url + SENTRY_AUTH_TOKEN: + from_secret: glitchtip_auth_token + SENTRY_ORG: + from_secret: glitchtip_org + SENTRY_PROJECT: + from_secret: glitchtip_project + commands: + - cargo install --locked --git https://gitlab.com/glitchtip/glitchtip-cli.git + - glitchtip-cli sourcemaps inject ./dist + - glitchtip-cli sourcemaps upload ./dist --release "$CI_PIPELINE_NUMBER" - name: deploy image: node:22 diff --git a/README.md b/README.md index c186ff7..39014f6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Played at [halfstreet.io](https://halfstreet.io). - TypeScript engine — pure (no DOM, `Date`, `Math.random`, or console) - World content authored in markdown (rooms, items, encounters, endings) under `src/world/` - [Vitest](https://vitest.dev) for tests +- Optional client error reporting via [GlitchTip](https://glitchtip.com) using `PUBLIC_GLITCHTIP_DSN` ## Development @@ -22,6 +23,9 @@ npm run dev # local dev server npm run build # type-check + production build ``` +To enable GlitchTip in the browser, set `PUBLIC_GLITCHTIP_DSN` in your environment before running or building the site. +If you want deploy-aware grouping and readable stack traces, also set `PUBLIC_GLITCHTIP_RELEASE` at build time and upload the generated source maps for that same release. + ## Layout - `src/engine/` — parser, dispatcher, encounter logic diff --git a/astro.config.mjs b/astro.config.mjs index 697ad6d..72231f0 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -6,4 +6,9 @@ export default defineConfig({ build: { inlineStylesheets: 'auto', }, + vite: { + build: { + sourcemap: true, + }, + }, }) diff --git a/package-lock.json b/package-lock.json index 43e263b..4c0ac4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "GPL-3.0-or-later", "dependencies": { + "@sentry/browser": "^10.52.0", "astro": "^6.1.9", "yaml": "^2.8.4", "zod": "^4.4.3" @@ -1766,6 +1767,81 @@ "win32" ] }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.52.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.52.0.tgz", + "integrity": "sha512-x/yEPZdpH6NGQeoeQnV9tj8reAH8twNttiltGZl2o8Rk7sQeUfe7E8yuYP2XbJ2RqyZK5qRS3COrNyMPzf6KFA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.52.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.52.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.52.0.tgz", + "integrity": "sha512-5kAn1W8ZvCuHtEHXpq6iRkUMdNCilwww+YxaN2yofVrCivAbB3Ha5JJUMqmWOPW0pC27zGYmoJMIDvG+PczUxA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.52.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.52.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.52.0.tgz", + "integrity": "sha512-diywyuc/H7VTUR+W5ryVmLF+0X4UP1OskMqb6V8RSAvJHcj2JmIm7uP+Fc6ACTno+b6AUShwT/L4xVXzO6X9Cw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.52.0", + "@sentry/core": "10.52.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.52.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.52.0.tgz", + "integrity": "sha512-BI5ie4dxPuUJ344CXVSnAxY1xZCbghglPSCIlTOYODpR9so9yo5IZh+Mwspt0oWsUMaxWJiQSNYlbPWi7WDavg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.52.0", + "@sentry/core": "10.52.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.52.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.52.0.tgz", + "integrity": "sha512-ijL9jN86oXwXQWbwhPlEb70ODJSEmjxQEQdnZkC4gDWbjswcwvRsVJPYk+1xl2ir2iZixRIHipVxDcLwian35g==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.52.0", + "@sentry-internal/feedback": "10.52.0", + "@sentry-internal/replay": "10.52.0", + "@sentry-internal/replay-canvas": "10.52.0", + "@sentry/core": "10.52.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/core": { + "version": "10.52.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.52.0.tgz", + "integrity": "sha512-VA/kAqLhkMnRWY2RXdBLyTemR9D4m7MVRy/gyapoq9yvllVPx9WXbvKgnMP2LQp7mFgT/oLFvw58aQKaYTGn3A==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@shikijs/core": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", diff --git a/package.json b/package.json index cdc75a3..6e39a3f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test:watch": "vitest" }, "dependencies": { + "@sentry/browser": "^10.52.0", "astro": "^6.1.9", "yaml": "^2.8.4", "zod": "^4.4.3" diff --git a/src/assets/noun-8-bit-dragon-6513752.svg b/src/assets/noun-8-bit-dragon-6513752.svg new file mode 100644 index 0000000..cad6c99 --- /dev/null +++ b/src/assets/noun-8-bit-dragon-6513752.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/noun-8-bit-revolver-6513773.svg b/src/assets/noun-8-bit-revolver-6513773.svg new file mode 100644 index 0000000..4a7baa7 --- /dev/null +++ b/src/assets/noun-8-bit-revolver-6513773.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/noun-8-bit-skull-6513765.svg b/src/assets/noun-8-bit-skull-6513765.svg new file mode 100644 index 0000000..3324f5c --- /dev/null +++ b/src/assets/noun-8-bit-skull-6513765.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/noun-apple-6351196.png b/src/assets/noun-apple-6351196.png new file mode 100644 index 0000000000000000000000000000000000000000..fa48d194a34d74b4500f6636a9aa282230a5d4b2 GIT binary patch literal 8636 zcmeHNZA@EL7(N&3+I0@7nh`#7!8CCnYT`U7$XZKDw?d4a*)Y>=d~A`6#z70XQF6O+ zVT`fFSrV3vJ330{3?XF9jIQlgHZD3xlZ>d#aoqP4( z?&|~~8x*UQC3@2@u95LykEM+T{hwuQKht(dPTtk%Es9;KRlZ9BNr5L9%|e4&k|1gWQ1mPx;sgT6EDm(9 zHqo@F#^}wt17U+ztrM?z)1GQ0wJEejNA=`gEOvqfx~C7g5&@MlW2XCTXihTWn+r&U zx&~sU8eMwwO*k@zW4bLG5yn#}4xb{9Nx;3F3qwvsi{E0a=NJ6JNxHFt6!ZoE3}B$D z?uHxy?~8v-vWAaImHQOks3!%0xv*$^2H{2JV4L_5PY_uA0gO2;+X)q@aHRUq(b0u) zd!oLQz^8FZ#2mnc!BsZ)AtzM1Rld(Tq53flyswmX(Kef!Io(JQD_T^g>K>OMRITRO z;URg#a($r)5vkxJ^%NiC34-b0!$71zN}RkUp9eitI7nUI3PXvNvE>jgfkr7qIyBe@MWXtE84>Fat>5m;IW<7W%ia2LhHk7u1w(dS4)WRtkjC8F`?xK` ztT*4QJ;J+^cy=X~$d$|DGLlNh>DMc1UE0FzcuhF~~mJ2}5&X(bP}8 zko>=T39FV9yR27tfXmzL<#`2ym6oKJ)Fp*!tvLmI(n^%eq7QC@1Kx+i_Kq#t!JbH0 zzqf5b`tp^H@1dIx_`pN(?^#f46&5f7*3E5q9CY-$JONPqqcW(lsWTZPeUsZ4=eiQJ zz4_3?rMfxIGTooBf73kTJpcx+u-#<^x(O_lFaSfp8)U=413^dmzT?Qu!xXYaFL zb6FQ_m_b>YlRZWXerOm%*5*?`E91@@%B(`&j=eC%wSpZ1%=F#3eIw9Umhs02adPzZ zw`EdIwGU7)TEq?)% CV??I_ literal 0 HcmV?d00001 diff --git a/src/assets/noun-candle-6409709.svg b/src/assets/noun-candle-6409709.svg new file mode 100644 index 0000000..52d2ca7 --- /dev/null +++ b/src/assets/noun-candle-6409709.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/noun-coffe-6351204.svg b/src/assets/noun-coffe-6351204.svg new file mode 100644 index 0000000..06953a7 --- /dev/null +++ b/src/assets/noun-coffe-6351204.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/assets/noun-document-6351208.svg b/src/assets/noun-document-6351208.svg new file mode 100644 index 0000000..76b73e9 --- /dev/null +++ b/src/assets/noun-document-6351208.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/noun-gun-7637672.svg b/src/assets/noun-gun-7637672.svg new file mode 100644 index 0000000..d34b0e1 --- /dev/null +++ b/src/assets/noun-gun-7637672.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/noun-key-7637683.svg b/src/assets/noun-key-7637683.svg new file mode 100644 index 0000000..95ae427 --- /dev/null +++ b/src/assets/noun-key-7637683.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/noun-knife-7637608.svg b/src/assets/noun-knife-7637608.svg new file mode 100644 index 0000000..a94b5a6 --- /dev/null +++ b/src/assets/noun-knife-7637608.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/noun-monster-7637602.svg b/src/assets/noun-monster-7637602.svg new file mode 100644 index 0000000..d40beaf --- /dev/null +++ b/src/assets/noun-monster-7637602.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/noun-newspaper-6661160.svg b/src/assets/noun-newspaper-6661160.svg new file mode 100644 index 0000000..e6e3fda --- /dev/null +++ b/src/assets/noun-newspaper-6661160.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/noun-people-6409658.svg b/src/assets/noun-people-6409658.svg new file mode 100644 index 0000000..edea147 --- /dev/null +++ b/src/assets/noun-people-6409658.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/noun-piano-6661161.svg b/src/assets/noun-piano-6661161.svg new file mode 100644 index 0000000..435f7c2 --- /dev/null +++ b/src/assets/noun-piano-6661161.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/noun-poison-6351215.svg b/src/assets/noun-poison-6351215.svg new file mode 100644 index 0000000..2121c60 --- /dev/null +++ b/src/assets/noun-poison-6351215.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/noun-potion-1096542.svg b/src/assets/noun-potion-1096542.svg new file mode 100644 index 0000000..b3fd743 --- /dev/null +++ b/src/assets/noun-potion-1096542.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/noun-stairway-7637615.svg b/src/assets/noun-stairway-7637615.svg new file mode 100644 index 0000000..9d7b7c8 --- /dev/null +++ b/src/assets/noun-stairway-7637615.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/noun-witch-7637595.svg b/src/assets/noun-witch-7637595.svg new file mode 100644 index 0000000..4e18e10 --- /dev/null +++ b/src/assets/noun-witch-7637595.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/engine/dispatcher.test.ts b/src/engine/dispatcher.test.ts index 1fb2361..ef5d706 100644 --- a/src/engine/dispatcher.test.ts +++ b/src/engine/dispatcher.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { dispatch, initialStateFor } from './dispatcher' +import { dispatch, getLightStatus, initialStateFor } from './dispatcher' import type { World } from '../world/types' import type { GameState, ParsedCommand } from './types' import { SCHEMA_VERSION } from './types' @@ -256,6 +256,78 @@ describe('dispatcher — inventory', () => { }) }) +describe('light status', () => { + it('shows the meter when carrying a lightable item even before it is lit', () => { + const lightWorld: World = { + ...world, + items: { + ...world.items, + torch: { + id: 'torch', + names: ['torch', 'lamp'], + short: 'an oil lamp', + long: 'An iron oil lamp, unlit.', + initialState: { lit: false }, + takeable: true, + lightable: true, + }, + }, + } + const state: GameState = { + ...initialStateFor(lightWorld), + inventory: [{ id: 'torch', state: { lit: false } }], + } + + expect(getLightStatus(state, lightWorld)).toEqual({ + itemId: 'torch', + lit: false, + turnsLeft: 0, + maxTurns: 6, + }) + }) + + it('prefers a lit lightable item over an unlit one in inventory order', () => { + const lightWorld: World = { + ...world, + items: { + ...world.items, + torch: { + id: 'torch', + names: ['torch', 'lamp'], + short: 'an oil lamp', + long: 'An iron oil lamp, unlit.', + initialState: { lit: false }, + takeable: true, + lightable: true, + }, + candlestick: { + id: 'candlestick', + names: ['candlestick', 'candle'], + short: 'a brass candlestick', + long: 'A brass candlestick.', + initialState: { lit: false }, + takeable: true, + lightable: true, + }, + }, + } + const state: GameState = { + ...initialStateFor(lightWorld), + inventory: [ + { id: 'candlestick', state: { lit: false } }, + { id: 'torch', state: { lit: true, burn: 6 } }, + ], + } + + expect(getLightStatus(state, lightWorld)).toEqual({ + itemId: 'torch', + lit: true, + turnsLeft: 6, + maxTurns: 6, + }) + }) +}) + describe('ambiguous → disambiguation flow', () => { function makeAmbiguousWorld(): World { return { diff --git a/src/engine/dispatcher.ts b/src/engine/dispatcher.ts index e46b366..b88cf3a 100644 --- a/src/engine/dispatcher.ts +++ b/src/engine/dispatcher.ts @@ -52,19 +52,22 @@ export function initialStateFor(world: World): GameState { } export function getLightStatus(state: GameState, world: World): LightStatus | null { + let fallback: LightStatus | null = null for (const inst of state.inventory) { const def = world.items[inst.id] if (!def?.lightable) continue - if (inst.state['lit'] !== true) continue - const turnsLeft = getLightTurnsLeft(inst) - return { + const lit = inst.state['lit'] === true + const turnsLeft = lit ? getLightTurnsLeft(inst) : 0 + const status = { itemId: inst.id, - lit: true, + lit, turnsLeft, maxTurns: LIGHT_TURNS_MAX, } + if (lit) return status + fallback = fallback ?? status } - return null + return fallback } function append(state: GameState, lines: TranscriptLine[]): GameState { diff --git a/src/ui/crt.css b/src/ui/crt.css index 484ae38..9301698 100644 --- a/src/ui/crt.css +++ b/src/ui/crt.css @@ -405,8 +405,15 @@ body { width: 30px; height: 30px; display: block; - object-fit: contain; - filter: drop-shadow(0 0 2px currentColor); + color: var(--m-dim); + opacity: 0.85; +} + +.mystery-light-icon svg { + width: 100%; + height: 100%; + display: block; + fill: currentColor; } .mystery-light-leds { @@ -431,6 +438,12 @@ body { box-shadow: 0 0 7px currentColor; } +.mystery-light-meter[data-lit='true'] .mystery-light-icon { + color: var(--m-fg); + opacity: 1; + filter: drop-shadow(0 0 2px currentColor) drop-shadow(0 0 6px currentColor); +} + .mystery-chip { background: transparent; color: var(--m-fg); diff --git a/src/ui/glitchtip.ts b/src/ui/glitchtip.ts new file mode 100644 index 0000000..70ae510 --- /dev/null +++ b/src/ui/glitchtip.ts @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser' + +const dsn = import.meta.env.PUBLIC_GLITCHTIP_DSN as string | undefined +const release = import.meta.env.PUBLIC_GLITCHTIP_RELEASE as string | undefined + +export function initGlitchTip(): void { + if (!dsn) return + + Sentry.init({ + dsn, + release, + environment: import.meta.env.MODE, + tracesSampleRate: 0.01, + }) +} diff --git a/src/ui/terminal.ts b/src/ui/terminal.ts index 7ba0808..14495e2 100644 --- a/src/ui/terminal.ts +++ b/src/ui/terminal.ts @@ -7,12 +7,15 @@ import type { GameState, TranscriptLine } from '../engine/types' import { TRANSCRIPT_CAP } from '../engine/types' import { computeChips } from './chips' import { renderChips } from './chip-render' +import LIGHT_ICON_SVG from '../assets/noun-candle-6409709.svg?raw' +import { initGlitchTip } from './glitchtip' const transcriptEl = document.querySelector('[data-mystery-transcript]') const inputEl = document.querySelector('[data-mystery-input]') const inputDisplayEl = document.querySelector('[data-mystery-input-display]') const lightMeterEl = document.querySelector('[data-mystery-light-meter]') -const LIGHT_ICON_URL = new URL('../assets/noun-oil-lamp-8301660.svg', import.meta.url).href + +initGlitchTip() const HELP_TEXT = `You arrive at the address, but you do not remember what has happened. The road behind you is gone... @@ -60,11 +63,10 @@ if (!transcriptEl || !inputEl || !inputDisplayEl) { lightMeterEl.dataset['lit'] = 'true' lightMeterEl.dataset['turnsLeft'] = String(status.turnsLeft) - const icon = document.createElement('img') + const icon = document.createElement('div') icon.className = 'mystery-light-icon' - icon.src = LIGHT_ICON_URL - icon.alt = '' icon.setAttribute('aria-hidden', 'true') + icon.innerHTML = LIGHT_ICON_SVG lightMeterEl.appendChild(icon) const leds = document.createElement('div') @@ -73,7 +75,11 @@ if (!transcriptEl || !inputEl || !inputDisplayEl) { for (let i = 0; i < LIGHT_TURNS_MAX; i++) { const segment = document.createElement('span') segment.className = 'mystery-light-segment' - segment.dataset['segmentState'] = i < turnsLeft ? 'lit' : 'dim' + const lit = i < turnsLeft + segment.dataset['segmentState'] = lit ? 'lit' : 'dim' + segment.style.backgroundColor = lit ? 'var(--m-fg)' : 'var(--m-dim)' + segment.style.boxShadow = lit ? '0 0 7px var(--m-fg)' : '0 0 0 1px rgba(0, 0, 0, 0.15) inset' + segment.style.opacity = lit ? '1' : '0.45' leds.appendChild(segment) } lightMeterEl.appendChild(leds) diff --git a/src/world/TODOs.md b/src/world/TODOs.md index d3962b9..31b7c6e 100644 --- a/src/world/TODOs.md +++ b/src/world/TODOs.md @@ -25,7 +25,6 @@ - [x] If the user says "use match with letter" they should burn the letter. - [x] There should be a lighter in the smoking room that allows unlimited lighting. - [ ] Create a mechanic that asks "Are you sure?" before taking critical actions like attacking or other game-changing mechanics that might affect the final ending. -- [ ] Create a new item with a mechanic: whiskey bottle. When the user drinks it they get drunk and are transported to the "drunk rooms" which are a bit of a maze and things get a little topsy-turvy. The player chances to lose an item (returning to its original spot) when they get drunk and wakes up several turns later somewhere else predetermined. - [ ] Add lightened descriptions to darkened rooms. About half the rooms should be too dark to see anything (affects ability to move forward, can't see exits or entounters, except for maybe hints at the encounters, like sounds or shapes in the dark) Add frontmatter property to all rooms: (dark: true/false). Make text in darkened rooms a grey color. - [ ] Implement a simple "stealth mechanic", where sometimes it's advantageous to have the light out. - [ ] Implement a simple (optional?) minimap in the UI? - Maybe tied to an item? Once you get the map the minimap appears? Can we POC it? @@ -37,3 +36,10 @@ - [ ] Implement a carry mechanic. Decide whether we should have a limited carry ability (only able to carry a few things?) or we night need a full inventory system, where items are assigned to pockets or hand carry and we can only hand carry a couple of items? - [ ] Implement a "drop" mechanic - [x] We need a light indicator that shows when the light is lit and how much time is left on the light. Use the svg file I dropped in the src/assets folder for the indicator. The indicator should be a 6-segment led that runs in a dotted line underneath the light indicator and burns out right to left. The color of the indicator should be bright when it's lit and dim when it's not. The indicator should be to the right of the tiles and sized appropriately. +- [ ] FEATURE: Add an option to disable the chips in the options menu. +- [ ] BUG: The new cursor doesn't appear on mobile. +- [ ] FEATURE: Add a Safe to somewhere that it makes sense (the bedroom?) We can add a safe-cracking mini-game. The safe contains a single bullet, which can be used with the revolver. +- [ ] FEATURE: Add item Revolver, which can be used to kill the thing at the end. It comes with no bullets, so players need to defeat the safe-cracking minjgame. Place the revolver somewhere it takes the player some effort to find. +- [ ] Feature: A faceless voice that speaks in a whisper in the chapel, which demands whiskey and dispenses riddles if the player finds a bottle of whiskey (but hasn't drunk it yet). The riddles are randomly chosen from a list of 25 difficult riddles (source them). If the riddle is answered correctly there needs to be some reward and maybe a major plot point is revealed. +- [ ] Add item Whiskey bottle, half full of something smoky. In the kitchen. Drinking it gets the player drunk, which causes them to unlock the drunk rooms, which are a series of rooms where they can go many directions, but somehow end up in seemingly back in the same spot. They are essentially a maze of doors and hallways, ladders and levels. There should be boundaries established though, it shouldn't be endless. After 20 or so moves the player passes out and wakes up back in the foyer, with the whiskey bottle returned to the kitchen, somehow still half full. Add a random encounter in the dark rooms, a creaking floorboard helps you find a secret door that opens with a faceless man inside who gives you major plot info. After this encounter you pass out and wake up in the lobby as before. +- [ ] diff --git a/src/world/encounters/breathing-wall.md b/src/world/encounters/breathing-wall.md index f8ab451..2bbbe28 100644 --- a/src/world/encounters/breathing-wall.md +++ b/src/world/encounters/breathing-wall.md @@ -10,10 +10,10 @@ The wall beside your shoulder breathes in. The opposite wall answers. ## wait-resolved You stand still. -The passage narrows, then forgets to. The breathing passes on ahead of you. +The passage narrows, then forgets to. The breathing passes on ahead of you, down the wall and out of reach. ## wrong-verb The walls take a slower breath. ## failed -The boards lean close enough to touch your sleeves. You retreat into the music room. +The boards lean close enough to touch your sleeves. You retreat into the music room before they can close around you. diff --git a/src/world/encounters/covered-cage.md b/src/world/encounters/covered-cage.md index e137ed5..244319b 100644 --- a/src/world/encounters/covered-cage.md +++ b/src/world/encounters/covered-cage.md @@ -15,7 +15,7 @@ The cage is empty. A few pale feathers cling to the wire, though no bird could h Somewhere far above you, wings beat once inside a wall. ## wrong-verb -The cloth trembles. +The cloth trembles, then goes still again. ## failed -The rustling grows too close to your ear. You leave the room before you decide to. +The rustling grows too close to your ear. You leave the room before you decide to, and the cage is still covered when you look back. diff --git a/src/world/encounters/ivy-figure.md b/src/world/encounters/ivy-figure.md index 5e79273..0b7269c 100644 --- a/src/world/encounters/ivy-figure.md +++ b/src/world/encounters/ivy-figure.md @@ -13,7 +13,7 @@ The shears close with a sound like teeth. The figure falls apart leaf by leaf. Behind it, the glass is only glass. ## wrong-verb -The vines tighten without moving. +The vines tighten without moving. Their silence feels deliberate. ## failed -The ivy catches at your wrists. When you pull free, you are back among the cold plates of the dining room. +The ivy catches at your wrists. When you pull free, you are back among the cold plates of the dining room, with leaves clinging to your sleeves. diff --git a/src/world/encounters/linen-shape.md b/src/world/encounters/linen-shape.md index 014d30e..a5f92d4 100644 --- a/src/world/encounters/linen-shape.md +++ b/src/world/encounters/linen-shape.md @@ -5,7 +5,7 @@ initialPhase: hanging --- ## hanging -One hanging sheet has the weight and outline of a person standing behind it. +One hanging sheet has the weight and outline of a person standing behind it. The shape shifts when you look straight at it. ## wait-resolved You wait. @@ -13,7 +13,7 @@ You wait. The sheet stirs. Nothing stands behind it. Nothing had stood behind it. ## wrong-verb -The shape seems to lean toward you. +The shape seems to lean toward you, then settles back into stillness. ## failed -You push through the hanging sheets and come out in the servants' passage, breathing hard. +You push through the hanging sheets and come out in the servants' passage, breathing hard, with damp cloth brushing your face. diff --git a/src/world/encounters/piano-echo.md b/src/world/encounters/piano-echo.md index b56f5e1..042fd96 100644 --- a/src/world/encounters/piano-echo.md +++ b/src/world/encounters/piano-echo.md @@ -13,7 +13,7 @@ You play the waiting note. The answer comes at once, nearer than before. A narrow part of the wall settles back into shadow. ## wrong-verb -The answering note repeats, patient and exact. +The answering note repeats, patient and exact. It sounds as if it has not moved at all. ## failed The wrong chord goes through the floorboards. You climb back to the hallway before the echo finishes. diff --git a/src/world/encounters/window-guest.md b/src/world/encounters/window-guest.md index 3cc31c1..78f4869 100644 --- a/src/world/encounters/window-guest.md +++ b/src/world/encounters/window-guest.md @@ -5,7 +5,7 @@ initialPhase: standing-outside --- ## standing-outside -Rain touches the dining-room window from the wrong side. Someone stands beyond the glass with their head bowed. +Rain touches the dining-room window from the wrong side. Someone stands beyond the glass with their head bowed. Their coat is too dark to make out properly. ## close-window-guest-resolved You draw the curtains together before you look closely. @@ -13,7 +13,7 @@ You draw the curtains together before you look closely. For a moment, cloth and rain hold the same shape. Then there is only the table behind you. ## wrong-verb -The figure outside lifts its face a little. +The figure outside lifts its face a little, as if disappointed by your hesitation. ## failed -The glass shows too much. You find yourself back in the hallway with the taste of rain in your mouth. +The glass shows too much. For one instant there is a second dining room beyond it. Then you are back in the hallway with rain in your mouth. diff --git a/src/world/items/candlestick.md b/src/world/items/candlestick.md index 4279050..e8d4d00 100644 --- a/src/world/items/candlestick.md +++ b/src/world/items/candlestick.md @@ -8,10 +8,10 @@ initialState: lit: false --- -A brass candlestick, heavy at the base. The candle inside it is burned low but not spent. +A brass candlestick, heavy at the base. The candle inside it is burned low but not spent. It holds a little light very steadily, as if it dislikes being noticed. ## lit -The wick takes. The flame bends toward the nearest doorway. +The wick takes. The flame stands upright for a moment, then bends toward the nearest doorway. ## extinguished -The candle gutters out. For a moment the smoke leans against your hand. +The candle gutters out. For a moment the smoke leans against your hand before it thins. diff --git a/src/world/items/damp-sheet.md b/src/world/items/damp-sheet.md index a960376..73b3c17 100644 --- a/src/world/items/damp-sheet.md +++ b/src/world/items/damp-sheet.md @@ -6,4 +6,4 @@ takeable: true initialState: {} --- -A sheet still wet from some old washing. It smells of rainwater and closed rooms. +A sheet still wet from some old washing. It smells of rainwater and closed rooms. It keeps the cold longer than cloth should. diff --git a/src/world/items/music-box-key.md b/src/world/items/music-box-key.md index cdaf0df..4db443e 100644 --- a/src/world/items/music-box-key.md +++ b/src/world/items/music-box-key.md @@ -6,4 +6,4 @@ takeable: true initialState: {} --- -A tiny key on a black ribbon. It is too small for any door in the house. +A tiny key on a black ribbon. It is too small for any door in the house, and yet the ribbon has been tied and retied as if someone meant to keep it safe. diff --git a/src/world/items/pruning-shears.md b/src/world/items/pruning-shears.md index 9660e87..36a23c7 100644 --- a/src/world/items/pruning-shears.md +++ b/src/world/items/pruning-shears.md @@ -6,4 +6,4 @@ takeable: true initialState: {} --- -Iron pruning shears with dark soil in the hinge. The handles are cold through your palm. +Iron pruning shears with dark soil in the hinge. The handles are cold through your palm. They have been used often enough to remember the shape of branches. diff --git a/src/world/items/silver-lighter.md b/src/world/items/silver-lighter.md index aeea42a..548760b 100644 --- a/src/world/items/silver-lighter.md +++ b/src/world/items/silver-lighter.md @@ -7,4 +7,4 @@ lighter: true initialState: {} --- -A silver lighter with a worn crest on one side. It opens with a small reluctant click. +A silver lighter with a worn crest on one side. It opens with a small reluctant click, then catches as if it had been waiting to be asked. diff --git a/src/world/items/whiskey.md b/src/world/items/whiskey.md new file mode 100644 index 0000000..0d45717 --- /dev/null +++ b/src/world/items/whiskey.md @@ -0,0 +1,9 @@ +--- +id: whiskey +names: ["whiskey", "bottle of whiskey", "bottle"] +short: "a bottle of whiskey" +takeable: true +initialState: {} +--- + +A bottle of whiskey with a cloudy label and a dark amber level inside. It has the look of something set down in a hurry and never touched again. diff --git a/src/world/rooms/conservatory.md b/src/world/rooms/conservatory.md index 52d43fb..b87b024 100644 --- a/src/world/rooms/conservatory.md +++ b/src/world/rooms/conservatory.md @@ -13,7 +13,7 @@ encounter: "[[ivy-figure]]" --- ## first-visit -The conservatory roof has gone blind with moss and old rain. Vines press against the glass from both sides. +The conservatory roof has gone blind with moss and old rain. Vines press against the glass from both sides, as if the room were trying to keep something in. Something human-shaped hangs among the ivy. The dining room is south. @@ -21,4 +21,4 @@ Something human-shaped hangs among the ivy. The dining room is south. The conservatory sweats in the dark. ## examined -The tiled floor is split by roots. The air smells of wet soil and cold iron. A bench has been dragged beneath the blind glass roof. +The tiled floor is split by roots. The air smells of wet soil and cold iron. A bench has been dragged beneath the blind glass roof and left at an angle, as if someone stood up too quickly. diff --git a/src/world/rooms/dining-room.md b/src/world/rooms/dining-room.md index b072707..66ff6ca 100644 --- a/src/world/rooms/dining-room.md +++ b/src/world/rooms/dining-room.md @@ -3,7 +3,7 @@ id: dining-room title: "[ Dining Room ]" exitN: "[[conservatory]]" exitS: null -exitE: null +exitE: "[[kitchen]]" exitW: "[[hallway]]" exitU: null exitD: null @@ -14,12 +14,12 @@ encounter: "[[window-guest]]" --- ## first-visit -The dining room is laid for supper long after supper ended. Plates dull the long table. One place has not been touched. +The dining room is laid for supper long after supper ended. Plates dull the long table. One place at the far end has not been touched. -The hallway is west. A greenish light presses through glass to the north. +The hallway is west. The kitchen is east. A green light presses through the glass to the north. ## revisit The dining room holds its place at the table. ## examined -The silver has tarnished black at the edges. The untouched plate is clean enough to show a dim shape behind you, though there is no one there. +The silver has gone black at the edges. The untouched plate is clean enough to show a shape behind you, though there is no one there when you turn. diff --git a/src/world/rooms/kitchen.md b/src/world/rooms/kitchen.md new file mode 100644 index 0000000..ea85148 --- /dev/null +++ b/src/world/rooms/kitchen.md @@ -0,0 +1,25 @@ +--- +id: kitchen +title: "[ Kitchen ]" +exitN: null +exitS: null +exitE: null +exitW: "[[dining-room]]" +exitU: null +exitD: null +items: + - "[[whiskey]]" +encounter: null +safe: true +--- + +## first-visit +Something recently warm. The room smells of fat, onions, and wood smoke that has not yet cleared. + +The dining room is west. + +## revisit +The kitchen keeps its heat a little too long. + +## examined +Copper pots hang over the stove. A corkscrew lies by the sink. The bottle on the counter catches what little light there is and holds it. diff --git a/src/world/rooms/laundry.md b/src/world/rooms/laundry.md index ef0a40e..a14478a 100644 --- a/src/world/rooms/laundry.md +++ b/src/world/rooms/laundry.md @@ -14,7 +14,7 @@ safe: true --- ## first-visit -Sheets hang unmoving in the still air. They divide the room into narrow aisles. One of them has the shape of a person behind it. +Sheets hang unmoving in the still air. They divide the room into narrow aisles. One of them has the shape of a person behind it, or the suggestion of one. The servants' passage is west. @@ -22,4 +22,4 @@ The servants' passage is west. The laundry hangs white and silent. ## examined -Water darkens the floor beneath the hanging sheets, though none of them drip. An empty basket waits below the line. +Water darkens the floor beneath the hanging sheets, though none of them drip. An empty basket waits below the line, tilted as if something climbed out of it. diff --git a/src/world/rooms/music-room.md b/src/world/rooms/music-room.md index 782bada..75de3d3 100644 --- a/src/world/rooms/music-room.md +++ b/src/world/rooms/music-room.md @@ -22,4 +22,4 @@ Something answers from elsewhere in the house, too soft to be music. A servants' The music room waits on the same note. ## examined -The depressed piano key does not rise when touched. The music stand is empty, but its clips remain open. +The depressed piano key does not rise when touched. The music stand is empty, but its clips remain open, as if a page had just been lifted away. diff --git a/src/world/rooms/parlor.md b/src/world/rooms/parlor.md index 83c9006..468623a 100644 --- a/src/world/rooms/parlor.md +++ b/src/world/rooms/parlor.md @@ -14,14 +14,14 @@ safe: true --- ## first-visit -The parlor has been arranged for company. No chair faces another. Each one waits at a slight angle, as if the guests stood suddenly and left the room in silence. +The parlor has been set for company that never arrives. The chairs are drawn a little too far apart, as if the people meant to speak and then forgot what they had meant to say. -A stopped clock stands against the north wall. A narrow study opens beyond it. The hallway is below. +A stopped clock stands against the north wall. The study lies beyond it. The hallway is below. -A staircase climbs where the corner of the room should be. +A staircase rises where the corner should be. ## revisit -The parlor waits with its empty chairs. +The parlor keeps its arrangement. ## examined -Dust gathers on the chair arms, but not on the seats. Someone has sat here recently, or the room remembers being occupied. The grandfather clock shows no hour you can name. +Dust gathers on the chair arms, not on the seats. Something in the room has been touched often. The grandfather clock shows no hour you can name. diff --git a/src/world/rooms/servants-passage.md b/src/world/rooms/servants-passage.md index 9e5bd67..f32b3d5 100644 --- a/src/world/rooms/servants-passage.md +++ b/src/world/rooms/servants-passage.md @@ -12,7 +12,7 @@ encounter: "[[breathing-wall]]" --- ## first-visit -The walls here are unfinished and smell of wet wood. The passage is too narrow for two people to pass. +The walls here are unfinished and smell of wet wood. The passage is too narrow for two people to pass, though it feels made for someone else to follow. The music room is south. A laundry lies east. @@ -20,4 +20,4 @@ The music room is south. A laundry lies east. The servants' passage breathes less loudly now, or you have learned when not to listen. ## examined -The boards show old finger marks in the grain. They run at shoulder height along both walls, as if someone felt their way through in the dark. +The boards show old finger marks in the grain. They run at shoulder height along both walls, as if someone felt their way through in the dark and never touched the floor. diff --git a/src/world/rooms/smoking-room.md b/src/world/rooms/smoking-room.md index 0fb9699..02d47ff 100644 --- a/src/world/rooms/smoking-room.md +++ b/src/world/rooms/smoking-room.md @@ -14,7 +14,7 @@ encounter: "[[covered-cage]]" --- ## first-visit -The smoking room smells faintly of ash, velvet, and bitter almonds. The chairs are turned toward the cold hearth. +The smoking room smells faintly of ash, velvet, and bitter almonds. The chairs are turned toward the cold hearth, though no one has sat here in years. A cloth-covered cage hangs from a stand. The hallway is east. @@ -22,4 +22,4 @@ A cloth-covered cage hangs from a stand. The hallway is east. The smoking room keeps its breath. ## examined -Cigar ash lies in a clean saucer. The cold hearth reflects the chairs in a narrow black curve. +Cigar ash lies in a clean saucer. The cold hearth reflects the chairs in a narrow black curve. Nothing in the room seems to have been allowed to burn all the way down.