API reference
API stability
Stable · 0.x contract
Pre-1.0, the surface evolves — new exports land freely and the check never fails on an addition. What the surface below is protected from is silent removal:
each symbol stays exported, from the same package, with the same kind. Enforced in CI by
pnpm check:api — 20 entries that fail the build if they disappear.
Removing one is allowed and expected; it just has to be deliberate, and it comes with a changelog
migration note.
@fundamental-engine/core;
the rest are @fundamental-engine/dom, @fundamental-engine/elements,
@fundamental-engine/react, @fundamental-engine/vanilla.
Stable — entry points & runtime
| Symbol | Package | What it is |
|---|---|---|
createField value frozen · @fundamental-engine/core | @fundamental-engine/core | host-required primitive — throws without opts.host (the renderer-agnostic door). |
compilePattern value frozen · @fundamental-engine/core | @fundamental-engine/core | pure FieldPattern → compiled plan (no DOM). |
compileRecipe value frozen · @fundamental-engine/core | @fundamental-engine/core | DEPRECATED alias of compilePattern (recipe → Pattern rename) — removed at 1.0; frozen until then. |
browserHost value frozen · @fundamental-engine/dom | @fundamental-engine/dom | the canonical DOM FieldHost for core.createField. |
createFieldPlatform value frozen · @fundamental-engine/dom | @fundamental-engine/dom | wires the six native-first registries on a root. |
applyPattern value frozen · @fundamental-engine/dom | @fundamental-engine/dom | applies a pattern to a live platform (compilePattern lives in core). |
applyRecipe value frozen · @fundamental-engine/dom | @fundamental-engine/dom | DEPRECATED alias of applyPattern (recipe → Pattern rename) — removed at 1.0; frozen until then. |
bindData value frozen · @fundamental-engine/dom | @fundamental-engine/dom | binds records → bodies; data drives the field. |
createField value frozen · @fundamental-engine/vanilla | @fundamental-engine/vanilla | the one imperative door — resolves the host from opts.host -> bounds (contained) -> browserHost (default). Frozen contract preserved: createField(canvas) with no host still auto-supplies browserHost; bounds/host are additive options. |
browserHost value frozen · @fundamental-engine/vanilla | @fundamental-engine/vanilla | re-export of the platform host for the no-framework path. |
FIELD_VERSION value frozen · @fundamental-engine/vanilla | @fundamental-engine/vanilla | re-export of the core engine-version constant (#584) — a named import off the authoring door, beside field.version on the handle. A missing named import aborts the whole ES module, so every door must carry it. |
FIELD_VERSION value frozen · @fundamental-engine/react | @fundamental-engine/react | re-export of the core engine-version constant (#584) — a named import off the React door. |
FIELD_VERSION value frozen · @fundamental-engine/elements | @fundamental-engine/elements | re-export of the core engine-version constant (#584) — a named import off the elements door, beside el.version on <field-root>. |
createField has two doors on purpose: the core primitive is renderer-agnostic
and host-required; @fundamental-engine/vanilla re-exports the host-bundled convenience so the
no-framework path stays one call. Both are protected.
Stable — types
| Type | Package | What it is |
|---|---|---|
FieldPattern type frozen · @fundamental-engine/core | @fundamental-engine/core | the pattern schema (recipes/schema.ts). |
FieldRecipe type frozen · @fundamental-engine/core | @fundamental-engine/core | DEPRECATED alias of FieldPattern (recipe → Pattern rename) — removed at 1.0; frozen until then. |
FieldHost type frozen · @fundamental-engine/core | @fundamental-engine/core | the renderer-agnostic host contract createField requires; browserHost implements it. |
FieldPlatform type frozen · @fundamental-engine/dom | @fundamental-engine/dom | the surface createFieldPlatform returns. |
Stable — elements & the body contract
| Surface | Package | What it is |
|---|---|---|
<field-root> element frozen · @fundamental-engine/elements | @fundamental-engine/elements | one background field per page; scans the document for [data-body]. |
<field-cell> element frozen · @fundamental-engine/elements | @fundamental-engine/elements | a scoped local field region. |
data-body attribute frozen · attribute contract | core BODY_SELECTOR | The body contract. "Every element is a body" via the data-body attribute on ordinary elements. |
There is no <field-body> element. Bodies are an attribute on ordinary
elements, not a tag — the body contract is the data-body attribute. The pre-rename
<forces-field> / <forces-cell> tags were removed in the hard
rename and are not available — there is no alias window.
Experimental — no guarantee
These carry no stability guarantee and may change shape or be removed in any release. Some
have exported building blocks today — those are shipped-but-unfrozen: present in the
package, but not part of the contract until promoted above.
Area Status Notes FieldHandle — full shape partial The entry points that return FieldHandle are frozen; the handle shape itself is not. New methods may be added in any patch. MinimalFieldHost + host capability model (hostCapabilities / HostCapabilities / defineHost) partial Shipped in @fundamental-engine/core (core/host.ts). MinimalFieldHost is the smallest surface a host must supply — root + viewport() (geometry) and raf()/cancelRaf() (time). FieldHost now `extends MinimalFieldHost` with every other member (scrollY / scrollHeight / reducedMotion / hidden / createCanvas / onResize / onScroll / onVisibility / onInput / onBodyEvent) marked OPTIONAL: absent capabilities degrade gracefully (scroll → 0, reduced-motion / hidden → false, subscriptions → no-op; a drawing mode needing createCanvas throws a clear error, signals-first never calls it). This WIDENS the frozen FieldHost type — every existing host (browserHost / containerHost / headlessHost / threeHost) still satisfies it; a new host only implements the four required members. hostCapabilities(host) → HostCapabilities inspects which optional lanes a host provides (host conformance — the third parity category beside API-surface + mathematical conformance). defineHost(minimal & partial) builds a full FieldHost with no-op subscription defaults. Additive exports (types + two functions); FieldHost stays frozen/exported (name + package unchanged). Swift/Kotlin ports are a batched follow-up. FieldHandle.particleCount() / .energy() partial Shipped in @fundamental-engine/core and proxied on <field-root>. Safe to use; signatures may refine before 1.0 as FieldPerf is designed. FieldHandle.readParticles(out) partial Shipped in @fundamental-engine/core. Copies live particle state into a caller-owned Float32Array (stride 5: x, y, z, heat, size, in CSS-pixel field coords; z is the optional depth lane, 0 in a flat field) and returns the count written = min(particleCount(), floor(out.length/5)). Zero-alloc, read-only; the render-agnostic swarm read-out @fundamental-engine/three's particle bridge consumes. Additive to the (unfrozen) handle shape; stride may widen further before 1.0. FieldHandle.readParticleIds(out) partial Shipped in @fundamental-engine/core. Copies each live particle's STABLE id into a caller-owned Uint32Array, parallel to readParticles (same pool order, same agent exclusion) so ids[i] belongs to the particle at stride offset i*5 there. Identity is what pooled particles otherwise lack: a host that seeds entities (wind-borne seeds, tagged motes) reads ids back each frame to track which is which and key its own opaque payload off them (engine carries identity, not payload). Particle.id added (optional, engine always sets it). Zero-alloc, read-only. Mirrored on vanilla / elements / three. Additive to the (unfrozen) handle. cssFeedbackSink partial Shipped in @fundamental-engine/core. The public name for the CSS-variable feedback adapter — the DOM write path (--d/--field-density/--load/--lit + field:lit/dim events) the default sink uses. Feedback is plain data first (FeedbackChannels); this is one adapter the DOM door (createField/vanilla/<field-root>) installs by default, and a non-DOM host (three FieldLayer) opts out by passing its own feedbackSink. Additive export; behavior identical to the historical default. FieldHandle.sample(x, y) partial Shipped in @fundamental-engine/core. Returns the net field force a still test particle would feel at (x, y) as { x, y } in field-pixel space — a thin wrapper over forceAt(bodies, forces, env). Pure, read-only, samplable at any resolution; the seam external visualizers (@fundamental-engine/three vectorField / streamlineTubes, mesh displacement) consume. Additive to the (unfrozen) handle shape; may gain a structure-only companion before 1.0. FieldHandle.sampleScalar(x, y) partial Shipped in @fundamental-engine/core. Returns the smooth diffused density scalar [0,1] at (x, y) — the heatmap grid, bilinear-sampled, so its gradient stays meaningful at a source (forage-by-gradient). Requires the heatmap layer (createField({ heatmap: true }) / setHeatmap(true)); returns 0 when off. Read-only, updated each frame incl. under render:none. Additive to the (unfrozen) handle shape. FieldHandle.sampleGradient(x, y) partial Shipped in @fundamental-engine/core. The analytic companion to sampleScalar: returns the gradient ∇ {x,y} (direction + steepness in 1/px) of the diffused density field at (x, y), pointing up-density. Computed from the same heatmap grid (central difference, normalized by the eased peak), so it stays non-degenerate at a source where a nearest-body density flattens to zero — the smooth cue reliable forage-/flee-by-gradient steers by. Requires the heatmap layer (createField({ heatmap: true }) / setHeatmap(true)); returns { x: 0, y: 0 } when off or empty. Pure, read-only, maintained under render:none. Mirrored on vanilla / elements / three. Additive to the (unfrozen) handle shape. FieldHandle.grid(name) partial Shipped in @fundamental-engine/core. Opens a named host-authorable ScalarGrid — the engine field-buffer primitive (the same one diffuse/memory/propagate run on) promoted to a public surface, for application fields the simulation composes with (a scent map, a wear/desire-path layer, a goal attractor). { sample(x,y), deposit(x,y,amount), gradient(x,y), decay(rate), clear() } in field px. Created on first access (allocating nothing until then), kept viewport-sized, advanced once per frame by its mode inferred from the name (wave… = wave scheme, memory… = slow decay, else diffuse). A same-named force shares the buffer; pick a distinct name to keep an authored field independent. ScalarGrid gained decay()/clear() (additive). Mirrored on vanilla / elements / three. Additive to the (unfrozen) handle shape. FieldHandle.on(type, cb) partial Shipped in @fundamental-engine/core. A host-agnostic discrete EVENT BUS: subscribe to occurrences (push, plain data, no DOM) instead of polling the continuous feedback channels — for non-DOM hosts (3D/native/headless) and clean gameplay triggers. Returns an unsubscribe fn. Events: captured / released — a sink body captured / let go of matter (rising / falling edge of accretion), { body, count }; enter / exit — another body crossed INTO / OUT OF a body's range, { body, other } (#441); met — two bodies' boxes touched, { a, b } (#441, rising edge). Detection is lazy (a type with no listener costs nothing); body-level proximity runs on the measure cadence. per-particle enter·exit + settle are a later slice. Distinct from the data-on CustomEvent bindings (DOM-only). Mirrored on vanilla / elements / three. Additive to the (unfrozen) handle shape. FieldHandle.addAgent(spec) partial Shipped in @fundamental-engine/core. Adds an engine-stepped agent — a participant the integrator MOVES (vs sample(), where you integrate yourself). It lives in the conserved particle pool, so it feels every force the swarm feels (body forces and the particle-level hunt/align/cohesion); each step its report(p) fires so an external transform (a THREE.Object3D, a label) follows it. spec: { x, y, z?, mass?, maxSpeed?, species?, report }. maxSpeed is a hard clamp; species lets tagged bodies (data-affects) steer it selectively; it edge-bounces rather than wrapping toroidally, and is counted by particleCount() but excluded from readParticles(). Returns { particle, remove() }. The creatures primitive @fundamental-engine/three's layer.addAgent binds a mesh over. Additive to the (unfrozen) handle shape. FieldHandle.addBody(spec) partial Shipped in @fundamental-engine/core. Adds a programmatic body (no DOM) from a spec — the sanctioned alternative to the [data-body] scan for a non-DOM host (a Three.js mesh, a native view): no fake document, no querySelectorAll duck-typing. spec: { tokens, identity?, strength?, range?, spin?, angle?, color?, rect:()=>{left,top,width,height}, data?, onFeedback? }. rect() is sampled each frame for the box in field px. The body CARRIES a data record (the Body-level analog of a particle atom — Field Agent Consumption Model) and takes per-body feedback (its channels demuxed from the global sink); it survives rescan. Returns BodyHandle { data, channels, remove() }. Mirrored on vanilla / elements / three (overloaded with the mesh form). Additive to the (unfrozen) handle shape; three FieldBodyRegistry collapse onto it is a follow-up. FieldBodyIdentity (first-class body identity) partial Shipped in @fundamental-engine/core. A stable, structured identity for every body — { id, namespace?, kind?, host? } — so query()/snapshot()/diff()/replay()/relationships reference bodies by identity, not object reference or display text. id is the stable primary key (unique in the field, constant for the body's life) and equals a reading/snapshot's existing top-level `id` (back-compat). Supply it via addBody({ identity }) (a bare string is shorthand for { id }) or the new createField({ identify }) resolver (derives an identity from a DOM element, called once per body); omitted ⇒ the engine derives a deterministic stable id (the element DOM id, else a monotonic `body-N` — never Math.random). Surfaced on FieldBodyReading.identity and FieldBodySnapshot.identity (both additive; top-level `id` unchanged). Additive to types + the (unfrozen) query/snapshot shapes; Swift/Kotlin ports are a follow-up. FieldHandle.scrollV() partial Shipped in @fundamental-engine/core; returns the engine's EMA scroll velocity in px/frame (refresh-rate dependent — reads roughly half on a 120 Hz display). Mirrored as --field-scroll-v on :root by the platform runtime. Signature stable; semantics (the unit may normalize to px/ms, EMA factor) may refine before 1.0. FieldOptions.policy / FieldHandle.policy / FieldHandle.setPolicy(p) partial Shipped in @fundamental-engine/core. Runtime FIELD POLICY — what THIS host/session/user/app PERMITS at runtime, a distinct lane from governance (what doctrine allows — static lint). FieldPolicy { allowBodyDataInSnapshots?, allowMotionProjection?, maxMotionBudget? (0..1), budgets?: Partial<FieldBudgets> }; FieldBudgets covers motion/force/attention/thermal/render/privacy/accessibility/agentRead (0..1). setPolicy REPLACES (not merges); field.policy reads a frozen copy ({} when unset). WIRED: the motion budget folds (via min) with reduced-motion + perf pressure into the effective motion allowance the integrator/easing path reads — reduced-motion always wins (accessibility can only lower motion, never raise it); the privacy budget (+ allowBodyDataInSnapshots) gates body data in snapshot(). Other budgets are declared-not-yet-enforced (carried for host/tooling introspection). Purely additive — a field with no policy behaves exactly as before. Mirrored on vanilla / three. Additive to the (unfrozen) handle + options. FieldHandle.forAgent(opts) / AgentCapability / AgentFieldView / FieldSnapshotOptions.profile partial Shipped in @fundamental-engine/core. The scoped, READ-ONLY surface a Software Agent uses to read the field safely — the safety layer over the query/snapshot substrate (agent-readable is NOT agent-writable). forAgent({ capabilities, redactions? }) returns an AgentFieldView facade exposing ONLY scoped query() / snapshot() (+ replay() only when read:replay is granted); it has NO mutation methods (no applyForce/addBody/setPolicy) — enforced by the facade's shape. AgentCapability = read:metrics | read:relationships | read:influences | read:snapshots | read:body-data | read:projections | read:diagnostics | read:replay; a cap set is an allow-list — a dimension not granted is stripped from every reading (tightens, never widens). redactions?: string[] strips dotted paths (body.data, host.user, metrics.temperature) after capability scoping. FieldSnapshotOptions.profile (SnapshotProfile = debug | agent | bug-report | public) is a concrete inclusion preset composed with the explicit include* flags + the FieldPolicy privacy budget, always resolving to the TIGHTEST (most private) result — a profile/agent-view can never widen past what policy allows. WIRED: caps scope query/snapshot dimensions; body.data withheld without read:body-data (and still gated by the privacy policy); profiles resolve tightest; the budgets.agentRead === 0 boundary closes the agent surface (most-restricted view). SEAM: the fractional 0<agentRead<1 gradient is declared-not-yet-enforced. Purely additive (types + one handle method + one snapshot option); Swift/Kotlin ports are a batched follow-up. Additive to the (unfrozen) handle + snapshot options. FieldHandle.focus(target, input?) / FieldHandle.focusState(opts?) / AgentCapability read:focus / AgentFieldView.focusState? / FocusState / FocusEntry / FocusEvent / metrics.salience / FOCUS_WELL Pattern partial Shipped in @fundamental-engine/core. The focus / attention substrate — a shared, source-tagged, decaying attention LEDGER so operator attention is an INPUT and an agent's is an OUTPUT over one channel distinguished only by `source`. focus(target: string | FieldBodyIdentity, { amount?=1, source?='system', halfLife?≈8s (env.t seconds), at?=env.t }) deposits decay-then-add per source (decay is temporal.freshness, the env.t clock — no Date.now); it always records (a string id is never "unknown"), retained identity-keyed until a body with that id appears, then binds. focusState({ limit?=8, threshold?=0.05, source? }) returns the ranked, thresholded, capped SHARP TIP (FocusEntry { target, identity, salience 0..1, sources: FocusSourceShare[], updatedAt }) — a few hundred bytes to push into an agent turn. The aggregate per-body `metrics.salience` rides query()/snapshot() (breadth + a diffable receipt) under the always-on base grant — an agent should see WHERE attention is; the per-source split (WHO) is gated by the new read:focus capability, which alone gates AgentFieldView.focusState? (the view stays READ-ONLY — agent writes route through the host relay field.focus(id, { source: 'agent' }), so agent-readable is still NOT agent-writable). The `focus` discrete event (FieldEventMap.focus, a flat FocusEvent) is the write-back channel + append-only provenance receipt; a mandatory coalescer keyOf branch keys it by (target, source) so operator + agent deposits in one frame both survive. A focus well (Body.focusMul, clamped ≤2×) at the integrator deepens a focused body's forces as its salience freshens (the field gathers where attention is) and relaxes as it goes stale; any unfocused body keeps the mul===1 fast path. Signals-first, runs under render:'none'. The FOCUS_WELL Pattern (attract + freshness decay + a static reduced-motion digest) ships in EXPERIMENTAL_PATTERNS — never the locked 64. On DOM custom elements focus/focusState are reached via el.handle (HTMLElement.focus stays the DOM/keyboard focus). Mirrored on vanilla (FieldField delegates). Additive to the (unfrozen) handle + agent view + event map + AgentCapability union; Swift/Kotlin ports are a batched follow-up. FieldOptions.ambientOrbit / FieldOptions.ambientWander partial Shipped in @fundamental-engine/core (Wallpaper Rule, #978). DECLARED the resting `ambient` formation's tangential swirl on attract (ambientOrbit, default 0.1) and its per-particle drift (ambientWander, default 1.0) — formerly hardcoded constants (0.1 / 1.0) in FORMATION_BY.ambient.preset, a content-independent "gray debt". The defaults reproduce the historical values, so the resting field is byte-identical; ambientOrbit:0 gives a purely radial resting attract. Applies to the ambient formation only (section formations keep their authored presets). Mirrored: FieldOptions (createField / vanilla / react), <field-root ambient-orbit> / <field-root ambient-wander> attributes. Additive to the (unfrozen) options. FieldOptions.heatCenter / redshiftObserver / depthFocal / heatmapFade partial Shipped in @fundamental-engine/core (Wallpaper Rule, #975). DECLARED four content-independent render reference points formerly painted into the draw path (a "gray debt"): heatCenter { x, y } — the cool→warm heat vignette center for dots/depth (viewport fractions, default { x: 0.5, y: 0.4 } = the old (W/2, H·0.4)); redshiftObserver { x, y } — the observer the redshift mode reads radial velocity against (default { x: 0.5, y: 0.5 } = (W/2, H/2)); depthFocal number — the depth camera focal length in CSS px (default 480 = the old FOCAL); heatmapFade { start, span } — the heatmap scroll-fade curve in viewports (default { start: 0.3, span: 0.85 } reproduces the old (1.15 - scrollY/H)/0.85). Every default reproduces the historical constant, so every render mode is byte-identical by default. createField-only options (not <field-root> attributes / not yet ported to Swift/Kotlin). Additive to the (unfrozen) options. FieldOptions.dprCap / FieldHandle.setDprCap(cap) partial Shipped in @fundamental-engine/core. Backing-store device-pixel-ratio ceiling (#410): the effective DPR is min(devicePixelRatio, dprCap), default 2. The dominant fill-rate lever — the ambient field is soft, so capping at ~1.5 buys ~1.8x headroom on retina for a small softening. setDprCap(cap) re-sizes immediately. Mirrored: FieldOptions.dprCap (createField / vanilla / react), <field-root dpr-cap> attribute (live), the setter on all planes. Additive to the (unfrozen) handle + options. render mode 'none' (signals-only engine) partial Shipped in @fundamental-engine/core (#297): createField({ render: 'none' }) runs the full simulation + feedback pipeline but never acquires a canvas context, never sizes the backing store (it stays 0×0), and never draws — the field exists purely as signals (--d, --load, --lit, capture events, scrollV). setRender FROM 'none' acquires the context lazily; setRender TO 'none' at runtime stops drawing but keeps an already-acquired context. Accepted by <field-root render="none">. Shipped-but-unfrozen; may refine before being added to the frozen list. QualityGovernor / field:quality-tier partial Tier detection (0-3) ships in @fundamental-engine/dom; the <field-root> runtime throttles its own tick cadence at tiers 2-3 and emits field:quality-tier. Engine-side degradation (render simplification, particle caps) is the embedder's to wire; unfrozen. performance budget (inspectBudget / DEFAULT_BUDGET / createFieldPerf) partial inspectBudget(), withinBudget(), BudgetFinding, and DEFAULT_BUDGET ship in @fundamental-engine/core. QualityGovernor covers adaptive tier detection. The FieldPerf frame-duration split now ships in @fundamental-engine/dom as createFieldPerf() — pure timing math lifted from the site DataConsole prototype (rolling delta window of 180, nearest-rank-floor percentiles, budget = median of the first 30 clean deltas, dropped = delta > budget×1.5, gaps > 500 ms skipped as discontinuities; callers feed rAF timestamps — no rAF of its own). The LoAF / long-task lane now ships too: opt in with createFieldPerf({ loaf: true }) and the meter attaches a feature-detected PerformanceObserver (long-animation-frame, falling back to longtask), exposing loafCount + tbtMs (Total Blocking Time, Σ max(0, duration−50)) on the same snapshot(); call dispose() to disconnect. Graceful no-op where unsupported, off by default (the meter stays pure without it). Unfrozen — option/snapshot shapes may refine before 1.0. advanced diagnostics partial DIAGNOSTICS / DIAGNOSTIC_LENS / draw* primitives ship today but are shipped-but-unfrozen until added here. visual recipe editor absent no editor UI; the authoring toolkit (compileRecipe/recipeAuthoring/validateRecipe) is the substrate to build one on. GPU / WebGPU backend planned a named direction (VisualBindingRegistry mentions WebGL); the eleven shipped drawing render modes are CPU/canvas (the twelfth, 'none', draws nothing). multi-root bridge absent no API for coordinating multiple <field-root> instances yet. AI evidence fields partial EVIDENCE_FIELD + the agent API ship as a substrate, but no packaged feature; unfrozen. custom render backends partial a custom backend is possible via opts.host, but there is no stable backend-registration API. withFlip() partial Shipped in @fundamental-engine/dom; a pure DOM FLIP reflow helper (measure → mutate → invert → release) extracted from the invisible-fields example runtimes (#295). No registry dependencies; honors prefers-reduced-motion (the mutation still runs). Unfrozen — the options shape may refine before 1.0. allocateAttention() partial Shipped in @fundamental-engine/core; a pure conserved-attention allocator (water-filling: Σw pinned to one finite budget, per-item cap defaulting to 1, capped excess re-flows, pinned items take exactly cap off the top) extracted from the Inbox example runtime (#296). Pure and deterministic — no DOM. Unfrozen — the item/options shapes may refine before 1.0. textBodies() partial Shipped in @fundamental-engine/dom — the Range-geometry slice of #257: samples a text element's rendered line/word boxes (document.createRange + getClientRects) into aria-hidden wall/shear boundary spans bound back to the source via data-field-visual-for (role representation), so the field flows around/along the words. Box geometry, not glyph contours — glyph-outline sampling is the planned next slice; callers re-annotate on resize. Unfrozen — the options/handle shapes may refine before 1.0. threadOverlay() partial Shipped in @fundamental-engine/dom; the hover-thread SVG overlay extracted from three hand-rolled copies in the example family (Evidence wireThreads/centerIn, Backlog, Dependencies). Geometry + classes only — one absolutely-positioned aria-hidden pointer-events:none SVG prepended into a host (viewBox from the host rect), one host-relative center-to-center cubic bezier per target (midpoint-y shape), --thread set from draw()'s color, .lit/.cited marks on the endpoints. NO event wiring (pages own hover semantics) and no layout observation (callers re-draw on layout changes). Unfrozen — shapes may refine before 1.0. applyRecipe renderless / extraMetrics options partial Shipped in @fundamental-engine/dom; the scoped invisible-field idiom the twelve example runtimes hand-spread (render: [] + metrics dedupe-append) lifted into ApplyRecipeOptions. Both derive an EFFECTIVE recipe copy inside applyRecipe — the caller's (possibly shared catalog) recipe object is never mutated; the returned handle's recipe/compiled reflect the effective one. Additive — existing call shapes unchanged. Unfrozen — option names may refine before 1.0. bindFieldNav() + classifyMetric() / lintInertFeedback partial Shipped in @fundamental-engine/dom; the navigation-chrome idiom the site hand-spread across ~12 surfaces (top nav, chapter rail, docs sidebar/outline/search, breadcrumbs, pagers, footer, filter rosters) lifted into bindFieldNav(root, recipe, { pin, visited, extraMetrics, reducedMotion }): runs a recipe signals-only (render: []) over the <a href> links, pins the current as the well (data-field-attention=1), marks caller-flagged visited links (data-field-memory=1 + a nav-visited class), and returns a teardown; reduced-motion → null (plain links). Paired guard: classifyMetric(name) splits a lane into computed / supplied-only / designed (COMPUTED_METRICS + SUPPLIED_ONLY_METRICS partition METRIC_KINDS), and the new lintInertFeedback rule (in lintPlatform) flags a feedback binding to a DESIGNED --field-<m> lane the host never supplies — declared but never written, the same silent-contract class as lintSinkFeedback. Unfrozen — option names + the designed/computed split may refine before 1.0. `screen` modifier (quiet zones) partial Shipped in @fundamental-engine/core (physics workover v0.3): a body with `screen` in data-body damps OTHER bodies' forces on matter inside its data-range — clamp(1 − S·(1 − d/r)², data-screen-min, 1), applied in the integrator force pass (smooth edge, no NaN at zero range, never global). Passported (truth mode: designed, class modifier) with a conformance scenario. The data-body attribute contract itself is frozen; this TOKEN and its data-screen-* attrs are unfrozen — data-screen-mode (inside/outside/behind) is planned and the attenuation curve may refine before 1.0. measured thermodynamics (--entropy / --coherence / --temperature) partial Shipped in @fundamental-engine/core (physics workover v0.3): per-body LOCAL measurements on data-feedback bodies — entropy = (1 − R)·min(1, s̄/1.5) with R = |Σv|/Σ|v| (velocity alignment), coherence = 1 − entropy, temperature = ½·meanHeat + ½·min(1, s̄²/9) — accumulated in the existing density pass (core/thermo.ts is the pure math), eased like --d, and written through both feedback sinks as the bare names --entropy/--coherence/--temperature. Distinct from the platform's inferred --field-entropy/--field-coherence lanes and from the --coherence palette COLOR cssTokens() sets on :root. Unfrozen — the formulas' reference constants and the FeedbackChannels fields may refine before 1.0. source budget (data-life / data-cap + the unbudgeted-source guard) partial Shipped in @fundamental-engine/core (physics workover v0.3): a class-[S] source body (spawn) must declare one of data-life / data-cap / data-budget / data-sink; otherwise the scanner warns in dev (naming the element) and applies the safe defaults data-life="300" / data-cap="120". data-life sets each emission's mortal age; data-cap clamps the emission rate to cap/life so the body's live population is bounded at ~cap. Conformance pins the bound. Unfrozen — data-budget/data-sink carry presence-only semantics today and may gain richer ones. weight primitives (logNormalize/weightToStrength) partial Shipped in @fundamental-engine/core (core/weights.ts): the page-weight → body-strength contract extracted from ~38 hand-rolled call sites in the example family. logNormalize(value, max) is the family's log "consensus" shape — ln(value+1)/ln(max+1), clamped 0..1; heavy tails compress, zero stays zero, value === max reads exactly 1 (bit-for-bit the pages' Math.log(x+1)/Math.log(max+1) for max > 0). logNormalizeAll(values) runs the set in one pass and returns the max for live re-normalization. weightToStrength(w) maps the 0..1 weight onto the attract-body data-strength range: 0.4 + w·1.6 → 0.4..2.0, with WEIGHT_STRENGTH_BASE/WEIGHT_STRENGTH_SPAN exported so the magic numbers have ONE definition (returns the number; callers .toFixed(2) at the attribute write). Pure and deterministic — no DOM, NaN-safe on degenerate inputs. Unfrozen — names and shapes may refine before 1.0. temporal kernels (imminence/freshness/retention/phase) + data-field-at partial Shipped in @fundamental-engine/core (core/temporal.ts): the world-time clock — pure, deterministic kernels extracted from the example family. imminence(at, now, horizon) log-ramps to 1 at T−0 (the calendar page); freshness(at, now, halfLife) is exponential newness, exactly 0.5 one half-life out (staleness = 1 − freshness; the backlog/inbox recency shape); retention(anchor, since, opts) is the Ebbinghaus curve with τ growing with anchor strength (the memory page); phase(now, period, offset) is cyclical 0..1 (consumer-less today, shipped for completeness). Riding on them in @fundamental-engine/dom: a declared data-field-at (ISO 8601 or epoch ms; half-life via data-field-halflife, default 7 days) GROUNDS the metric pipeline's recency lane in world time — without it, recency stays interaction-inferred. World time is the third clock, alongside simulation time (env.t/dt) and experiential time (the metric pipeline); see docs/canonical/time.md. Unfrozen — names, option shapes, and the attribute contract may refine before 1.0.
Compatibility rules
- Pre-1.0, the surface evolves. New exports, new optional fields and new patterns/modes land freely; the check never fails on an addition. Treat 0.x as pre-stable and pin to ~0.MINOR.
- What is protected is REMOVAL, not change. If a listed symbol stops being exported, stops being declared, or stops registering, CI fails — because a consumer would otherwise find out before we did.
- Removing or renaming a listed symbol is allowed and expected. Do it deliberately: update the list in the same change and record it in the CHANGELOG so consumers get a migration note.
- createField is listed in BOTH Fundamental (host-required primitive; throws without opts.host) and @fundamental-engine/vanilla (host-bundled convenience). Both doors are protected; the vanilla door must keep auto-supplying browserHost.
- Package ownership is part of what is protected: compilePattern / FieldPattern / FieldHost (and the deprecated aliases compileRecipe / FieldRecipe) are core; createFieldPlatform / applyPattern / bindData / FieldPlatform / browserHost (and the deprecated alias applyRecipe) are @fundamental-engine/dom; field-root / field-cell are @fundamental-engine/elements. Moving one between packages is a removal from the old package.
- Bodies are an ATTRIBUTE contract: [data-body] on ordinary elements is the authoring surface. There is no <field-body> tag and none will be introduced as the body mechanism.
- Anything not listed carries no guarantee at all — including diagnostics, agent and render-mode exports that happen to ship today. Absence from the list is not a promise of instability, only of silence.
Enforced. scripts/api-surface.ts (typechecked) locks every value and type;
scripts/check-api-surface.mjs locks the element tags and the data-body
contract; both run via pnpm check:api in CI. The full contract is in
docs/canonical/api-stability.md; publish order is in PUBLISHING.md.