PURPOSE
Render adapter that bridges the per-frame 2D draw pass into the live-shader runtime for archetype === 'cone_beam' bullets. A single shared LiveShaderRuntime hosts the fragment shader; each active cone bullet renders the shader once with its own uniforms and composites the result back onto the main 2D canvas via drawImage. The cone’s hitbox is computed elsewhere from the same world-space inputs sent here, keeping visual and collision locked together.
OWNS
- Module-scoped singleton
_runtime: LiveShaderRuntime | nullfor the shared cone-beam shader instance. - Module-scoped flag
_unavailable: booleanthat latches when WebGL2 or shader compilation fails, suppressing further init attempts. - Lazy construction of the runtime on first
getConeBeamRuntime()call usingDYNAMIC_CONE_FIRE_IDandDYNAMIC_CONE_FIRE_GLSLwith'additive'blend mode. - Public
ConeBeamDrawParamsinterface defining the per-draw input shape (world endpoints, half-angle, intensity, heat, camera, zoom, time).
READS FROM
./live-shader-runtimefor theLiveShaderRuntimeclass (init, resize, setUniforms, render, isReady, getCanvas, destroy).../../data/vfx/live-shaders/dynamic_cone_fire.livefor the shader id and GLSL source constants.- The caller-supplied
CanvasRenderingContext2D(itscanvas.width/canvas.heightfor the composite destination size). - Caller-supplied
ConeBeamDrawParams(world coordinates, beam geometry, visual modulation, camera transform, time).
PUSHES TO
- The shared
LiveShaderRuntimeviasetUniforms(uFrom,uTo,uHalfAngle,uCoreIntensity,uHeat,uCamera,uZoom) andrender(timeSeconds). - The caller’s
CanvasRenderingContext2Dviactx.drawImage(glCanvas, 0, 0, ctx.canvas.width, ctx.canvas.height), upsampling the half-res shader canvas to full viewport.
DOES NOT
- Does not set
ctx.globalCompositeOperation— caller must already have additive ('lighter') mode active, matching the bullet pass in the bridge layer. - Does not compute the cone hitbox — that lives in
bullets.tsand consumes the same world-space inputs independently. - Does not iterate bullets or filter by archetype — caller decides when to invoke
drawConeBeam. - Does not retry runtime init after a failure; once
_unavailablelatches, onlydisposeConeBeamRuntimeclears it. - Does not own the destination canvas, the camera transform, or bullet lifecycle.
Signals
getConeBeamRuntimereturnsnullto signal WebGL2 unavailable or shader compile failure (latched via_unavailable).drawConeBeamis a silent no-op when the runtime is missing orrt.isReady()is false.resizeConeBeamRuntimeis idempotent and is a no-op when the runtime is unavailable.
Entry points
getConeBeamRuntime(): LiveShaderRuntime | null— lazy accessor for the shared runtime singleton.resizeConeBeamRuntime(viewW: number, viewH: number): void— call when the main canvas resizes.drawConeBeam(ctx: CanvasRenderingContext2D, params: ConeBeamDrawParams): void— render one cone and composite ontoctx.disposeConeBeamRuntime(): void— destroy the runtime and clear both the singleton and the_unavailableflag; exposed for shutdown / test teardown.
Pattern notes
- Lazy singleton with negative-result caching:
_unavailableprevents repeated failed init attempts each frame. - Init failure path constructs the runtime, calls
init(), then latches_unavailableand returnsnullwithout retaining the runtime reference. - Half-resolution offscreen render upsampled by
drawImage(bilinear) into the full-res context; the source comment notes this is acceptable for a soft fire effect. - Stateless per-draw API: the caller passes the full uniform set on every call; no diffing or persistence between frames.
- Shared runtime is reused across all simultaneous cone bullets — one shader compilation, one GL canvas, one
render+drawImagepair per cone per frame. - Visual/collision coupling is enforced by convention (shared inputs), not by code in this module.