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.