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.
| Family | Module | File |
|---|---|---|
level_up | levelUpSlotModule | level-up-slot.ts |
weapon_box | weaponChestModule | weapon-chest.ts |
artifact_box | artifactGlitchModule | artifact-glitch.ts |
shooting_star | shootingStarModule | shooting-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 theannouncestate).audio.ts—playCinematicTone(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 honorsMicroSfx.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:
| Field | Meaning |
|---|---|
time | game.time at frame start (seconds, monotonic). |
dt | Seconds since previous frame. Respects time dilation — not raw RAF dt. |
state | Current RewardState (none / announce / slot_intro / slot_lock / shine / showing / fadeout / fly_center / burst / upgrade_show / merge_show). |
stateTimer | Seconds inside the current state — resets on every transition. |
family | Reward family id. |
choices | The 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, H | Screen size in CSS pixels. |
uiScale | HUD scale factor for fonts/radii at device DPI. |
cardW, cardH | Card dimensions in CSS px (mirrors hud.ts — do not re-derive). |
Modules must not mutate ctx fields.
Lifecycle hooks
| Hook | When | Notes |
|---|---|---|
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
| Function | Purpose |
|---|---|
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
- Create
my-family.tsexporting aCinematicModule. - Import it in
index.ts. - 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.