shader-templates/live-registry.ts

PURPOSE

Registry of live_shader templates — GLSL source shipped directly to runtime rendering with no bake step. These are the starting-point fragments for authoring live_shader VFX components: shaders dispatched per-frame against runtime-varying uniforms (endpoint world positions, per-instance seeds, elapsed seconds) instead of being pre-rasterized into a baked atlas. Currently registers three templates: lightning_beam, adaptive_beam, chain_arc_dynamic.

OWNS

  • LIVE_TEMPLATES: Record<LiveTemplateId, LiveTemplateSpec> — single source of truth for runtime-dispatched shader templates.
  • LiveTemplateId union — 'lightning_beam' | 'adaptive_beam' | 'chain_arc_dynamic'.
  • LiveTemplateSpec interface — { id, name, fragGlsl, uniformSchema, previewSampleInputs }.
  • UniformDecl interface — local re-declaration of the uniform-entry shape (structural fallback; documented as a subset compatible with component-schema.ts task 4.1).
  • Module-private SHARED_UNIFORMS array — three uniforms every live template inherits (u_resolution, u_palette, u_intensity).

READS FROM

  • ./live/lightning_beam.frag — beam-between-two-points with flicker (spanBetween-style).
  • ./live/adaptive_beam.frag — laser / rail with separate halo and inner core widths.
  • ./live/chain_arc_dynamic.frag — per-jump segment for a chain-arc with lifetime-driven fade.

PUSHES TO

Nothing — pure data export. Consumers (the weapon workbench’s component editor, library pane, and new-component modal) import LIVE_TEMPLATES to populate the live-shader picker, seed fragGlsl into a new component, and feed previewSampleInputs into the preview renderer.

DOES NOT

  • Does not compile, link, or run GLSL — only references frag strings imported from sibling files.
  • Does not bake to a texture atlas — that is the explicit point of the live family (vs. the baked registry in registry.ts).
  • Does not validate uniform values, instance counts, or endpoint positions at runtime — range is advisory metadata for the editor.
  • Does not own the shared uniform contract globally — it re-declares UniformDecl locally as a structural fallback and documents the planned reconciliation with component-schema.ts.
  • Does not include the handle: 'radius' | 'angle' variants in practice; only handle: 'point' is used by current entries.
  • Does not own family tagging — there is no family field on LiveTemplateSpec; the whole file is implicitly one family.

Signals

SignalSourceShape
LIVE_TEMPLATES[id]exported constLiveTemplateSpec{ id, name, fragGlsl, uniformSchema, previewSampleInputs }
LiveTemplateIdtype'lightning_beam' | 'adaptive_beam' | 'chain_arc_dynamic'
LiveTemplateSpecinterfacespec contract (adds name + previewSampleInputs vs. baked TemplateSpec)
UniformDeclinterface{ name, type, doc, range?, default?, handle?, handleColor? }

Entry points

  • Weapon workbench NewComponentModal lists LIVE_TEMPLATES so a new live_shader component can be seeded from a template id.
  • ComponentEditor reads LIVE_TEMPLATES[id].fragGlsl as the starting GLSL and uniformSchema to render uniform editors.
  • ForgeLibraryPane surfaces the template names for the live-shader category in the library tree.
  • Preview renderer feeds previewSampleInputs into the shader to draw a canonical preview frame without needing live gameplay state.

Pattern notes

  • Three templates, all spanBetween-shape. Every current entry takes uFromXY + uToXY vec2 endpoint pair (with handle: 'point' so the workbench draws drag gizmos) plus a uWidth pixel scalar. The family is implicitly “directed effect between two world points.”
  • Shared uniform contract. SHARED_UNIFORMS is spread into every entry’s uniformSchema so all live templates start with u_resolution (vec2 px), u_palette (vec3[4] Quilez cosine palette flat), and u_intensity (HDR multiplier 0–4, default 1). Per-template uniforms are appended after.
  • Pixel-space uniforms. All position / width uniforms are in canvas / world pixels, not normalized [0,1] UV — different from the baked registry.ts convention where most ranges are 0–1.
  • Time / seed split. lightning_beam and chain_arc_dynamic declare per-instance uSeed: float [0,1] plus a uTimeSec clock (game-start for lightning, jump-start for chain_arc). adaptive_beam is time-stateless. chain_arc_dynamic additionally declares uLifetimeMs (50–500 ms) so the shader can fade based on uTimeSec / uLifetimeMs.
  • previewSampleInputs is a flat Record<string, unknown> keyed by uniform name. Vec2 values are [x, y] arrays, vec3[4] palette is one length-12 flat array of floats. The 720×720 canvas resolution is hard-coded in every preview.
  • Local UniformDecl re-declaration. Header comment notes this is a structural fallback subset; if task 4.1 lands component-schema.ts as the canonical schema, this should stay compatible.
  • Default palette per template is encoded in previewSampleInputs:
    • lightning_beam — blue-cyan accent (0, 0.5, 0.8).
    • adaptive_beam — mid-spectrum default (0, 0.33, 0.67).
    • chain_arc_dynamic — purple-blue accent (0.2, 0.4, 0.9), u_intensity: 2 (hotter).
  • No family field. Unlike baked TemplateSpec, LiveTemplateSpec carries no family tag — the whole file is the family. Library-pane grouping for live shaders must be inferred from the registry’s existence rather than per-entry metadata.

EXTRACT-CANDIDATE

  • Duplicate UniformDecl / UniformEntry types. This file’s UniformDecl and registry.ts’s UniformEntry are near-identical (same fields, same UniformType union, same handle / handleColor editor metadata) — divergence is that UniformDecl.default is optional while UniformEntry.default is required, and UniformDecl has a doc field that UniformEntry makes optional. Header comment explicitly flags this as a structural fallback pending component-schema.ts (task 4.1). Action: collapse to one shared uniform-schema type imported from component-schema.ts once that exists, drop the local re-declaration.
  • Two parallel template registries. LIVE_TEMPLATES (this file, 3 entries) and TEMPLATES (registry.ts, 66 entries) export different *Spec shapes — LiveTemplateSpec adds name + previewSampleInputs, drops family. The baked TemplateSpec has no name (id doubles as display) and no preview inputs. Consider unifying under one ShaderTemplateSpec with kind: 'baked' | 'live' and optional previewSampleInputs, so the workbench library pane can iterate one collection.
  • previewSampleInputs is missing from baked templates. The baked registry uses per-uniform default values to drive the preview; live templates instead carry a separate previewSampleInputs map. Either back-fill previewSampleInputs onto baked entries or derive live previews from default values — having two preview conventions is the kind of thing that drifts.
  • SHARED_UNIFORMS is duplicated as comments in baked registry. The baked registry.ts documents the shared uniform contract (u_time, u_resolution, u_palette[4], u_intensity) only in the add-a-template header comment and does not push them through uniformSchema. Live templates do push the shared three explicitly. Pick one convention.
  • name field is hand-written display copy. Worth treating consistently — either every template across both registries gets a name, or both rely on id-to-title-case derivation in the UI.
  • All three live templates are span-between shapes. If a future live template diverges (e.g. point-source aura, mesh-bound projection), revisit whether the implicit “two endpoints + width” pattern wants to be enforced by typing rather than convention.
  • No family on live templates. If LIVE_TEMPLATES grows, add a family field (e.g. 'beam' | 'arc' | 'aura') so the library pane can group entries — otherwise the picker becomes a flat unsorted list.
  • UniformDecl.type includes vec3 and vec3[4] but only 'float' and 'vec2' are used in per-template entries; vec3[4] appears once via the shared u_palette and vec3 is declared but unused. Same observation applies to baked registry — candidates for trimming or completing the helper set.