Concepts

The platform layer

@fundamental-engine/core (the core) computes field behavior, renderer-agnostic. @fundamental-engine/platform binds it to the DOM — measurement, state, feedback, relationships, visual bindings, and overlays, all driven by one frame scheduler. It is the DOM participation layer; canvas is one render surface.

Package hierarchy

@fundamental-engine/core              renderer-agnostic field, force, particle, metric, recipe, diagnostic logic
@fundamental-engine/platform          DOM participation: the six registries + scheduler + lintPlatform()
@fundamental-engine/elements          native web components — <field-root> + the [data-body] contract
@fundamental-engine/react             the React adapter over the same contracts
@fundamental-engine/vanilla           the FieldField class for plain TypeScript

Dependency direction is strict: elements → platform → core, react → platform → core, vanilla → core. Since the runtime-unification phase the platform runtime is the default for <field-root>.

The frame scheduler

One shared loop with explicit, ordered phases, so reads never thrash against writes:

  1. 1 discover register/unregister bodies, relationships, and visual bindings (structure changes)
  2. 2 read measure the DOM once — geometry + visibility. The only place layout is read.
  3. 3 compute run field/force/agent logic against the immutable snapshot. No DOM.
  4. 4 state fold results into the StateRegistry + thresholds. Internal truth, no DOM writes.
  5. 5 write flush state to CSS variables, data attributes, and thresholded events.
  6. 6 render draw overlays, field lines, and heatmaps from the registries (read-only).

An off-phase layout read is caught by the scheduler's guard. createFieldPlatform(root) frozen · @fundamental-engine/platform wires measure → read and flush → write by default and exposes .on(phase, fn) for the rest. The platform also exports browserHost() frozen · @fundamental-engine/platform — the canonical DOM host the renderer-agnostic core requires.

The six registries

MeasurementRegistryread

Frame-stable geometry: every registered element measured once per frame into an immutable snapshot, so nothing calls getBoundingClientRect ad hoc and thrashes layout. A getRect override covers closed Shadow roots.

StateRegistrystate

Typed, observable element state (number / boolean / string / vector2) — internal truth, distinct from ARIA. CSS and JS both consume it; only feedback writes it.

FeedbackRegistrywrite

The write phase: held state → CSS custom properties (continuous) + thresholded, debounced events (discrete, with hysteresis). Writes --field-* vars and field:* events.

RelationshipRegistrydiscover

The DOM is a tree; interfaces are graphs. Normalizes native links (href#id, label[for], aria-controls/describedby/labelledby/flowto, data-field-relation) into one typed graph.

VisualBindingRegistrydiscover

Binds an expressive visual layer (SVG/Canvas/WebGL) to its semantic DOM source without duplicating meaning. Author declaratively with data-field-visual-for + data-field-visual-role, then platform.visuals.scan(root) — idempotent, navigation-safe. aria-hidden unless it carries independent meaning; lint flags orphan + un-hidden visuals. Binds authored visuals; does not extract glyph outlines.

OverlayRegistryrender

Relationship lines, field lines, debug layers. Render layers only — they read from measurement + relationships and produce geometry to draw. They never own relationships or mutate physics.

Platform lint

lintPlatform(platform) surfaces the quiet failures of a field system — it reads, never mutates:

relation-target-missing[data-field-relation] with a missing or unresolvable target
sink-without-feedbacka sink body carries data-absorb/data-max but no data-feedback — it captures matter yet never writes --load back, so anything reading the meter stays empty
feedback-vars-unwrittenan inline style reads var(--d…), var(--load…) or var(--field-…) on a body that never opted into data-feedback — that channel is never written for it
state-unregisteredan element holds field state but was never registered for measurement
overlay-without-linksa relationship overlay with no relationships behind it
feedback-non-css-vara binding that writes ARIA/attributes instead of a --field-* variable
measurement-off-phasea layout read recorded outside the read phase
visual-orphan / visual-not-hiddenaccessibility hazards from VisualBindingRegistry
See it whole. The Reading Field demo exercises all six registries on the scheduler over ordinary prose. The deep reference is the canonical platform-architecture doc; the API surface is under API reference.