MeasurementRegistryreadFrame-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.
Concepts
@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.
@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>.
One shared loop with explicit, ordered phases, so reads never thrash against writes:
discover register/unregister bodies, relationships, and visual bindings (structure changes) read measure the DOM once — geometry + visibility. The only place layout is read. compute run field/force/agent logic against the immutable snapshot. No DOM. state fold results into the StateRegistry + thresholds. Internal truth, no DOM writes. write flush state to CSS variables, data attributes, and thresholded events. 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.
MeasurementRegistryreadFrame-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.
StateRegistrystateTyped, observable element state (number / boolean / string / vector2) — internal truth, distinct from ARIA. CSS and JS both consume it; only feedback writes it.
FeedbackRegistrywriteThe write phase: held state → CSS custom properties (continuous) + thresholded, debounced events (discrete, with hysteresis). Writes --field-* vars and field:* events.
RelationshipRegistrydiscoverThe 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.
VisualBindingRegistrydiscoverBinds 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.
OverlayRegistryrenderRelationship 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.
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-feedback | a 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-unwritten | an 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-unregistered | an element holds field state but was never registered for measurement |
overlay-without-links | a relationship overlay with no relationships behind it |
feedback-non-css-var | a binding that writes ARIA/attributes instead of a --field-* variable |
measurement-off-phase | a layout read recorded outside the read phase |
visual-orphan / visual-not-hidden | accessibility hazards from VisualBindingRegistry |