Event flow
Continuous state via MobX reactions; discrete events via typed emitters.
slotplate uses two observer primitives. Pick the one that matches the shape of the data, not the one you reach for out of habit.
MobX reactions for continuous state
autorun and reaction fire every time the observable they
depend on changes. They're perfect for "update the balance display when the balance
changes" — the data is a continuous value and the reaction is idempotent.
// HUDPresenter
autorun(() => {
balance.textContent = `Balance: $${stores.balance.balance.toFixed(2)}`;
}); Event emitters for discrete events
pixi-reels ships a typed EventEmitter<ReelSetEvents>. Use it for
discrete moments: "a reel landed", "all reels landed", "the spotlight started".
A reaction would fire on every frame while reels spin — the wrong shape.
reelSet.events.on('spin:reelLanded', ({ reel, index }) => {
analytics.track('reel_landed', { index });
}); Where events flow in a round
The loop above closes via resolution events — allLanded from the
reels, onComplete from win animations. A phase awaits these (or its
enter function returns a promise that resolves when the resolution
event arrives) and only then calls ctx.fsm.transition.
Who emits what
| Emitter | Events | Consumers |
|---|---|---|
HUD DOM | click, keydown | HUDPresenter |
ReelSet (pixi-reels) | spin:start, spin:reelLanded, spin:allLanded, spin:complete | ReelsPresenter, Analytics |
MobX stores | reactions on observables | Presenters |
NetworkManager | promise resolution | FSM phases |
What NOT to build
Specifically, avoid:
- A
globalEventssingleton you emit to from everywhere. Direct references are fine; they grep better. - Re-emitting MobX reactions as events to "decouple." Reactions already decouple — that's their job.
- Re-emitting
pixi-reelsevents on a custom bus. Consumers can subscribe to the original.