feat(state): story trigger orchestration and auto-open rules

This commit is contained in:
ginnoir
2026-06-11 18:55:08 -05:00
parent a69585e89c
commit a230a46850
2 changed files with 64 additions and 0 deletions
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest';
import { content } from '../../content';
import { createGameState } from '../../engine/game';
import { initStory } from '../../engine/story';
import { getPrefs } from '../prefs';
import { processStoryTriggers } from '../storyOrchestration';
describe('processStoryTriggers()', () => {
it('returns entered nodes on boot', () => {
const state = createGameState(content);
initStory(state, content);
const result = processStoryTriggers(state, content, { reason: 'boot' }, getPrefs());
expect(result.enteredNodeIds.length).toBeGreaterThan(0);
});
});
+49
View File
@@ -0,0 +1,49 @@
import type { GameContent } from '../content/index';
import type { GameState } from '../engine/game';
import { evaluateTriggers, type StoryEvent, type TriggerContext } from '../engine/story';
import type { GamePrefs } from './prefs';
import type { StoryLogEntry } from './store';
export interface StoryUiEffect {
enteredNodeIds: string[];
logEntries: StoryLogEntry[];
shouldOpenPanel: boolean;
eventLogLines: string[];
}
export function storyEventsToLogEntries(events: StoryEvent[]): StoryLogEntry[] {
return events
.filter((e) => e.kind === 'enter')
.map((e) => ({ nodeId: e.nodeId, prose: e.prose, choiceLabel: e.choiceLabel }));
}
export function shouldAutoOpenPanel(
prefs: GamePrefs,
nodeId: string,
content: GameContent,
): boolean {
const node = content.storyNodesById[nodeId];
if (!node) return false;
if (prefs.storyOpenMode === 'manual') return false;
if (prefs.storyOpenMode === 'auto') return true;
return (node.choices?.length ?? 0) > 0;
}
export function processStoryTriggers(
state: GameState,
content: GameContent,
ctx: TriggerContext,
prefs: GamePrefs,
): StoryUiEffect {
const { enteredNodeIds, events } = evaluateTriggers(state, content, ctx);
const logEntries = storyEventsToLogEntries(events);
const shouldOpenPanel =
enteredNodeIds.length > 0 &&
enteredNodeIds.some((id) => shouldAutoOpenPanel(prefs, id, content));
const eventLogLines = logEntries.map((e) =>
e.choiceLabel
? `Story: ${e.choiceLabel}`
: `Story: ${content.storyNodesById[e.nodeId]?.prose.slice(0, 40)}`,
);
return { enteredNodeIds, logEntries, shouldOpenPanel, eventLogLines };
}