/** * Game Engine - Main runtime for Sharpee IF games * * Manages game state, turn execution, and coordinates all subsystems */ import { WorldModel, IFEntity } from '@sharpee/world-model'; import { EventProcessor } from '@sharpee/event-processor'; import { Parser, IPerceptionService } from '@sharpee/stdlib'; import { LanguageProvider, ClientCapabilities, CmgtPacket, TurnPacket } from '@sharpee/if-domain'; import { ITextService } from './prose-pipeline'; import { ITextBlock } from '@sharpee/text-blocks'; import { ISemanticEvent, ISaveRestoreHooks, ISemanticEventSource } from '@sharpee/core'; import { PluginRegistry } from '@sharpee/plugins'; import { GameContext, TurnResult, EngineConfig, InputModeHandler, EngineIntrospection } from './types'; import { Story } from './story'; import { NarrativeSettings } from './narrative'; import { ParsedCommandTransformer, BeforeActionHookListener } from './command-executor'; /** * Game engine events */ export interface GameEngineEvents { 'turn:start': (turn: number, input: string) => void; 'turn:complete': (result: TurnResult) => void; 'turn:failed': (error: Error, turn: number) => void; 'event': (event: ISemanticEvent) => void; 'state:changed': (context: GameContext) => void; 'game:over': (context: GameContext) => void; 'text:output': (blocks: ITextBlock[], turn: number) => void; /** * CMGT manifest emission (ADR-163 §11). Fires once per session * during `start()` after `Story.registerChannels?` has run and the * `ChannelService` is constructed. Carries the capability-filtered * channel definitions for this client. */ 'channel:manifest': (cmgt: CmgtPacket) => void; /** * Per-turn channel packet emission (ADR-163 §1, §5). Fires after * `text-service.processTurn` produces the turn's blocks; carries * payload entries for every standard, story, and media channel that * had something to emit this turn. */ 'channel:packet': (packet: TurnPacket, turn: number) => void; } type GameEngineEventName = keyof GameEngineEvents; type GameEngineEventListener = GameEngineEvents[K]; /** * Conservative client-capability profile used when `start()` is called * without an explicit `capabilities` option. Mirrors a CLI / text-only * surface — every media flag is `false`, so capability-gated channels * (`image:*`, `sound`, `music`, `animation`, etc.) are filtered out of * the manifest. Single-user CLI bundles and existing test harnesses * use this profile by default; graphical surfaces pass their own * capabilities through. */ export declare const DEFAULT_TEXT_CAPABILITIES: ClientCapabilities; /** * Main game engine */ export declare class GameEngine { private world; private sessionStartTime?; private sessionTurns; private sessionMoves; private context; private config; private commandExecutor; private eventProcessor; private platformEvents; private actionRegistry; private textService?; private turnEvents; private running; private story?; private languageProvider?; private parser?; private eventListeners; private saveRestoreHooks?; private eventSource; private systemEventSource; private pendingPlatformOps; private perceptionService?; private pluginRegistry; /** * Per-turn sound buffer (ADR-172 Phase 6). Cleared at the start of every * `executeTurn()`; populated as actions call `context.emitSound`; * dispatched once after the plugin tick by `soundDispatcher.dispatch`. * Engine-internal — never serialized into save/restore snapshots * because sounds do not survive turn boundaries. */ private soundBuffer; /** * Per-turn sound dispatcher (ADR-172 Phase 6). Stateless — owns no * per-turn data; the buffer is passed in. Held as a field to leave * room for future extension seams (e.g., custom propagate injection * via `setSoundDispatcher` in tests). */ private soundDispatcher; private random; private narrativeSettings; private inputModeHandlers; private vocabularyManager; private saveRestoreService; private turnEventProcessor; private platformOpHandler?; private hasEmittedInitialized; /** * Channel-I/O service (ADR-163 §13, §14). Constructed in `start()` * once `Story.registerChannels?` has populated the registry and the * client capabilities are known. Optional — engines started without * a `capabilities` argument default to a text-only profile. */ private channelService?; /** * Negotiated client capabilities for this session. Populated by * `start({ capabilities })`; defaults to text-only when omitted. */ private clientCapabilities?; constructor(options: { world: WorldModel; player: IFEntity; parser: Parser; language: LanguageProvider; perceptionService?: IPerceptionService; config?: EngineConfig; }); /** * Set the story for this engine */ setStory(story: Story): void; /** * Get the current parser */ getParser(): Parser | undefined; /** * Get the current language provider */ getLanguageProvider(): LanguageProvider | undefined; /** * Returns a serializable snapshot of the engine's internal state for * tooling (VS Code extension, CLI --world-json). The engine owns the * serialization — callers consume the plain data shape. * * @returns EngineIntrospection with actions, patterns, and metadata */ introspect(): EngineIntrospection; /** * Start the game engine. * * @param options.capabilities — client capabilities for the channel-I/O * subsystem (ADR-163 §2). When provided, `start()` invokes * `Story.registerChannels?` to let the story extend or override * channels, constructs a `ChannelService`, and emits * `channel:manifest` plus a `channel:packet` per turn. When * omitted, the engine uses `DEFAULT_TEXT_CAPABILITIES` so * single-bundle and legacy callers receive packets without an * explicit declaration. */ start(options?: { capabilities?: ClientCapabilities; }): void; /** * Refresh the `storyInfo` capability from the current * `StoryInfoTrait`. Called once during `start()` (before the * `ChannelService` is constructed) so `infoChannel` / `ifidChannel` * project the trait's late-stage values (`engineVersion`, * `clientVersion`, `buildDate`) that consumers may have patched * after `setStory()`. * * No-op when no `StoryInfoTrait` is found (legacy stories that * don't use the trait still get the `StoryConfig`-only values from * the initial `setStory()` registration). */ private refreshStoryInfoCapability; /** * Build and emit a `channel:packet` for the turn just processed. * Co-fires with `text:output` at every block-emission site so * channel consumers and legacy text-service consumers see the same * turn boundary. No-op when the engine has no channel service yet * (`start()` has not run). */ private emitChannelPacket; /** * Stop the game engine */ stop(reason?: 'quit' | 'victory' | 'defeat' | 'abort', details?: any): void; /** * Restart the game from scratch. * * Clears the world, resets engine state, and re-initializes the story. * Called from both processMetaPlatformOperation and processPlatformOperations. */ private restartGame; /** * Execute a turn */ executeTurn(input: string): Promise; /** * Execute a meta-command (VERSION, SCORE, HELP, etc.) * * Meta-commands operate outside the turn cycle: * - They don't increment turns * - They don't trigger NPC ticks or scheduler * - They don't create undo snapshots * - They don't get stored in command history * - Events are processed immediately through text service (not stored in turnEvents) * * @param input - Raw command string * @param parsedCommand - Parsed command from parser * @returns MetaCommandResult with events and success status */ private executeMetaCommand; /** * Process meta-command events: text service → emit to clients * * - Does NOT store in turnEvents * - Passes currentTurn for display context (turn/score shown to player) * - Turn counter is NOT incremented */ private processMetaEvents; /** * Process a single platform operation for meta-commands. * * This is similar to processPlatformOperations but handles one operation * at a time and returns completion events for inclusion in the result. */ private processMetaPlatformOperation; /** * Get current game context */ getContext(): GameContext; /** * Switch the player character to a different entity (ADR-132). * * Synchronizes all three player identity layers: * 1. ActorTrait.isPlayer on old/new entities * 2. WorldModel.playerId * 3. GameContext.player * * Also resets parser context, vocabulary, and narrative settings. * * Must be called between turns only. Appropriate call sites: * - An interceptor's postExecute() phase * - A daemon/fuse callback * - A story-specific action's execute() phase * * Story code must position the new PC (via world.moveEntity) BEFORE * calling switchPlayer, since parser context uses the entity's current location. */ switchPlayer(entityId: string): void; /** * Get world model */ getWorld(): WorldModel; /** * Get the current story */ getStory(): Story | undefined; /** * Get the event source for save/restore */ getEventSource(): ISemanticEventSource; /** * Get narrative settings (ADR-089) * * Returns the story's narrative perspective and related settings. * Use this for text rendering that needs to know 1st/2nd/3rd person. */ getNarrativeSettings(): NarrativeSettings; /** * Configure language provider with narrative settings (ADR-089) * * Sets up the language provider for perspective-aware message resolution. * For 3rd person narratives, extracts player pronouns from ActorTrait. */ private configureLanguageProviderNarrative; /** * Synchronize all derived player state after a player identity change (ADR-132). * * Updates GameContext.player, parser world context, pronoun context, * scope vocabulary, and narrative settings. WorldModel.playerId and * ActorTrait.isPlayer must already be set before calling this. */ private syncPlayerState; /** * Get plugin registry for registering turn-cycle plugins (ADR-120) */ getPluginRegistry(): PluginRegistry; /** * Get event processor for handler registration (ADR-075) */ getEventProcessor(): EventProcessor; /** * Register an alternate input mode handler (ADR-137). * * Stories call this at init time. The handler is invoked when the * world state key `if.inputMode` matches the registered ID. * * @param id Mode identifier (e.g., 'dungeo.mode.gdt') * @param handler The input mode handler */ registerInputMode(id: string, handler: InputModeHandler): void; /** * Execute input through an alternate input mode handler (ADR-137). * * Bypasses the standard parser pipeline. Events go through the text * service for rendering. Turn counter advances only if the handler says so. */ private executeInputMode; /** * Append a PROMPT block to the output (ADR-137). * * Reads the current prompt from world state, resolves through the * language provider, and appends as the last block. */ private appendPromptBlock; /** * Get the text service */ getTextService(): ITextService | undefined; /** * Set a custom text service */ setTextService(service: ITextService): void; /** * Register save/restore hooks */ registerSaveRestoreHooks(hooks: ISaveRestoreHooks): void; /** * Get currently registered save/restore hooks */ getSaveRestoreHooks(): ISaveRestoreHooks | undefined; /** * Register a transformer for parsed commands. * Transformers are called after parsing but before validation, * allowing stories to modify commands (e.g., for debug tools). * * @param transformer - Function to transform parsed commands */ registerParsedCommandTransformer(transformer: ParsedCommandTransformer): void; /** * Unregister a parsed command transformer. * * @param transformer - The transformer to remove * @returns true if the transformer was found and removed */ unregisterParsedCommandTransformer(transformer: ParsedCommandTransformer): boolean; /** * Register a pre-action hook listener (ADR-148). * * Listeners fire after command context creation but before the action's * validate phase. They can modify world state (e.g., break concealment * before a noisy action executes). * * @param listener - The hook listener */ onBeforeAction(listener: BeforeActionHookListener): void; /** * Save game state using registered hooks */ save(): Promise; /** * Restore game state using registered hooks */ restore(): Promise; /** * Create an undo snapshot of the current world state */ private createUndoSnapshot; /** * Undo to previous turn * @returns true if undo succeeded, false if nothing to undo */ undo(): boolean; /** * Check if undo is available */ canUndo(): boolean; /** * Get number of undo levels available */ getUndoLevels(): number; /** * Process events from a plugin through the shared pipeline (ADR-120) * Enriches, filters, stores, and emits events. */ private processPluginEvents; /** * Create save data from current engine state */ private createSaveData; /** * Load save data into engine */ private loadSaveData; /** * Get turn history */ getHistory(): TurnResult[]; /** * Get recent events */ getRecentEvents(count?: number): ISemanticEvent[]; /** * Update vocabulary for an entity */ updateEntityVocabulary(entity: IFEntity, inScope: boolean): void; /** * Update vocabulary for all entities in scope */ updateScopeVocabulary(): void; /** * Emit a platform event with turn metadata */ emitPlatformEvent(event: Omit): void; /** * Update context after a turn */ private updateContext; /** * Update command history capability */ private updateCommandHistory; /** * Process pending platform operations */ private processPlatformOperations; /** * Emit a game lifecycle event. * All game events now use ISemanticEvent with data in the `data` field. * (IGameEvent with `payload` is deprecated - see ADR-097) */ private emitGameEvent; /** * Emit an event to listeners */ private emit; /** * Check if game is over */ private isGameOver; /** * Add event listener */ on(event: K, listener: GameEngineEventListener): this; /** * Remove event listener */ off(event: K, listener: GameEngineEventListener): this; } export {}; //# sourceMappingURL=game-engine.d.ts.map