PURPOSE
Cinematic module for the artifact_box reward family. Plays an alien-treasure reveal where three reward cards flip from a runed back face to their front face one at a time L→R, each landing emitting a rarity-colored rune ring burst with a chime. Despite the filename, the visual is explicitly not a glitch/horror effect — it is a cybernetic alien-artifact discovery sequence. Exports artifactGlitchModule with id 'artifact-treasure'.
OWNS
- Module-local clock
_elapsedplus_clockArmedflag, driven offctx.dtbecausegame.timefreezes during level-up. - Per-card state:
_flipStarted,_landed,_landedAt, and a stashed_cardRectrectangle per slot (three slots). - A rune particle pool
_runes(capped atRUNE_POOL_CAP = 72) ofRuneParticlerecords with position, velocity, rotation, age, life, color, and shape (hex / triangle / plus / circle-dot). - Painting of the back face (dark plate with cyan radial glow, pulsing central hex glyph + crosshair, rarity-accented border) via
_paintBackFace/_hexPath/_roundRect. - Painting of the front-approx half-flip plate (rarity gradient + white accent ring) inside
drawCardOverride. - Painting of the expanding ring pulse on each flip-land and additive
lighterrune-particle rendering indrawOverlay. - The ambient cyan radial glow drawn behind the card row in
drawBackdrop.
READS FROM
CinematicContextfrom./registry—state,dt,W,H,choices(per-cardrarity).RARITY_ACCENTfrom../card-themefor rarity-tinted colors.isSkipRewardAnimationsfrom../../core/config/_accessibilityfor the skip-animations branch.
PUSHES TO
setSlotIntroDuration(...)from../hud— sets2.55for the normal path so cards become interactable shortly after the last rune burst, or0.05when reward animations are skipped.playCinematicTone(...)from./audio— emits'sparkle'pre-flip shimmer and'cheer' + 'sparkle'cues per flip-land.SampleSfx.playArtifactReveal()from../../audio/sample-sfx— artifact reveal stinger ononStart.- Canvas via the
CanvasRenderingContext2Dpassed todrawBackdrop,drawCardOverride, anddrawOverlay.
DOES NOT
- Does not read or modify
game.time(frozen during level-up); all timing comes from accumulatedctx.dt. - Does not own card layout, badge, icon, name, description, or pill rendering — once
_elapsedis pastflipStart + FLIP_DURATION,drawCardOverridereturnsfalseand hud’s default paint takes over. - Does not allocate beyond
RUNE_POOL_CAPrune particles; spawn loop breaks once the cap is reached. - Does not perform input handling, gameplay logic, or rarity selection — choices arrive prepopulated via
ctx.choices. - Does not persist any state across cinematics;
_resetis called in bothonStartandonEnd.
Signals
- One-shot cue per flip start:
playCinematicTone('sparkle', 1800 + i * 120, 0.12)when_elapsedfirst crossesFLIP_START_TIMES[i]. - One-shot cues per flip land:
playCinematicTone('cheer', 520 + i * 55)andplayCinematicTone('sparkle', 2400 + i * 140), paired with a_spawnRuneBurstat the card center. - Module-load-time stinger:
SampleSfx.playArtifactReveal()fires inonStartfor the non-skip path.
Entry points
onStart(_ctx)— resets all state; in skip-animations mode marks every card landed at_elapsed = 0and sets a0.05sslot intro; otherwise sets a2.55sslot intro and plays the artifact reveal stinger.onUpdate(ctx)— parks the clock duringstate === 'announce', then accumulates_elapsed += ctx.dt. Fires flip-start cues, lands cards once a_cardRectis available (otherwise retries next frame), and steps rune particles with dragMath.pow(0.90, ctx.dt * 60).drawBackdrop(ctx2d, ctx)— paints the ambient cyan radial glow once pastannounce.drawCardOverride(ctx2d, cardIndex, x, y, w, h, reward, _alpha, _scale, _isSelected, ctx)— stashes_cardRect[cardIndex], suppresses paint duringannounce(returnstrue), paints the back face pre-flip and during the first half of the flip, paints the rarity-gradient front-approx during the second half, then returnsfalsepost-flip to hand off to default paint.drawOverlay(ctx2d, _ctx)— draws the expanding rarity-colored impact ring per landed card (lifetimeRING_LIFE = 0.55) and additive-composited rune particles with a fade curve (p < 0.15 ? p / 0.15 : 1 - (p - 0.15) / 0.85) plus a white inner dot.onEnd(_ctx)— calls_reset.artifactGlitchModule: CinematicModule— exported registration, id'artifact-treasure'.
Pattern notes
- Tunables block at the top of the file:
FLIP_START_TIMES = [0.30, 1.10, 1.90],FLIP_DURATION = 0.45,RING_LIFE = 0.55,RUNES_PER_CARD_BY_RARITY(8 / 10 / 12 / 14 / 18 for common / uncommon / rare / epic / legendary),RUNE_POOL_CAP = 72. Last flip lands at ~2.35s, slot intro sized to2.55sso interactability arrives shortly after the last rune burst. - The flip is a
Math.abs(Math.cos(p * Math.PI))horizontal scale (xScale1 → 0 at half → 1 at full). The first half paints back face, the second half paints a cheap “front-approx” rarity gradient plate so the reveal identity is visible mid-flip before hud’s default paint takes over. - Landing is gated on
_cardRect[i]being non-null; ifdrawCardOverridehas not stashed the rect yet,_landed[i]stays false and the land retries next frame. - Clock arming pattern: while
ctx.state === 'announce'the elapsed clock stays parked; the first non-announce frame sets_clockArmed = trueand zeroes_elapsed. This sidesteps the frozengame.timeduring level-up. - Rune burst seeding:
_spawnRuneBurstusesseed = i + 1to varybaseAngle = (seed * 51.7) % (2π), per-particlespeed = 170 + ((i * 37 + seed * 11) % 130),size = 7 + ((i * 13 + seed) % 6),lifeSec = 0.6 + ((i * 23 + seed * 7) % 30) / 100, and shape(i % 4). - Module-scoped mutable state (single-instance cinematic) is acceptable here because cinematics run one at a time and
_resetbrackets each play; bothonStartandonEndinvoke it. - Skip-animations short-circuit:
isSkipRewardAnimations()returns early from every hook exceptonStart, which still callssetSlotIntroDuration(0.05)and marks every card landed at time 0 so downstream state machines (slot_lock / shine / showing) advance immediately. - Rune particles render with
globalCompositeOperation = 'lighter'for an additive glow; each particle also gets a small white inner dot atsize * 0.22radius to sell the “glowing rune” feel. - Per-shape paths defined in
_runePath: shape 0 hex outline, 1 upward triangle (s * 0.87base), 2 plus/cross with thicknesss * 0.32, 3 outer circle (caller fills inner dot separately).