feat(mystery): define engine and world type contracts
This commit is contained in:
@@ -0,0 +1,84 @@
|
|||||||
|
// Engine type definitions. No runtime code — these shapes are the contract
|
||||||
|
// between the world data, the engine, and the UI.
|
||||||
|
|
||||||
|
export type RoomId = string
|
||||||
|
export type ItemId = string
|
||||||
|
export type EncounterId = string
|
||||||
|
export type Direction = 'n' | 's' | 'e' | 'w' | 'u' | 'd'
|
||||||
|
|
||||||
|
export type Verb =
|
||||||
|
| 'go' | 'look' | 'examine' | 'take' | 'drop' | 'use' | 'open' | 'close'
|
||||||
|
| 'read' | 'light' | 'extinguish' | 'attack' | 'inventory' | 'wait'
|
||||||
|
| 'hold' | 'push' | 'pull'
|
||||||
|
|
||||||
|
export type MetaVerb = 'restart' | 'undo' | 'hint' | 'save' | 'quit' | 'theme'
|
||||||
|
|
||||||
|
export interface NounRef {
|
||||||
|
/** Canonical noun (matches an ItemId, EncounterId, or a directional/world noun). */
|
||||||
|
canonical: string
|
||||||
|
/** The raw token(s) the player typed, for narration. */
|
||||||
|
raw: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ParsedCommand =
|
||||||
|
| { kind: 'verb-only'; verb: Verb | 'look' | 'inventory' | 'wait' }
|
||||||
|
| { kind: 'verb-target'; verb: Verb; target: NounRef }
|
||||||
|
| { kind: 'verb-target-prep'; verb: Verb; target: NounRef; preposition: string; indirect: NounRef }
|
||||||
|
| { kind: 'go'; direction: Direction }
|
||||||
|
| { kind: 'meta'; verb: MetaVerb }
|
||||||
|
| { kind: 'disambiguation'; chosen: string }
|
||||||
|
| { 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
|
||||||
|
/** Per-instance state: lit/unlit, broken/whole, etc. */
|
||||||
|
state: Record<string, string | boolean | number>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EncounterPhase = string // phase names are encounter-specific
|
||||||
|
|
||||||
|
export interface TranscriptLine {
|
||||||
|
kind: 'narration' | 'player' | 'system'
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingDisambiguation {
|
||||||
|
verb: Verb
|
||||||
|
candidates: string[] // canonical noun ids the player must choose between
|
||||||
|
prompt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameState {
|
||||||
|
schemaVersion: number
|
||||||
|
location: RoomId
|
||||||
|
inventory: ItemInstance[]
|
||||||
|
/** Per-room state: visited, items dropped, descriptive flags. */
|
||||||
|
roomState: Record<RoomId, Record<string, string | boolean | number>>
|
||||||
|
/** Story-wide flags (e.g. 'gateOpened', 'mirrorTarnished'). */
|
||||||
|
flags: Record<string, string | boolean | number>
|
||||||
|
resolveLevel: ResolveLevel
|
||||||
|
/** Active encounter phase by encounter id, or null if no encounter is mid-flight. */
|
||||||
|
encounterState: Record<EncounterId, EncounterPhase>
|
||||||
|
/** Last referenced noun, for pronoun resolution. */
|
||||||
|
lastNoun: NounRef | null
|
||||||
|
/** Pending multi-word disambiguation, set when the parser cannot decide. */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchResult {
|
||||||
|
state: GameState
|
||||||
|
/** Lines to append to the transcript (already added to state.transcript). */
|
||||||
|
appended: TranscriptLine[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SCHEMA_VERSION = 1
|
||||||
|
export const TRANSCRIPT_CAP = 200
|
||||||
|
export const RESOLVE_LEVELS: ResolveLevel[] = ['steady', 'shaken', 'reeling', 'returning']
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// World data type definitions. World modules export plain data conforming to
|
||||||
|
// these shapes; the engine reads but never mutates them.
|
||||||
|
|
||||||
|
import type { RoomId, ItemId, EncounterId, Direction, Verb, EncounterPhase } from '../engine/types'
|
||||||
|
|
||||||
|
export interface RoomDescriptions {
|
||||||
|
/** Shown the first time the player enters this room. */
|
||||||
|
firstVisit: string
|
||||||
|
/** Shown on subsequent entries. */
|
||||||
|
revisit: string
|
||||||
|
/** Shown when the player types `look` (richer detail). */
|
||||||
|
examined: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Room {
|
||||||
|
id: RoomId
|
||||||
|
title: string // e.g. "[ Foyer ]"
|
||||||
|
descriptions: RoomDescriptions
|
||||||
|
/** Direction → destination room id. Locked exits are listed in `lockedExits`. */
|
||||||
|
exits: Partial<Record<Direction, RoomId>>
|
||||||
|
/** Direction → unlock condition (item id or flag name). */
|
||||||
|
lockedExits?: Partial<Record<Direction, { requires: ItemId | string; lockedNarration: string }>>
|
||||||
|
/** Items that start in this room. Items the player drops are tracked in roomState. */
|
||||||
|
items: ItemId[]
|
||||||
|
/** Encounter that triggers when this room is entered, or null. */
|
||||||
|
encounter?: EncounterId
|
||||||
|
/** Optional "safe" flag: entering a safe room regenerates one resolve step. */
|
||||||
|
safe?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
id: ItemId
|
||||||
|
/** Canonical name and any aliases for the parser. */
|
||||||
|
names: string[]
|
||||||
|
/** Short description shown in inventory. */
|
||||||
|
short: string
|
||||||
|
/** Long description shown when examined. */
|
||||||
|
long: string
|
||||||
|
/** Initial per-instance state (e.g. `{ lit: false }`). */
|
||||||
|
initialState: Record<string, string | boolean | number>
|
||||||
|
/** True if the player can pick it up. */
|
||||||
|
takeable: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EncounterPhaseDef {
|
||||||
|
/** Description shown each turn while in this phase. */
|
||||||
|
description: string
|
||||||
|
transitions: EncounterTransition[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EncounterTransition {
|
||||||
|
verb: Verb
|
||||||
|
/** Required target noun id, or '*' for any target, or undefined for verb-only. */
|
||||||
|
target?: ItemId | EncounterId | '*'
|
||||||
|
/** Required item id in inventory (and optional state predicate). */
|
||||||
|
requires?: { item: ItemId; state?: Record<string, string | boolean | number> }
|
||||||
|
/** Phase to transition to, or 'resolved' / 'failed'. */
|
||||||
|
to: EncounterPhase | 'resolved' | 'failed'
|
||||||
|
/** Narration on this transition. */
|
||||||
|
narration: string
|
||||||
|
/** Resolve cost for the player on this transition (0–2). */
|
||||||
|
resolveCost?: 0 | 1 | 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EncounterDef {
|
||||||
|
id: EncounterId
|
||||||
|
startsIn: RoomId
|
||||||
|
initialPhase: EncounterPhase
|
||||||
|
phases: Record<EncounterPhase, EncounterPhaseDef>
|
||||||
|
/** Effects on resolution (set flags, unlock exits). */
|
||||||
|
onResolved?: { setFlags?: Record<string, string | boolean | number>; unlockExits?: { room: RoomId; direction: Direction }[] }
|
||||||
|
/** What happens at 'failed' (e.g. retreat to previous safe room). */
|
||||||
|
onFailed?: { narration: string; retreatTo: RoomId }
|
||||||
|
/** Default narration for wrong-verb attempts not matching any transition. */
|
||||||
|
defaultWrongVerbNarration?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface World {
|
||||||
|
startingRoom: RoomId
|
||||||
|
startingInventory: ItemId[]
|
||||||
|
rooms: Record<RoomId, Room>
|
||||||
|
items: Record<ItemId, Item>
|
||||||
|
encounters: Record<EncounterId, EncounterDef>
|
||||||
|
/** Story flag definitions and the endings they unlock. */
|
||||||
|
endings: {
|
||||||
|
true: { whenFlags: Record<string, string | boolean | number>; narration: string }
|
||||||
|
wrong: { whenFlags: Record<string, string | boolean | number>; narration: string }
|
||||||
|
bad: { whenFlags: Record<string, string | boolean | number>; narration: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user