feat(engine): add performAction dispatcher by kind

This commit is contained in:
ginnoir
2026-06-11 20:53:55 -05:00
parent 0de61d3e41
commit 163f710f4c
2 changed files with 137 additions and 0 deletions
+44
View File
@@ -220,6 +220,50 @@ export function maybeStartLoopAction(state: GameState, content: Content): void {
}
}
/**
* Single entry point for all player-initiated action dispatch.
*
* Dispatches by action.kind to the appropriate per-kind function:
* - instant: execute immediately (costs/yields, no queue slot)
* - timed: enqueue (start if idle, queue otherwise)
* - loop: toggle player enable preference; start runner if just enabled
* - story: resolve storyChoiceId and apply the choice
* - context: not implemented in M1 — throws
*
* Note on loop toggle: disabling is always allowed, even when the action is
* currently unaffordable. Affordability is the runner's concern (maybeStartLoopAction
* re-checks each tick). Throwing on unaffordable before toggling would wrongly
* block the player from DISABLING an active but now-unaffordable loop.
*/
export function performAction(state: GameState, content: GameContent, actionId: string): void {
const action = content.actionsById[actionId];
if (!action) throw new Error(`Unknown action "${actionId}"`);
switch (action.kind) {
case 'instant':
executeInstant(state, content, actionId);
break;
case 'timed':
enqueueAction(state, content, actionId);
break;
case 'loop': {
const willEnable = !state.enabledLoopActionIds[actionId];
state.enabledLoopActionIds[actionId] = willEnable;
if (willEnable) {
maybeStartLoopAction(state, content);
}
break;
}
case 'story':
executeStoryAction(state, content, actionId);
break;
case 'context':
throw new Error(`Context action "${actionId}" is not implemented`);
default:
throw new Error(`Unknown action kind "${(action as { kind: string }).kind}"`);
}
}
/**
* Advance the active action by `tickMs`. On completion, grants yields and
* advances the queue — actions do not auto-repeat when the queue is empty.