docs(mystery): spec for engine prereqs (verbs, disambiguation, ending UI) #1

Merged
ejlewis merged 19 commits from feat/engine-prereqs into main 2026-05-09 15:10:20 -05:00
2 changed files with 31 additions and 0 deletions
Showing only changes of commit e167979fa7 - Show all commits
+14
View File
@@ -169,3 +169,17 @@
@media (pointer: coarse) { @media (pointer: coarse) {
.mystery-chips { display: flex; } .mystery-chips { display: flex; }
} }
.mystery-transcript .ending {
margin-top: 2em;
margin-bottom: 1em;
padding-top: 1em;
border-top: 1px solid currentColor;
font-style: italic;
white-space: pre-wrap;
}
[data-mystery-input]:disabled {
opacity: 0.4;
cursor: not-allowed;
}
+17
View File
@@ -33,6 +33,10 @@ if (!transcriptEl || !inputEl) {
}) })
} }
const syncEndedUI = (): void => {
inputEl!.disabled = state.endedWith !== null
}
const buildParserContext = (s: GameState): ParserContext => { const buildParserContext = (s: GameState): ParserContext => {
const room = world.rooms[s.location] const room = world.rooms[s.location]
const visibleNouns: { id: string; aliases: string[] }[] = [] const visibleNouns: { id: string; aliases: string[] }[] = []
@@ -81,6 +85,7 @@ if (!transcriptEl || !inputEl) {
renderAll(state.transcript) renderAll(state.transcript)
refreshChips() refreshChips()
syncEndedUI()
inputEl.focus() inputEl.focus()
inputEl.addEventListener('keydown', (e) => { inputEl.addEventListener('keydown', (e) => {
@@ -91,6 +96,15 @@ if (!transcriptEl || !inputEl) {
if (!raw.trim()) return if (!raw.trim()) return
appendLines([{ kind: 'player', text: raw }]) appendLines([{ kind: 'player', text: raw }])
// Once the game has ended, only restart and undo are allowed.
if (state.endedWith !== null) {
const lower = raw.trim().toLowerCase()
if (lower !== 'restart' && lower !== 'undo') {
appendLines([{ kind: 'system', text: 'The story has ended. Type `restart` or `undo`.' }])
return
}
}
// Engine-level meta-commands handled here so the engine stays pure. // Engine-level meta-commands handled here so the engine stays pure.
const trimmed = raw.trim().toLowerCase() const trimmed = raw.trim().toLowerCase()
if (trimmed === 'restart') { if (trimmed === 'restart') {
@@ -105,6 +119,7 @@ if (!transcriptEl || !inputEl) {
renderAll(state.transcript) renderAll(state.transcript)
saveState(state) saveState(state)
refreshChips() refreshChips()
syncEndedUI()
return return
} }
if (trimmed === 'undo') { if (trimmed === 'undo') {
@@ -114,6 +129,7 @@ if (!transcriptEl || !inputEl) {
appendLines([{ kind: 'system', text: '(undone)' }]) appendLines([{ kind: 'system', text: '(undone)' }])
saveState(state) saveState(state)
refreshChips() refreshChips()
syncEndedUI()
} else { } else {
appendLines([{ kind: 'system', text: 'There is no further back.' }]) appendLines([{ kind: 'system', text: 'There is no further back.' }])
} }
@@ -139,6 +155,7 @@ if (!transcriptEl || !inputEl) {
document.dispatchEvent(new CustomEvent('halfstreet-toggle-theme')) document.dispatchEvent(new CustomEvent('halfstreet-toggle-theme'))
} }
refreshChips() refreshChips()
syncEndedUI()
} catch (err) { } catch (err) {
console.error('[halfstreet] dispatch error', err) console.error('[halfstreet] dispatch error', err)
appendLines([{ kind: 'system', text: '[ The terminal hums and resets. ]' }]) appendLines([{ kind: 'system', text: '[ The terminal hums and resets. ]' }])