Design and implement VGF phase systems: phase definitions, transitions, lifecycle hooks, endIf conditions, cascading transitions, and real-world phase patterns. Use when designing game flow, debugging phase transitions, implementing endIf/next/onBegin/onEnd, or understanding the PhaseRunner. Triggers: vgf phase, phase design, endIf, onBegin, onEnd, phase transition, phase runner, game flow, state machine, game phase, lobby phase, game over phase, cascading transition, phase modification, engine state machine, dispatch buffering, transition depth, cascade depth, WoF pattern, dispatchFromLifecycle
Design game flow with VGF's phase system — a structured state machine for game stages. Now integrated with the engine state machine, dispatch buffering, and cascade depth limiting.
interface Phase<GameState extends BaseGameState, TActions = ActionRecord<GameState>> {
actions: TActions // REMOVED (v4.9.0) — leave empty {}
reducers: ReducerRecord<GameState> // Phase-scoped reducers
thunks: ThunkRecord<GameState> // Phase-scoped thunks
onBegin?: OnBegin<GameState> // Fires when entering phase (can return void — v4.11.0+)
onEnd?: OnEnd<GameState> // Fires when leaving phase (can return void — v4.11.0+)
endIf?: EndIf<GameState> // Auto-transition predicate
next: Next<GameState> // Target phase (string or function)
}
onBegin(ctx) — Receives IOnBeginContext with reducer/thunk dispatchers, . Returns (void support added v4.11.0). CANNOT modify or . Engine state: . Dispatches via DO trigger endIf (cascading).
getState()GameState | void | Promise<GameState | void>state.phasestate.previousPhaseOnBegindispatchFromLifecyclePhase active — Phase-specific + root reducers/thunks are available. Engine state: Idle.
endIf(ctx) — Checked after EVERY non-internal reducer dispatch. Returns boolean.
When endIf → true:
OnEnd: onEnd(ctx) fires for outgoing phase. Dispatches via dispatchFromLifecycle do NOT trigger endIf (onEnd is a finaliser).Swapping: internal:SET_PHASE dispatched (via dispatchInternalReducer — synchronous)OnBegin: onBegin(ctx) fires for incoming phase. Dispatches via dispatchFromLifecycle DO trigger endIf (cascading).Draining: buffered dispatches processed sequentiallyIdleCascading — If new phase's endIf is immediately true, PhaseRunner loops (max depth 10 — TransitionDepthTracker).
Dispatch buffering (v4.12.0) — Any dispatchReducer calls arriving during non-Idle engine states are queued in the DispatchBuffer and processed during the drain phase. Max 100 drain iterations (throws DrainOverflowError).
const phases = {
lobby: {
actions: {}, reducers: {}, thunks: {},
endIf: (ctx) => ctx.session.state.allPlayersReady,
next: "playing",
},
playing: {
actions: {}, reducers: {}, thunks: {},
endIf: (ctx) => ctx.session.state.gameComplete,
next: "gameOver",
onBegin: async (ctx) => {
// Load questions, start timers
// v4.11.0+: can return void — no need to return state
},
},
gameOver: {
actions: {}, reducers: {}, thunks: {},
endIf: undefined, // Terminal — thunks handle transitions
next: "playing", // Default for endPhase() calls
},
}