Guides

The core engine

Skip every wrapper and drive the engine on a canvas you own. The core is zero-dependency TypeScript. For a managed canvas and an ergonomic class, reach for the TypeScript wrapper instead.

Where this fits. Use @fundamental-engine/core when you own the render surface and want renderer-agnostic field math. When you want DOM participation — measurement, feedback, relationships, visual bindings, and overlays on a frame scheduler — reach for @fundamental-engine/platform. Canvas is one render surface, not the whole system.

Install

npm i @fundamental-engine/core the engine itself — when you own the canvas

createField — your own canvas

When you want to place and size the canvas yourself, hand it to createField. You get the FieldHandle. This is the lowest-level entry point; the web component, the React adapter, and @fundamental-engine/vanilla all wrap it.

TypeScript
// @fundamental-engine/core is renderer-agnostic — createField requires an explicit host.
// In the browser, browserHost() (from @fundamental-engine/platform) is the host. Pass a custom
// host to drive a headless or alternative renderer.
import { createField } from '@fundamental-engine/core';
import { browserHost } from '@fundamental-engine/platform';

const canvas = document.querySelector('canvas');
const field = createField(canvas, {
  host: browserHost(),
  accent: '#4da3ff',
  density: 1,
  attention: false,
  causality: false,
});

// Prefer a browser convenience wrapper? @fundamental-engine/vanilla re-exports a createField
// that wires browserHost() for you, so you can drop the host:
//   import { createField } from '@fundamental-engine/vanilla';
//   const field = createField(canvas, { accent: '#4da3ff' });

// the field reads every [data-body] element on the page.
// after you add or remove bodies, rescan:
field.scan();
The canvas is decorative. Position it fixed behind your content (position: fixed; inset: 0; z-index: 0; pointer-events: none) and mark it aria-hidden. Reduced motion is honoured automatically, and the loop pauses when the tab is backgrounded.
Want a managed canvas? @fundamental-engine/vanilla creates and tears down the canvas for you — as a FieldField class or the mountField() function. See the TypeScript guide.

The force contract

A force is one small object — a token and an apply(body, particle, env) that nudges a particle's velocity. Every one of the 34 built-ins implements this exact contract, and the engine never special-cases any of them, which is why they compose. The shape:

TypeScript
import type { Force } from '@fundamental-engine/core';

// Every force is one object: a token + an apply(body, particle, env).
// This is the exact contract all 34 built-ins implement.
const lift: Force = {
  token: 'lift',
  label: 'Lift',
  apply(b, p, env) {
    if (env.dist >= b.range) return;          // out of reach -> skip
    const f = (1 - env.dist / b.range) * b.strength * 0.5;
    p.vy -= f;                                // nudge the particle upward
  },
};
Registering your own. The built-in catalog is registered for you, and the registry that holds it lives inside createField — so authoring a brand-new force today means building against the Force contract at the engine level rather than passing it in as an option. The documented catalog covers most needs; compose forces with presets for bigger effects.