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
4 changed files with 7 additions and 8 deletions
Showing only changes of commit 14a58481b1 - Show all commits
+4 -5
View File
@@ -29,7 +29,6 @@ export function initialStateFor(world: World): GameState {
lastNoun: null,
pendingDisambiguation: null,
transcript: opening,
theme: 'amber',
endedWith: null,
}
}
@@ -115,10 +114,10 @@ function narrate(state: GameState, lines: TranscriptLine[]): DispatchResult {
function handleMeta(state: GameState, verb: 'restart' | 'undo' | 'hint' | 'save' | 'quit' | 'theme'): DispatchResult {
if (verb === 'save') return narrate(state, [{ kind: 'system', text: '(your progress is saved automatically)' }])
if (verb === 'theme') {
const newTheme = state.theme === 'amber' ? 'ansi' : 'amber'
return narrate({ ...state, theme: newTheme }, [{ kind: 'system', text: `Theme: ${newTheme}.` }])
}
// 'theme' is a UI preference: the terminal intercepts it before dispatch and
// dispatches a 'halfstreet-toggle-theme' DOM event. The engine no-ops here so
// typing the verb still produces transcript output if the UI ever misses it.
if (verb === 'theme') return narrate(state, [{ kind: 'system', text: '(theme)' }])
// restart / undo / hint / quit are handled by the UI layer (state mutations
// require coordination with the save layer and route navigation). The
// engine acknowledges them with a no-op narration; the UI intercepts before
-1
View File
@@ -14,7 +14,6 @@ const baseState = (overrides: Partial<GameState> = {}): GameState => ({
lastNoun: null,
pendingDisambiguation: null,
transcript: [],
theme: 'amber',
endedWith: null,
...overrides,
})
+3
View File
@@ -48,6 +48,9 @@ export function loadState(): GameState | null {
return null
}
// Older saves may carry fields no longer in GameState (e.g. `theme` before
// it became a UI-only preference). TypeScript ignores extra fields at runtime;
// bump SCHEMA_VERSION only when the meaning of an existing field changes.
return parsed as GameState
}
-2
View File
@@ -30,7 +30,6 @@ export type ParsedCommand =
| { kind: 'unknown'; raw: string; reason: 'unknown-verb' | 'unknown-noun' | 'malformed' }
export type ResolveLevel = 'steady' | 'shaken' | 'reeling' | 'returning'
export type Theme = 'amber' | 'ansi'
export interface ItemInstance {
id: ItemId
@@ -68,7 +67,6 @@ export interface GameState {
pendingDisambiguation: PendingDisambiguation | null
/** Capped at 200 entries; older entries are dropped on append. */
transcript: TranscriptLine[]
theme: Theme
/** Set true when the player has reached an ending. UI shows ending screen. */
endedWith: 'true' | 'wrong' | 'bad' | null
}