Showcases: Foundations
The hero on this site is a switchable showcase: five self-contained Skia/CanvasKit pieces — Physarum, a nuclear reactor, a spacetime lattice, a procedural map, and a logo reveal — that share one spine. This page is that spine. Read it first; each showcase guide then covers only what is unique to it.
The honest seam: what the engine does, and what you write
Section titled “The honest seam: what the engine does, and what you write”These pieces look like the engine is doing the heavy lifting. It isn’t, and the guides are careful about this — because the interesting, sellable story is the real one. Across the showcases:
| Concern | Where it lives |
|---|---|
| Fixed-timestep loop, variable display step | @flare-engine/core — GameLoop |
| Timer-free choreography (phases, sequences) | @flare-engine/core — CoroutineManager |
clamp / lerp, seeded RNG |
@flare-engine/math |
| Pooled, zero-alloc particle bursts | @flare-engine/particles — ParticlePool |
| Bloom + chromatic aberration (real SkSL) | @flare-engine/postfx — EffectChain |
| Agents as entities, control-rod tweens | @flare-engine/ecs + @flare-engine/animation (reactor only) |
| The simulation itself — pheromone fields, reactor kinetics, N-body gravity, lensing, seeded map + road generation | Your application code |
| Every scene draw — circles, lines, the field image | Raw CanvasKit |
So the engine gives you a tight loop, math helpers, a particle pool, and a high-quality post-FX
finish — plus, for the reactor, an ECS and tweens. Everything domain-specific is ordinary code
you write against raw CanvasKit. None of these demos import @flare-engine/render or
@flare-engine/physics; the physics is bespoke, pure TypeScript. Since these guides were written,
the reusable primitives behind two of them — the N-body gravity, Verlet integration, and
wave-packet ripples of Spacetime, and the stigmergy field of Physarum — have upstreamed into
@flare-engine/simulation (shipped in 0.2.0). The demos on this site still run their own local
copies, but you no longer have to write those from scratch. (The reactor’s neutron kinetics stay
bespoke — no reactor package exists.)
The animation contract
Section titled “The animation contract”Every showcase implements one small interface, HeroAnimation
(src/components/demos/animations/types.ts). The generic host (HeroStage.tsx) owns CanvasKit
loading (streamed WASM with a progress bar), the ResizeObserver / IntersectionObserver
lifecycle, an FPS monitor, cross-fade switching, and the reduced-motion fallback. An animation
only has to build an instance against an already-loaded CanvasKit and a sized canvas:
export interface HeroAnimation { readonly id: string; readonly label: string; readonly ariaLabel: string; create( kit: CanvasKit, canvas: HTMLCanvasElement, overrides?: ParamValues, ): HeroAnimationInstance | null;}
export interface HeroAnimationInstance { start(): void; pause(): void; resume(): void; resize(width: number, height: number): void; status(): string; dispose(): void; // Optional: a tunable-parameter schema + live stats the dev panel feature-detects. params?(): readonly ParamDescriptor[]; getParams?(): ParamValues; setParam?(key: string, value: ParamValue): void; stats?(): readonly StatReadout[];}The five adapters (animations/physarum-network.ts, nuclear-reaction.ts,
spacetime-lattice.ts, map.ts, logo.ts) are near-identical thin merges of defaults +
overrides. The whole wiring is three lines — build a renderer, build a sim, hang the render call
off the loop (the Logo piece is the minimal case: a pure timeline clock and no sim at all):
create(kit, canvas, overrides) { const renderer = createPhysarumRenderer(kit, canvas); if (!renderer) return null; const sim = createPhysarumSim(canvas.width, canvas.height, configFrom(overrides)); const offRender = sim.loop.onPreRender.add(() => renderer.draw(sim)); return { start: () => sim.loop.start(), pause: () => sim.loop.pause(), resume: () => sim.loop.resume(), resize: (w, h) => { renderer.resize(w, h); sim.resize(w, h); }, status: () => sim.phase(), dispose: () => { offRender(); sim.dispose(); renderer.dispose(); }, };}The game loop
Section titled “The game loop”The sim owns a GameLoop from @flare-engine/core. It separates a fixed-rate simulation step
from a variable-rate display/choreography step, and emits a pre-render signal the renderer hangs
off of:
import { GameLoop } from "@flare-engine/core";
const loop = new GameLoop({ fixedDt: 1 / 60, maxDt: 0.1 });
loop.onFixedUpdate.add((dt) => stepSimulation(dt)); // deterministic, 1/60 s — physics/agentsloop.onUpdate.add((dt) => { coro.update(dt); smoothDisplay(dt); }); // variable — choreographyloop.onPreRender.add(() => renderer.draw(sim)); // the renderer reads sim statePhysics on the fixed step is what makes the behavior frame-rate-independent and reproducible; rendering and choreography ride the variable step. This is the engine’s documented per-frame order — see Concepts → Architecture.
Timer-free choreography with coroutines
Section titled “Timer-free choreography with coroutines”None of these animations use wall-clock timers. Each runs a CoroutineManager state machine
whose waits are gated on the live state of the simulation, so the show always paces itself to
what’s actually happening on screen and loops seamlessly:
import { CoroutineManager } from "@flare-engine/core";
function* waitUntil(pred: () => boolean) { while (!pred()) yield;}
function* cycle() { yield* waitUntil(() => population() >= growthTrigger()); // wait for the reaction to take off // ...drive the next phase...}
const coro = new CoroutineManager();coro.start(cycle());// driven from loop.onUpdate: coro.update(dt)The post-FX surface (the one place the engine renders)
Section titled “The post-FX surface (the one place the engine renders)”This is the most important shared module: src/components/demos/postfx/surface.ts. It wraps a
CanvasKit WebGL on-screen surface plus an offscreen ping-pong pair, adapts CanvasKit’s
RuntimeEffect to @flare-engine/postfx’s SkiaPostFxModuleLike, and runs a real EffectChain:
import { createBloom, createChromaticAberration, EffectChain } from "@flare-engine/postfx";
const chain = new EffectChain();chain.add(createBloom("bloom", { threshold: 0.5, intensity: 0.9 }));chain.add(createChromaticAberration("aberration", { amount: 0.0022 }));Those are the engine’s actual SkSL shaders, compiled against CanvasKit’s RuntimeEffect — the
same post-processing you’d get on native Skia, here on the web. The surface defines the universal
per-frame shape every animation follows:
surface.fadeTrail(BG, trailFade); // 1. wipe the previous frame — trailFade<1 = motion-blur, 1 = hard cleardrawScene(surface.sceneCanvas()); // 2. draw the additive scene (raw CanvasKit, BlendMode.Plus)surface.composite(globalIntensity); // 3. snapshot → bloom+aberration chain → on-screen surfaceThe trailFade alpha passed to fadeTrail is the trails knob, and it’s per-animation. Below 1,
each frame fades the prior scene toward the background instead of erasing it, so motion leaves a
glow — Physarum and Spacetime do this (≈0.12–0.18). At exactly 1, fadeTrail is a hard
clear: the reactor uses that on purpose, for crisp, trail-free entities that read as solid
objects rather than streaks. Either way, composite() runs the real bloom + aberration chain and
presents the frame to the on-screen surface — so the three-step shape is universal even though
the trail is opt-in.
The palette
Section titled “The palette”One frozen prism palette (src/components/demos/reactor/palette.ts) is the single source for
PALETTE, SPARK_COLORS, and the color4f helper, re-exported verbatim by the other animations.
Spacetime adds scene-only planet colors (spacetime/planets.ts) deliberately outside the brand
palette, because the solar system reads better with real planetary hues.
The pure-module discipline
Section titled “The pure-module discipline”The interesting math in each piece — reactor/physics.ts, physarum/field.ts,
spacetime/{nbody,lattice,ripples,starfield}.ts, and map/grid.ts — is written as
zero-import, framework-free TypeScript with a co-located *.test.ts, “designed to upstream.”
That discipline paid off: the Spacetime N-body/Verlet/ripple math and the Physarum field have since
shipped as @flare-engine/simulation (0.2.0), and the Map’s seeded road generator + edge-mask
autotiler are the seed of a planned @flare-engine/procgen (its tile side already has a home in
the shipped @flare-engine/tilemap). That’s the convention worth copying: your simulation math is
your own pure code, unit-tested with bun test in isolation from CanvasKit and from the engine, so
it stays correct and portable — and, when it’s good, liftable into a package. The engine seam
(loop, pool, post-FX) wraps it; it doesn’t own it.