API reference
Platform parity matrix
Which FieldHandle methods ship on each surface.
JS is the source of truth. @fundamental-engine/three delegates all 52 methods to
the wrapped engine — 52/52. Swift (50/52) and Kotlin (50/52)
are native ports, mirrored but currently trailing the newest additions (see the table below for
which methods and why). The cross-plane physics is identical (the 36-force conformance
suite asserts it) regardless of API-surface gaps. This matrix tracks API surface parity.
FieldHandle method surface — 52/52 Three.js,
50/52 Swift, 50/52 Kotlin.
Conformance: the force math matches, asserted by the shared cross-plane golden (the 36-force
suite). Production readiness: packaging, platform polish, and docs — this is
Preview and differs by host. Native ports are at FieldHandle API parity, but
packaging, platform polish, and production-readiness differ by host.
FieldHandle coverage
| Method |
Three.js
52/52 (100%) |
Swift
50/52 (96%) |
Kotlin
50/52 (96%) | Notes |
|---|---|---|---|---|
scan | ✓ | ✓ | ✓ | |
rescan | ✓ | ✓ | ✓ | alias of scan |
setAccent | ✓ | ✓ | ✓ | |
setPalette | ✓ | ✓ | ✓ | |
setFormation | ✓ | ✓ | ✓ | |
setWaveStyle | ✓ | ✓ | ✓ | |
setWaveCenter | ✓ | ✓ | ✓ | |
setSeparation | ✓ | ✓ | ✓ | Kotlin fun setSeparation(Float) coexists with var separation via @JvmName on the property |
setAttention | ✓ | ✓ | ✓ | |
setCausality | ✓ | ✓ | ✓ | |
setHeatmap | ✓ | ✓ | ✓ | |
setDprCap | ✓ | ✓ | ✓ | Three.js delegates but DPR is renderer-owned; native stores in FieldOptions / FieldHandle |
setQualityTier | ✓ | ✓ | ✓ | |
setRender | ✓ | ✓ | ✓ | render surface is host-owned on native + Three.js; Kotlin host reads renderMode |
setOverlay | ✓ | ✓ | ✓ | use threeBackend() for Three.js diagnostic overlays |
setBackground | ✓ | ✓ | ✓ | web canvas clearing; native stores in FieldOptions.background |
threads | ✓ | ✓ | ✓ | visual layer — glowing connector lines |
burst | ✓ | ✓ | ✓ | |
flowTo | ✓ | ✓ | ✓ | |
clearFlow | ✓ | ✓ | ✓ | |
seed | ✓ | ✓ | ✓ | |
addAgent | ✓ | ✓ | ✓ | AgentSpec / AgentHandle; Kotlin init-block wires tickAgents() via onAfterTick; Swift uses UUID-keyed agent list |
addBody | ✓ | ✓ | ✓ | |
addEdge | ✓ | ✓ | ✓ | |
readEdges | ✓ | ✓ | ✓ | |
addField | ✓ | ✓ | ✓ | |
sampleField | ✓ | ✓ | ✓ | |
atomAt | ✓ | ✓ | ✓ | |
focusAt | ✓ | ✓ | ✓ | |
clearFocus | ✓ | ✓ | ✓ | |
particleCount | ✓ | ✓ | ✓ | |
energy | ✓ | ✓ | ✓ | |
query | ✓ | ✓ | ✓ | structured read of bodies/metrics/relationships; the influences/projections/dimensions lanes stay empty on the ports until an accumulator/registry/dimension lane lands (JS field names kept) |
snapshot | ✓ | ✓ | ✓ | point-in-time field capture; body data withheld by default (tightest-wins profile) |
diff | ✓ | ✓ | ✓ | pure comparison of two snapshots — added/removed/changed bodies, metric deltas, relationship + formation changes |
replay | ✓ | ✓ | ✓ | causal replay over a snapshot pair; force-attributed steps empty-for-now on the ports (no impulse accumulator yet) |
forAgent | ✓ | ✓ | ✓ | capability-scoped, redacted, read-only agent view (agent-readable is not agent-writable) |
sample | ✓ | ✓ | ✓ | force-probe at world point; Swift delegates to forceAt(); Kotlin delegates to controller.sample() → forceAt() |
sampleScalar | ✓ | ✓ | ✓ | |
sampleGradient | ✓ | ✓ | ✓ | |
grid | ✓ | ✓ | ✓ | named ScalarGrid — lazy get/create, same grid as env.grid in the force system |
on | ✓ | ✓ | ✓ | UUID-keyed subscriber bus; FieldEvent enum (tick/bodyAdd/bodyRemove + captured/released, the JS bus keys; #1020); returns Subscription |
readParticles | ✓ | ✓ | ✓ | |
readParticleIds | ✓ | ✓ | ✓ | Particle.id (stable, auto-incremented by FieldStore.add); fills an Int buffer |
readParticleChannels | ✓ | ✓ | ✓ | samples named ScalarGrids at each particle position; stride = names.count/size |
registerOverlay | ✓ | ✓ | ✓ | OverlayRenderer protocol/interface; host reads overlayRegistry and calls render() each frame |
scrollV | ✓ | ✓ | ✓ | DOM/UIKit scroll velocity; Kotlin: scrollV() + feedScrollV(); Three.js returns 0 in headless |
setVisible | ✓ | ✓ | ✓ | |
destroy | ✓ | ✓ | ✓ | |
setPolicy | ✓ | ✓ | ✓ | runtime FieldPolicy (motion/privacy budgets); mirrored on all three planes |
focus | ✓ | — | — | focus/attention substrate (EXPERIMENTAL, shipped 0.9.4) — operator/agent attention write; Swift + Kotlin ports are a batched follow-up |
focusState | ✓ | — | — | focus/attention substrate (EXPERIMENTAL, shipped 0.9.4) — the ranked, thresholded attention read; Swift + Kotlin ports are a batched follow-up |
Platform notes
All three platforms briefly shipped the full 44-method surface as of June 2026 (#822 Swift, #823 Kotlin) — the JS-side surface has grown since (now 52 methods; see the live counts above), and the ports have not fully caught up. Some methods behave differently across platforms by design:
- Three.js — 52/52, host-context notes
-
@fundamental-engine/threedelegates all 52 methods to the wrappedFieldHandle. Some are contextually different in a 3D scene:setRender/setOverlay/setBackgroundare owned by the Three.js renderer — usethreeBackend()for diagnostic overlays.scrollVreturns 0 (no DOM scroll).scan/rescanare called internally; in Three.js register bodies vialayer.addBody(mesh, spec). - sample — force probe
-
Swift delegates to
forceAt(bodies:forces:env:at:)inStreamlines.swift. Kotlin delegates toFieldController.sample()which calls the sameforceAt()function used by the streamlines overlay. Both are safe to call between ticks. - on — event bus
-
Both platforms use a UUID-keyed subscriber registry (not closure equality, which doesn't hold
across captures).
FieldEvent.tickfires after every simulation step via anonAfterTickhook.bodyAdd/bodyRemovefire from theaddBody/ remove path. Returns aSubscription; call.cancel()to unsubscribe. - addAgent — autonomous consumers
-
AgentSpeccarries apositionclosure sampled each tick;onInfluencereceives the net force vector at that point. Agents are ticked via the sameonAfterTickhook as the event bus, after each force step. - readParticleIds / readParticleChannels
-
Particle.idis assigned byFieldStore.add()via a monotonically increasing counter — stable for the lifetime of the pool.readParticleChannelssamples each namedScalarGridat each particle's position; the output stride equals the number of channel names. - registerOverlay
-
OverlayRendereris a protocol (Swift) / interface (Kotlin). TheFieldHandlekeeps a[String: OverlayRenderer]registry the platform host frame loop reads to callrender()after the underlay draw each frame. No deep engine integration — pure registry delegation.