# Showcases: Foundations

The hero on this site is a switchable showcase: five self-contained Skia/CanvasKit pieces —
[Physarum](/guides/physarum), [a nuclear reactor](/guides/reactions), a
[spacetime lattice](/guides/spacetime), a [procedural map](/guides/map), and a
[logo reveal](/guides/logo) — 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

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.)

<Aside type="tip" title="Why that's the good news">
A thin, excellent loop-plus-post-FX seam is exactly what lets a heavy custom CanvasKit
simulation look AAA without a monolithic framework dictating your data model. You keep your sim;
the engine makes it run deterministically and finish beautifully.
</Aside>

## 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:

```ts
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):

```ts
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

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:

```ts

const loop = new GameLoop({ fixedDt: 1 / 60, maxDt: 0.1 });

loop.onFixedUpdate.add((dt) => stepSimulation(dt));   // deterministic, 1/60 s — physics/agents
loop.onUpdate.add((dt) => { coro.update(dt); smoothDisplay(dt); }); // variable — choreography
loop.onPreRender.add(() => renderer.draw(sim));        // the renderer reads sim state
```

Physics 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](/concepts/architecture).

## 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:

```ts

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)

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`:

```ts

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:

```ts
surface.fadeTrail(BG, trailFade);   // 1. wipe the previous frame — trailFade<1 = motion-blur, 1 = hard clear
drawScene(surface.sceneCanvas());    // 2. draw the additive scene (raw CanvasKit, BlendMode.Plus)
surface.composite(globalIntensity);  // 3. snapshot → bloom+aberration chain → on-screen surface
```

The `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

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

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.

## Next

<CardGrid>
  <LinkCard title="Physarum network" description="Start here — the gentlest engine footprint." href="/guides/physarum" />
  <LinkCard title="Nuclear reactor" description="ECS agents + tweens + reactor kinetics." href="/guides/reactions" />
  <LinkCard title="Spacetime lattice" description="The heaviest custom physics: N-body, lensing, the chirp." href="/guides/spacetime" />
  <LinkCard title="Procedural map" description="A seeded fantasy map: autotiled roads, terrain clusters, an adventure line." href="/guides/map" />
  <LinkCard title="Logo reveal" description="The taste-over-spectacle piece: a pure timeline + one hand-written Skia specular shader — the thinnest engine seam." href="/guides/logo" />
</CardGrid>

<Aside type="note">
See all five running in the [hero on the home page](/) — switch between them with the picker in
the top-right corner.
</Aside>
