PURPOSE
Plug-in contract and module-level registry for per-family reward reveal cinematics. Each reward family (level_up, weapon_box, artifact_box, shooting_star, etc.) may register a CinematicModule whose hooks are invoked by hud.ts during the reward reveal state machine. Families without a registered module receive a no-op sentinel so call sites can dispatch hooks unconditionally without branching.
This file exposes only the contract (types, interface) and the registry primitives — actual module registrations live in index.ts.
OWNS
RewardStatetype — a duplicate of theRewardStateunion fromhud.ts, kept in sync to avoid a circular import. Values:'none' | 'announce' | 'slot_intro' | 'slot_lock' | 'shine' | 'showing' | 'fadeout' | 'fly_center' | 'burst' | 'upgrade_show' | 'merge_show'.CinematicContextinterface — the read-only per-frame snapshot passed to every hook. Fields:time,dt,state,stateTimer,family,choices,selectedIndex,W,H,uiScale,cardW,cardH.CinematicModuleinterface — bag of optional hooks:id,onStart,onUpdate,onStateTransition,drawBackdrop,drawCardOverride,drawOverlay,onEnd.- The private module map
_modules: Map<string, CinematicModule>. - The private no-op sentinel
_noop(id'noop') returned for unregistered families. - Exported functions:
registerCinematic(family, module),getCinematicFor(family),resetActiveCinematic().
READS FROM
Nothing. This file has no imports — it is purely a contract + Map wrapper. Module registrations come in via call sites (notably index.ts).
PUSHES TO
hud.tsreadsgetCinematicFor(family)to fetch the active module and invokes hooks during the reward state machine.- Concrete cinematic modules (e.g.
default-cinematic, and any per-family modules registered fromindex.ts) callregisterCinematicat module load time. resetActiveCinematic()is called byhud.tsat the start of every fresh reveal so modules don’t leak state between reveals.
DOES NOT
- Does not render anything. Drawing is entirely the module’s responsibility; this file holds no canvas references.
- Does not iterate, advance, or own the reward state machine —
hud.tsdoes. Modules only observe transitions via the hooks. - Does not catch hook errors. If any module hook throws, the error propagates to
hud.ts. - Does not mutate
CinematicContext. The contract is read-only; modules must not write back to context fields. - Does not register any modules itself. Registration is a side effect performed by
index.tsand per-family files. - Does not clear module-local state in
resetActiveCinematic(). The function is currently a hook for future use — modules are expected to clear their own state inonStart. - Does not expose the
_modulesMap externally. All access goes throughregisterCinematic/getCinematicFor.
Signals
The cinematic lifecycle, in the order hud.ts invokes hooks:
startRewardReveal()→resetActiveCinematic()→module.onStart(ctx).updateRewardState()→module.onStateTransition(fromState, toState, ctx)fires once per transition withctxreflecting the new state.drawRewardCards()per frame →module.onUpdate(ctx)→module.drawBackdrop(ctx2d, ctx)→ per-card loopmodule.drawCardOverride(ctx2d, cardIndex, x, y, w, h, reward, alpha, scale, isSelected, ctx)→ default card paint (suppressed if override returnedtrue) →module.drawOverlay(ctx2d, ctx).- When
rewardStatereturns to'none'→module.onEnd(ctx)fires once.
Return semantics: drawCardOverride returns true to fully suppress the default drawRewardCardImpl for that card index; false (or nothing) lets the default paint run, which lets a module draw an aura underneath and then defer to the stock card render on top.
Entry points
registerCinematic(family: string, module: CinematicModule): void— replaces any prior registration for that family.getCinematicFor(family: string): CinematicModule— returns the registered module, or the_noopsentinel if none. Safe to invoke any hook unconditionally on the returned value.resetActiveCinematic(): void— single call site invoked byhud.tsat the start of every fresh reveal. Currently a stub for future cross-reveal state clearing.
Pattern notes
- The
RewardStateunion is intentionally duplicated here rather than imported fromhud.tsbecausehud.tsimports this file — importing back would create a circular dependency. Both unions must be kept in sync manually. - The
_noopsentinel pattern means call sites inhud.tsnever need a null check; every hook is optional onCinematicModule, and the sentinel simply has none defined, so?.()calls cleanly fall through. ctx2dpassed todrawBackdrop/drawCardOverride/drawOverlayis already wrapped in asave()/restore()pair byhud.ts— modules don’t need to restore state themselves.dtonCinematicContextalready respects time dilation; it is not raw RAF delta. Modules should use it directly without re-scaling.cardW/cardHare precomputed byhud.tsto match the stock card layout. Modules should consume them rather than re-deriving, otherwise card overrides will mis-align with surrounding HUD elements.choicesmay be empty duringupgrade_show,merge_show,fly_center, andburststates — modules that paint card content must guard against an empty array.selectedIndexis-1until the player or autopick chooses a card.- Module
idis for logging/debug only and is never shown to the player.