Cinematic Pipeline (Reward Cinematics)

The reward cinematic pipeline is a plug-in system for per-family reward reveal animations. Each reward family (level_up, weapon_box, artifact_box, shooting_star, etc.) may own a CinematicModule that drives its render path during the reward state machine. Families without a registered module fall back to a no-op sentinel that leaves stock HUD rendering untouched.

Registered cinematics

registry.ts exposes the contract and a family → CinematicModule map. index.ts performs the wiring at module-load time.

FamilyModuleFile
level_uplevelUpSlotModulelevel-up-slot.ts
weapon_boxweaponChestModuleweapon-chest.ts
artifact_boxartifactGlitchModuleartifact-glitch.ts
shooting_starshootingStarModuleshooting-star.ts
(unregistered)defaultCinematicModule (no-op)default-cinematic.ts

Shared supporting modules:

  • intro.ts — shared two-pass intro helper (placeholder ”?” card pop-in, staggered L→R, used by every cinematic during the announce state).
  • audio.tsplayCinematicTone(kind, freq?, dur?) quick Web Audio synth for cinematic SFX (slot-tick, slot-chunk, sparkle, cheer, glitch). Routes through a 1 kHz lowpass into the UI bus and honors MicroSfx.enabled.

Render path ownership

Each cinematic owns its render path through a fixed set of lifecycle hooks called by hud.ts during the reward state machine:

startRewardReveal()   → resetActiveCinematic() → module.onStart()
updateRewardState()   → module.onStateTransition() on every state change
drawRewardCards()     → module.onUpdate() → drawBackdrop() → drawCardOverride()*
                      → (default card paint) → drawOverlay()
rewardState == 'none' → module.onEnd() (fires once)

All hooks are optional and default to no-ops. Hooks that throw propagate — modules must not swallow their own errors.

CinematicContext (per-frame snapshot)

Every hook receives a read-only CinematicContext:

FieldMeaning
timegame.time at frame start (seconds, monotonic).
dtSeconds since previous frame. Respects time dilation — not raw RAF dt.
stateCurrent RewardState (none / announce / slot_intro / slot_lock / shine / showing / fadeout / fly_center / burst / upgrade_show / merge_show).
stateTimerSeconds inside the current state — resets on every transition.
familyReward family id.
choicesThe three reward cards ({ id, name, icon, rarity, type, … }). May be empty in upgrade_show / merge_show / fly_center / burst.
selectedIndex-1 until the player (or autopick) picks a card.
W, HScreen size in CSS pixels.
uiScaleHUD scale factor for fonts/radii at device DPI.
cardW, cardHCard dimensions in CSS px (mirrors hud.ts — do not re-derive).

Modules must not mutate ctx fields.

Lifecycle hooks

HookWhenNotes
onStart(ctx)Reveal begins (startRewardReveal).Called after resetActiveCinematic().
onUpdate(ctx)Once per frame inside drawRewardCards, before drawing.Advance module-local timers, spawn particles.
onStateTransition(from, to, ctx)Each state change.ctx reflects the new state.
drawBackdrop(ctx2d, ctx)Before the dim overlay + banner.Fullscreen blackouts, vignettes, starfields. ctx2d is save()-wrapped.
drawCardOverride(ctx2d, i, x, y, w, h, reward, alpha, scale, isSelected, ctx)Once per card, before drawRewardCardImpl.Return true to suppress the default card paint (module owns the card). Return false/nothing to let the default run on top of auras drawn here.
drawOverlay(ctx2d, ctx)After all cards, inside the same save/restore pair.Sparkles, wipes, spotlights, scanlines, per-family HUD overlays.
onEnd(ctx)When rewardState returns to 'none'.Fires once per reveal.

Registration API

FunctionPurpose
registerCinematic(family, module)Map a family to a module. Replaces any prior registration. Called from index.ts.
getCinematicFor(family)Returns the registered module, or a { id: 'noop' } sentinel. Safe to call hooks on unconditionally.
resetActiveCinematic()Hook called by hud.ts at the start of every fresh reveal, before onStart. Reserved for future cross-reveal state cleanup (e.g. spark pools).

Adding a new family

  1. Create my-family.ts exporting a CinematicModule.
  2. Import it in index.ts.
  3. Call registerCinematic('my_family', myFamilyModule).

The RewardState union in registry.ts must mirror the RewardState union in hud.ts (duplicated to avoid a circular import — keep in sync).

Source

  • src/starship-survivors/engine/rendering/reward-cinematics/registry.ts — contract + family map.
  • src/starship-survivors/engine/rendering/reward-cinematics/index.ts — wiring barrel.
  • src/starship-survivors/engine/rendering/reward-cinematics/intro.ts — shared placeholder intro pass.
  • src/starship-survivors/engine/rendering/reward-cinematics/audio.ts — cinematic tone synth.
  • src/starship-survivors/engine/rendering/reward-cinematics/default-cinematic.ts — no-op fallback.
  • src/starship-survivors/engine/rendering/reward-cinematics/level-up-slot.ts, weapon-chest.ts, artifact-glitch.ts, shooting-star.ts — per-family modules.