API reference
Platform parity matrix
Which FieldHandle methods ship on each surface.
JS is the source of truth. @fundamental-engine/three delegates all 44 methods to the
wrapped engine — 44/44. Swift and Kotlin are native ports at full parity — 44/44. The cross-plane
physics is identical (the 36-force conformance suite asserts it). This matrix tracks
API surface parity.
Three distinct axes — don't conflate them. API parity (this matrix): the
FieldHandle method surface exists — 44/44/44.
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.
How to read this table. A green chip means the method ships on that platform. A grey chip
means it is not yet implemented (or not applicable for that platform — see the note column for
context). The counts below each platform header show what fraction is covered.
FieldHandle coverage
| Method |
Three.js
50/50 (100%) |
Swift
49/50 (98%) |
Kotlin
50/50 (100%) | 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) |
projections | ✓ | — | ✓ | projection registry — JS core + web + Kotlin (agent-json + callback surfaces); Swift port in progress; css/dom/svg surfaces are web-first |
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/particleCapture/supernova); 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 | ✓ | ✓ | ✓ |
Platform notes
All three platforms ship 44/44 as of June 2026 (#822 Swift, #823 Kotlin). Some methods behave differently across platforms by design:
- Three.js — 44/44, host-context notes
-
@fundamental-engine/threedelegates all 44 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.