bookEvent to your game. We’ll use updateGlobalMult — a bonus game multiplier feature — as the running example throughout.
A book is the JSON payload returned by the RGS for each game round. It contains an array of bookEvents that are played in sequence. The order of bookEvents matters —
win should always come after reveal, for example.Add test data to bonus_books.ts
apps/lines/src/stories/data/bonus_books.ts holds an array of complete bonus books. The story MODE_BONUS/book/random randomly picks from this array to simulate RGS responses.Add a new updateGlobalMult event into an existing book’s events array (or create a new book). Copy the exact shape from your math package output:Add to bonus_events.ts
apps/lines/src/stories/data/bonus_events.ts exports a flat object where each key is a bookEvent.type. The per-event stories (MODE_BONUS/bookEvent/<TYPE>) use this to run a single bookEvent in isolation.Register a story in ModeBonusBookEvent.stories.svelte
apps/lines/src/stories/ModeBonusBookEvent.stories.svelte defines all sub-stories under MODE_BONUS/bookEvent. Add a <Story> block for your new event:MODE_BONUS/bookEvent/updateGlobalMult in the Storybook sidebar with an Action button. Clicking it does nothing yet — that is expected. You’ve set up the test harness first.Define the TypeScript type in typesBookEvent.ts
apps/lines/src/game/typesBookEvent.ts contains the BookEvent union type. Add a new member type for your bookEvent:BookEvent is a TypeScript union type. BookEventOfType<T> (defined at the bottom of this file) uses Extract to narrow the union to a specific member — this is what gives you typed bookEvent parameters in handlers.Add the bookEventHandler in bookEventHandlerMap.ts
apps/lines/src/game/bookEventHandlerMap.ts maps each bookEvent.type string to an async handler function. Add a handler for updateGlobalMult:BookEventUpdateGlobalMult to the union in the previous step, TypeScript will provide full intellisense on bookEvent — including bookEvent.globalMult.The handler broadcasts emitterEvents via
eventEmitter.broadcast() (sync, fire-and-forget) or eventEmitter.broadcastAsync() (async, waits for all subscribers to resolve). Use broadcastAsync when you need to wait for an animation to complete before proceeding.Create the Svelte component with emitterEvent type union
Create
apps/lines/src/components/GlobalMultiplier.svelte. All logic related to the global multiplier should live here.Start by declaring the EmitterEventGlobalMultiplier union type in the module script block. This type is exported so other files can import it:EmitterEventGlobalMultiplier is a TypeScript union type, just like BookEvent. Each member corresponds to one atomic action this component can respond to.Add emitterEvent types to typesEmitterEvent.ts
apps/lines/src/game/typesEmitterEvent.ts assembles the EmitterEventGame union from all per-component exports:Update eventEmitter.ts to include the new EmitterEvent types
apps/lines/src/game/eventEmitter.ts composes the top-level EmitterEvent union and creates the singleton eventEmitter instance:EmitterEventGame now includes EmitterEventGlobalMultiplier, all eventEmitter.broadcast() calls in the codebase will now offer globalMultiplierShow, globalMultiplierHide, and globalMultiplierUpdate as valid types in the intellisense autocomplete.Implement the component's subscribeOnMount handler
Back in
GlobalMultiplier.svelte, add the component script and subscribe to the emitter events:subscribeOnMount automatically registers the handlers when the component mounts and unregisters them on destroy, so you don’t need to manage lifecycle manually.Each handler in the map follows the Single Responsibility Principle — globalMultiplierShow only shows the component, globalMultiplierUpdate only updates the value. See the Task Breakdown Pattern guide for why this matters.Test individually in Storybook
Run Storybook and navigate to Click the Action button. The
MODE_BONUS/bookEvent/updateGlobalMult:GlobalMultiplier component should animate correctly. When the handler completes, the story displays:ⓘ Action is resolved ✅If the action does not resolve, go back to the component and debug until it resolves cleanly.
Test in books
Switch to
MODE_BONUS/book/random in the Storybook sidebar. Keep clicking Action to cycle through the books defined in bonus_books.ts. Because you added the updateGlobalMult event to one of those books in step 1, it will appear eventually — and your component should respond correctly.Once every bookEvent story resolves with ✅ and the full book plays through without issues, the feature is complete.Reference: existing handler example
For comparison, here is theupdateFreeSpin handler from bookEventHandlerMap.ts — a simpler real-world example showing the same pattern:
FreeSpinCounter.svelte:
