Fundamental is a platform-native relational field runtime; the DOM is its first host. Elements
are bodies in one shared field context — they bend the field; its density bends
them back. A renderer-agnostic core computes the field; a host adapter binds it to a platform
(on the web, to the DOM). The visible canvas is one render surface, not the whole system.
Bodies
A body is any element you mark with data-body. Its value is
one or more force tokens, space-separated — they compose, so a single element
can attract and swirl at once. The engine re-reads each body's bounding box every frame,
so moving the element moves the force.
HTML
<a data-body="attract swirl" // forces compose, space-separated data-strength="0.9" data-range="320" data-color="#4da3ff" data-when="active" // gate: only acts while engaged data-feedback // opt into density write-back>link</a>
Common attributes — data-strength, data-range,
data-color, data-spin, data-angle — are read by the
forces that use them; each force ignores the ones it doesn't. See the
attribute reference for the full matrix.
Data attributes are the contract. You make semantic HTML participate in the field by adding
attributes to it — there is no special body element. <field-root> only hosts the
field; applyPattern()frozen · @fundamental-engine/dom and bindData()frozen · @fundamental-engine/dom adapt it to patterns and data; and the
<field-cell> frames lower down are just isolated single-force microscopes. The
system is the markup, not the components.
The shared field context
Bodies, particles, relationships, measurements, metrics, feedback, and overlays all share one
field context. The visible particle layer is a conserved pool (~130 at default density) drawn
on a full-viewport <canvas> — one render surface among several (SVG overlays
and DOM feedback are others). Particles are one agent type, not the whole substrate.
The conserved pool carries a central law: nothing is created from nothing —
matter is moved, captured, and released; a sink that swallows particles must later release
them, so the count stays constant. That is what makes the field feel physical rather than
decorative.
The platform layer
@fundamental-engine/core (the core) computes the field against plain data and stays renderer-agnostic.
@fundamental-engine/dom binds it to the DOM through six registries — measurement
(frame-stable geometry), state (typed element state), feedback
(CSS variables + thresholded events), relationships (the native href/aria/data
graph), visual bindings (visual ↔ semantic pairing), and overlays
(render layers) — all driven by one frame scheduler with explicit phases:
Reads happen in the read phase and writes in the write phase, so geometry reads never thrash
against DOM writes. Since the runtime-unification phase the platform is the default for
<field-root>; the core engine still renders the canvas. See the
platform layer for the registries, scheduler, and lint.
The DOM ⇄ field runtime binding
The rarest property of the system is a two-way binding between layout and a
physics runtime. Fundamental is not a particle background: those are one-way (a
canvas reacts to the mouse). Here the DOM is the runtime's geometry, and the runtime
writes back into layout and type — a shared field context, not a decorative layer. Two flows, on
the scheduler's read and write phases:
Per frame
// read phase — DOM → field (platform MeasurementRegistry, once per frame, per body)cx = (rect.left + rect.width / 2) * DPRcy = (rect.top + rect.height / 2) * DPR// write phase — field → DOM (platform FeedbackRegistry, per data-feedback body)element.style.setProperty('--d', density) // d ∈ [0, 1], canonical raw channel// (the field-namespaced --field-density is written too, same value)
Read (geometry in): any DOM change — reflow, transform, resize — is an
instantaneous change to the force geometry, so animating the DOM animates the runtime for free.
Write (feedback out): the field samples how much matter pooled on each
data-feedback body and writes it back as --d (with the field-namespaced
--field-density holding the same value).
Reciprocity & --field-density
Reciprocity is the payoff. Opt a body in with data-feedback and the FeedbackRegistry
eases the gathered density into a CSS custom property --d ∈ [0, 1] on the
element — the canonical raw density channel (its field-namespaced form --field-density
is written alongside it, same value). Your styles turn that number into anything — weight, glow,
scale, color:
CSS
.headline { /* the field writes the density channel; CSS turns density into weight + glow. --d is the canonical raw channel; --field-density is its field-namespaced form. */ font-variation-settings: 'wght' calc(380 + var(--field-density, 0) * 420); text-shadow: 0 0 calc(var(--field-density, 0) * 40px) rgba(77, 163, 255, var(--field-density, 0));}
The value is low-pass filtered (a ~0.2s time constant), so it eases rather than jitters.
This is the sanctioned way to make type react — words gain weight where matter collects.
(The engine never assembles particles into letterforms; words glow and grow, they are not
drawn from dots.)
Metrics, and the lanes that stay separate
Density is one channel. The field also computes higher-level metrics — attention,
priority, coherence, entropy — and writes each back as a --field-<metric> custom
property. A pattern declares which metrics it tracks; the runtime writes
them. Below, real elements carry a runtime token (data-body="attract") and a supplied
priority; applyPattern() writes it back as --field-priority,
and the CSS reads it — no canvas, no cursor needed:
Primary — ship the runtime--field-priority: 0.90
Secondary — write the guide--field-priority: 0.50
Later — tidy the changelog--field-priority: 0.20
data-body="attract" + supplied priority → applyPattern() →
--field-priority → this element
Each row supplies a different value, so the field's feedback is visible at a glance — the readout shows what the field actually wrote back.
The vocabulary works because each word lives in exactly one lane, and the lanes
never bleed into each other:
Conceptdescribes (priority, polarity, binding) — it is never executed.
Tokenexecutes (attract, gravity) — the only thing a data-body runs.
Metricmeasures (--field-priority, --field-attention) — state the field writes back, not a force.
Diagnosticexplains (potential, topology, causality) — it inspects behavior, it does not cause it.
Conditionactivates (data-when="active") — when a token is allowed to act.
So: a concept is not a token, a token is not a metric, and a diagnostic is not behavior. The
pattern model composes these lanes without ever inventing a new force.
The orthogonal axes
The vocabulary stays small by separating four independent dimensions:
Forces — what a body does to nearby matter (attract, swirl, gravity,
lens…). They compose. Reference →
Conditions — when a force acts, via data-when (active, fast,
slow, hot, cool, scrolling). Reference →
Formations — a single global bias applied to every particle at once
(ambient, wells, lanes, scatter, accretion). Reference →
Render modes — the same physics drawn differently. render values: dots,
trails, links, metaballs, voronoi, streamlines, flow, knockout, redshift, blackbody, depth.
Overlay readings (setOverlay): field-lines, force-vectors, energy and more;
setHeatmap() for the density heatmap. Diagnostic helpers you call on your own
canvas (draw*()): contours, potential, topology, inspector, causality, prediction.
Diagnostics →
The natural model: four fields
Fundamental does not copy physics into the interface. It translates the four
fundamental fields into interface behavior. Every interface already has priority, polarity,
binding, and transformation — physics already has a language for those relations.
Natural fields are conceptual. Engine primitives are translations. Canonical forces are designed
verbs. Natural fields are not tokens; tokens are translations. This reframe
changes nothing the particles do — it only gives each behavior a clean place in the hierarchy:
Fundamental keeps these categories separate on purpose. A reader should never have to guess whether
a word is a runtime token, a metric, or a diagnostic — the lane says so:
older naming (--d, --mass) — kept, but not the primary path
The four fields
Gravity
priority, convergence, hierarchy
the field of what matters — important bodies bend attention and gather related matter into stable wells.
Use for priority · ranking · search relevance · centrality · anchoring · settling · attention wells
Expressionsgravity
Electromagnetic
polarity, signal, field lines, flow
the field of difference and signal. Electric fields push; magnetic fields bend; fieldflow carries.
Use for polarity · contrast · signal · routing · state opposition · field lines · guided flow · activation
Expressionschargemagnetismpropagate
Strong
binding, cohesion, structure
the field of what holds together — groups, clusters, bonds, constraints, and durable local structure.
Use for grouping · relationship strength · clusters · bonds · constraints · local structure · material integrity
Expressionscrystallizealigncohesionpressurelink
Weak
transformation, decay, release
the field of change — fading, mutation, release, phase shift, expiration, and handoff.
Use for state change · release · decay · expiration · handoff · transformation · instability
Expressionsmorph
Electric fields push. Magnetic fields bend. Fieldflow carries. Do not make
magnetism.apply() follow magnetic field lines — field-aligned transport belongs to
fieldflow. And attract is not gravity; repel is not charge —
those are designed UI verbs.
Author each field — the data-attribute patterns
You express a field's translation with data attributes on semantic HTML, using
honest runtime tokens only. A token's field is its classification, not a separate API — there is no
<gravity> element, only data-body="gravity". Strong binding is the
clearest case: a native href (or data-field-relation /
data-field-target) is the relationship — the graph is discovered from the DOM,
not declared in code.
<!-- Gravity → priority: the element converges attention --><section data-body="gravity" data-field-role="source" data-strength="0.9"> <h2>What matters most</h2></section>
<!-- Electromagnetic → polarity / signal. Electric pushes, magnetic bends. --><button data-body="charge" data-spin="1" data-range="280">positive</button><button data-body="charge" data-spin="-1" data-range="280">negative</button><!-- magnetism bends matter along the field; fieldflow carries it along the lines -->
<!-- Strong → binding. A native link IS the relationship — no new API. --><p id="claim">The runtime is renderer-agnostic.</p><a href="#claim" data-field-relation="supports" data-field-target="#claim"> Evidence: the boundary test</a>
<!-- Weak → transformation / decay. Not a fake force — state that ages. --><article data-body="morph"> <!-- or: a memory metric that decays, or a pattern that releases over time --></article>
Live — gravity bodies carrying a supplied priority; applyPattern()frozen · @fundamental-engine/dom writes it back as --field-priority and the CSS reads
it (gravity's interface translation, running):
data-body="gravity" + supplied priority → applyPattern() →
--field-priority → this element
Each row supplies a different value, so the field's feedback is visible at a glance — the readout shows what the field actually wrote back.
Fields vs primitives vs forces
Every catalog token has a place. This grouping is read straight from the engine's classification:
One need, separated into lanes: the runtime tokens that execute it, the metrics it measures, and the diagnostics that reveal it.
Interface need
Natural field
Runtime tokens
Metrics
Diagnostics
Show importance
Gravity
gravityattract
masspriorityattention
potentialprediction
Route signal
Electromagnetic
chargepropagatefieldflowstream
signalstrength
field-linesforce-vectors
Show relationship
Strong + EM
linkcohesion
relation-strength
topologycausality
Keep things grouped
Strong
cohesioncrystallizealign
clusterdensity
topologyheatmap
Show change / decay
Weak
morphmemory
decayage
causalitycontours
Show flow through structure
EM + transport
fieldflowstream
flowvelocity
field-linesprediction
Show reading history
Memory (metric)
memory
attentionrecency
heatmapinspector
Diagnostics by field
Each diagnostic answers “which invisible relation am I revealing?” — each one ships, as an overlay reading or a draw*() helper rather than a render value (see them live). Diagnostics are inspection modes, not runtime tokens.
Diagnostic
Reveals
Field
Potential
wells and energy landscape
Gravity
Field lines
electromagnetic structure
Electromagnetic
Topology
binding and relationships
Strong
Causality
which primitive caused change
any
Prediction
near-future path
Gravity / flow
Contours
scalar fields
derived
Four fields. Many expressions. One DOM runtime. Every interface has priority, polarity,
binding, and transformation. Fundamental gives those relations a field model.
One element, revealed as a field participant
Everything above, on a single element, one layer at a time — the way the runtime sees it. The same
ordinary HTML gains a layer per step, from plain markup to a body the field measures, drives and
feeds back. Step through it; the feedback stage prints the value the field actually wrote.
1 / 9
Semantic DOM
Start with real, accessible HTML — a heading and a note. No field yet, nothing to undo. Whatever happens next, this stays selectable, focusable, and readable.
Each frame below is a live <field-cell> running one force — the
lightweight demo surface with its own small particle pool, not the page field. Move your
cursor through a frame to push the matter around.
Attractpulls matter into a central wellRepelcarves a clean void at the centreSwirlcircles matter without pulling inGravitya true 1/d² well, softened at the coreStreama constant current along one headingBuoyancya steady lift against the frameTetherholds matter at a fixed radius
These are posters, not the detector. A cell uses a simplified centre-of-frame model
so the motion reads at a glance. To fire a particle into the canonical force math
and check it against its law, open the Lab.
Force systems also carry a known canon of failure modes — jitter, oscillation, local minima,
tuning brittleness. Which of those the field damps by construction (seeded RNG, per-step
friction, anchored bodies so the DOM owns placement) and which it still owns for bodies the
engine actually drives is stated per mechanism in
stability-and-convergence.md.
Agents: particles, elements, events
A force doesn't only push particles — it produces an influence at a location,
and whatever sits there decides how to consume it. A particle consumes it as
motion (the lightest agent); a DOM element consumes it as a transform offset
with an anchor tether; an event sink consumes a density threshold by firing a
CustomEvent you can listen for. One influence, three kinds of consumer — so the
field can drive real app behaviour, not just pixels.
Fundamental calls this the Field Agent Consumption Model. A single DOM body can
play several roles at once — force source, density receiver, force target, and event host. Its
capture-hold-release behaviour (the sink token, data-absorb,
data-max, --load) is the Body Matter Interaction submodel —
sink is the token; absorb is concept language, not a runtime token.
Composites, not new code. Bigger ideas — blackhole, galaxy,
tornado — are presets that expand to several
co-located bodies, one primitive each. The engine never grows to add a preset.
Engine-stepped agents, tagged matter, and the scalar field
Three engine capabilities turn the swarm from a backdrop into a population you can program.
They live in the core, so they work under any renderer — and the
Three.js layer binds a mesh over each one.
Engine-stepped agents — addAgent(spec)experimental adds
a participant the integrator moves (vs an agent that samples the field and
integrates itself). It lives in the conserved pool, so it feels every force the swarm feels
— body forces and the particle-level hunt/align/cohesion
— and each step its report(p) fires so an external object (a mesh, a label) can
follow it. maxSpeed clamps it; it edge-bounces rather than wrapping; it's counted
by particleCount() but excluded from readParticles().
Matter tagging — a body stamps the matter it emits with data-species and
acts selectively via data-affects (a species set). Matter outside the set feels
no force and is skipped in the density sample, so several ecologies — pollen, seeds, spores —
share one field with no cross-talk. Tags are read once at scan, allocation-free per frame.
The scalar field — sampleScalar(x, y)experimental
returns the smooth diffused density ∈ [0, 1] at a point (the heatmap grid, bilinear-sampled).
Its gradient stays meaningful right at a source — unlike a nearest-body readout — so a
creature can forage by gradient, always reading which way is "more". Enable it with
createField({ heatmap: true }); it runs even under render: 'none'.
These extend the consumption model rather than adding forces: an agent is a particle the engine
drives, tagging scopes which matter a force sees, and the scalar is a read channel —
no new data-body verb. The FieldHandle reference has
the exact signatures.
Relationship to research
These concepts didn't only fall out of building the runtime. An external research framework —
Foundations of Computational Interaction (FCI) — reaches the same distinctions
from the other direction, and names Fundamental as a computational substrate for a
bounded subset of them. The two converged independently: projection and
coupling carry the same meaning in both, and both hold that a metric is not a
construct and that a claim may only rise to the level its evidence supports.
The relationship is bounded on purpose. Fundamental is one instrument the research can use, and
neither proves the other — running a field is not evidence that the account is
correct. FCI is a pre-empirical program; the research papers here
design the studies and make predictions rather than reporting results. The full concept
map lives in the repository's canonical docs.