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

  • RewardState type — a duplicate of the RewardState union from hud.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'.
  • CinematicContext interface — the read-only per-frame snapshot passed to every hook. Fields: time, dt, state, stateTimer, family, choices, selectedIndex, W, H, uiScale, cardW, cardH.
  • CinematicModule interface — 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.ts reads getCinematicFor(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 from index.ts) call registerCinematic at module load time.
  • resetActiveCinematic() is called by hud.ts at 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.ts does. 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.ts and 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 in onStart.
  • Does not expose the _modules Map externally. All access goes through registerCinematic / 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 with ctx reflecting the new state.
  • drawRewardCards() per frame → module.onUpdate(ctx)module.drawBackdrop(ctx2d, ctx) → per-card loop module.drawCardOverride(ctx2d, cardIndex, x, y, w, h, reward, alpha, scale, isSelected, ctx) → default card paint (suppressed if override returned true) → module.drawOverlay(ctx2d, ctx).
  • When rewardState returns 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 _noop sentinel if none. Safe to invoke any hook unconditionally on the returned value.
  • resetActiveCinematic(): void — single call site invoked by hud.ts at the start of every fresh reveal. Currently a stub for future cross-reveal state clearing.

Pattern notes

  • The RewardState union is intentionally duplicated here rather than imported from hud.ts because hud.ts imports this file — importing back would create a circular dependency. Both unions must be kept in sync manually.
  • The _noop sentinel pattern means call sites in hud.ts never need a null check; every hook is optional on CinematicModule, and the sentinel simply has none defined, so ?.() calls cleanly fall through.
  • ctx2d passed to drawBackdrop / drawCardOverride / drawOverlay is already wrapped in a save()/restore() pair by hud.ts — modules don’t need to restore state themselves.
  • dt on CinematicContext already respects time dilation; it is not raw RAF delta. Modules should use it directly without re-scaling.
  • cardW / cardH are precomputed by hud.ts to match the stock card layout. Modules should consume them rather than re-deriving, otherwise card overrides will mis-align with surrounding HUD elements.
  • choices may be empty during upgrade_show, merge_show, fly_center, and burst states — modules that paint card content must guard against an empty array.
  • selectedIndex is -1 until the player or autopick chooses a card.
  • Module id is for logging/debug only and is never shown to the player.