80 animations (up from 60) and a WebGL2 tier with no new dependencies.
A WebGL2 tier, still zero dependencies
Canvas 2D remains the default renderer and the fallback. registerAnimation accepts renderer: "webgl2", and a new registerShader(name, { defaults, uniforms, fragment }) helper compiles a full-screen fragment shader and hands it the same lifecycle every 2D animation already gets:
- DPR-clamped
ResizeObserversizing - pointer input as
uMouse(xy in device pixels, y-up; z = 1 while inside) IntersectionObserversuspension while off-screen- a single still frame under
prefers-reduced-motion - uniform declarations generated from the arity of the values you pass, so changing an option never recompiles the program
Geometry is one full-screen triangle derived from gl_VertexID — no vertex buffer is allocated and nothing leaks. When a browser cannot supply a WebGL2 context, glPosterFallback paints a static 2D poster from the animation's own palette rather than leaving an empty canvas.
<div id="deep" style="height:70vh"></div>
<script>Galaxy.create('volumetricNebula', '#deep', { density: 1.2 });</script>20 new animations
Relativistic & gravitational — lensing (Schwarzschild deflection and the Einstein ring) · accretionDisk (relativistic Doppler beaming + gravitational redshift) · nBody · tidalStream · inspiral
Volumetric & raymarched (WebGL2) — volumetricNebula (Beer–Lambert absorption with a Henyey–Greenstein phase function) · starSurface (convective granulation, limb darkening, prominences) · atmosphere (Rayleigh + Mie scattering through a decaying shell) · dustLanes (self-shadowed dust) · protoplanetary (planet-carved gaps, flared scale height)
Instruments — spectrograph · transitCurve · waterfall · hrDiagram · pulsarTiming
Pointer-driven & generative — gravityWell · nebulaPaint (an advecting, diffusing fluid you paint into) · solarWind (bow shock and polar cusps) · starForge · relativisticJets
Changed
galaxy.d.tsdeclares all 80 names inAnimationType.- Expected counts in every test now derive from the generated manifest.
test.mjsandtest-mcp.mjsasserted the literal60in four places, which fails on every addition and trains you to bump the number instead of reading the failure. A>= 80floor still catches accidental deletion. - New WebGL2 lifecycle contract test:
registerShaderpresent, a WebGL2 context requested, a 2D fallback available, and an assertion that the library never force-loses a WebGL context — a canvas returns the same context object on everygetContext, so losing it would poison any later mount on that canvas. galaxy.min.jsrebuilt: 157.2 kB, 52.1 kB gzipped.
Fixed
- The CI workflow existed in the repo working tree but had never been pushed, so the README's CI badge pointed at a workflow that did not exist and nothing was ever gated. It now runs on Node 18/20/22: syntax check, lint, a manifest-drift gate, the contract test and the end-to-end MCP test.
package-lock.jsonhad been pinned at3.0.0since v3.0; it now tracks the package version.
Verification
Every one of the 20 was rendered in a real browser and inspected — not merely compiled. A harness reported blank / blown-out / not-actually-animating per animation with pixel statistics and screenshots, which caught four real bugs, including solarWind, whose particles were launched upstream of a cull box that destroyed them on their first frame: the scene silently froze because the shared render loop swallows per-animation exceptions by design.
Full Changelog: v3.1.0...v3.3.0