diff --git a/src/content/__tests__/schema.test.ts b/src/content/__tests__/schema.test.ts index fe5b756..b15c946 100644 --- a/src/content/__tests__/schema.test.ts +++ b/src/content/__tests__/schema.test.ts @@ -208,4 +208,21 @@ describe('action kind schema', () => { }); expect(content.actionsById.rest.kind).toBe('loop'); }); + + it('rejects loop action without durationMs', () => { + expect(() => + buildContent({ + resources: [{ id: 'supplies', name: 'Supplies' }], + actions: [ + { + id: 'broken_loop', + name: 'Broken loop', + kind: 'loop', + group: { id: 'camp', label: 'Camp' }, + yields: [], + }, + ], + }), + ).toThrow(/durationMs/); + }); }); diff --git a/src/content/schema.ts b/src/content/schema.ts index d42ad9d..527d274 100644 --- a/src/content/schema.ts +++ b/src/content/schema.ts @@ -61,7 +61,7 @@ export const actionDefSchema = z path: ['durationMs'], }); } - if (action.kind === 'story' && !action.storyChoiceId) { + if (action.kind === 'story' && action.storyChoiceId === undefined) { ctx.addIssue({ code: 'custom', message: 'story actions require storyChoiceId', diff --git a/src/engine/game.ts b/src/engine/game.ts index b83b52e..4b7a040 100644 --- a/src/engine/game.ts +++ b/src/engine/game.ts @@ -169,6 +169,7 @@ export function tickGame(state: GameState, content: Content, tickMs: number): Ti const actionId = state.activeActionId; const action = content.actionsById[actionId]; if (!action) return { completedActionIds }; + if (action.durationMs === undefined) return { completedActionIds }; if (state.actionElapsedMs < action.durationMs) return { completedActionIds }; state.actionElapsedMs -= action.durationMs; diff --git a/src/state/viewModel.ts b/src/state/viewModel.ts index 64b9957..168df55 100644 --- a/src/state/viewModel.ts +++ b/src/state/viewModel.ts @@ -80,7 +80,7 @@ export function toView(state: GameState, content: GameContent): GameView { })); const action = state.activeActionId ? content.actionsById[state.activeActionId] : undefined; - const actionProgress = action ? Math.min(1, state.actionElapsedMs / action.durationMs) : 0; + const actionProgress = action && action.durationMs ? Math.min(1, state.actionElapsedMs / action.durationMs) : 0; const queuedActionIds = [...state.actionQueue]; const queuedActionNames = queuedActionIds.map((id) => content.actionsById[id]?.name ?? id);