Guides

The Kotlin port

The same reciprocal field, native on Android. It is a port of the JS engine, not a reinterpretation: the package layout, the API surface, and the physics mirror the npm packages — and the Swift port — one-to-one. The two planes are held to the same shared cross-plane conformance golden (JS / Swift / Kotlin). Where they diverge, it is a bug or a documented platform difference.

Status: broad, not complete. The Kotlin port is conformance-checked against the JS engine (the same cross-plane golden the Swift port runs, which pins the six canonical deterministic forces; the other forces are tested behaviorally on the plane) and ships 59 of the 61 callable FieldHandle members any plane declares, against JS's 52 — generated from the port source, not typed here. The big catch-up landed in #823. See Implementations for the full surface-by-surface status, and Parity below for platform-specific notes.

Install

Nothing is on Maven Central yet — consume it from source. The modules live in the repo's android/ directory; the recommended door is :fundamental-compose.

settings.gradle.kts
// settings.gradle.kts — depend on the modules from source.
// Preview: nothing is published to Maven Central yet, so include the repo's
// android/ build as a composite (a maven coordinate door comes later).
includeBuild("../fundamental-engine/android") {
  dependencySubstitution {
    substitute(module("com.fundamental:fundamental-compose"))
      .using(project(":fundamental-compose"))
  }
}

// build.gradle.kts (your app module)
dependencies {
  implementation("com.fundamental:fundamental-compose")
}

To see it run before wiring it into your own app, build the bundled desktop Field Lab: ./gradlew :lab:run from android/ — every engine pillar as a live, interactive scene.

Modules — one-to-one with npm

Four libraries, each the mirror of an npm package (and of a Swift target). fundamental-core imports zero platform code, exactly as @fundamental-engine/core imports zero DOM.

Gradle modulenpm equivalentWhat it is
:fundamental-core@fundamental-engine/coreThe pure physics — particles, bodies, the 37-force catalog, the integrator, formations, reactions. Zero platform imports.
:fundamental-platform@fundamental-engine/domThe six-phase frame scheduler (discover → read → compute → state → write → render) and the six registries.
:fundamental-android@fundamental-engine/vanillaThe imperative host — FieldFieldView, a plain Android View / Canvas with no Compose dependency.
:fundamental-compose@fundamental-engine/reactThe declarative adapter — FieldView, Modifier.fieldBody(), the FieldHandle exposed to descendants.

Compose — FieldView + Modifier.fieldBody()

The declarative door. FieldView mounts the field surface; any composable inside becomes a body with the Modifier.fieldBody(...) modifier — the Kotlin analogue of the data-body attribute (and of SwiftUI .fieldBody()). It accepts the same token vocabulary and tuning as the web.

Compose
import com.fundamental.compose.FieldView
import com.fundamental.compose.fieldBody

@Composable
fun ContentScreen() {
  Box {
    // the field surface — accepts the same tuning as the web / Swift hosts
    FieldView(accent = Color(0xFF4DA3FF), renderMode = RenderMode.DOTS)

    // any composable becomes a body — the Kotlin analogue of [data-body]
    Text(
      "pull me",
      modifier = Modifier.fieldBody(tokens = listOf("attract"), strength = 1.2f, range = 150f),
    )
  }
}

View / Canvas — the imperative host

For non-Compose apps, FieldFieldView is a plain Android View that runs the field on a Canvas and drives its own frame loop — the analogue of new FieldField() / mountField() from the vanilla JS API.

Kotlin
import com.fundamental.android.FieldFieldView

// imperative host — a plain Android View / Canvas, no Compose required.
val field = FieldFieldView(context)
parent.addView(field)                 // mounts and starts the frame loop
field.addBody(BodySpec(tokens = listOf("attract"), strength = 1.2f, range = 150f))
field.burst(tap.x, tap.y)             // a CGPoint analogue from a MotionEvent
// the loop stops automatically on detach from the window

Parity

The Kotlin port ships 59 callable FieldHandle members against the JS engine's 52 — the widest of the three, because it adds the host-feed methods a Compose/View host needs (attach, tick, feedScrollV, feedReducedMotion, pause, resume) on top of the shared surface. It is still missing the focus/attention substrate (focus / focusState), a batched follow-up. The core force set, the scheduler and registries, formations, the FieldHandle public API (relationship edges included), and all six advanced methods (addAgent, sample, on, readParticleIds, readParticleChannels, registerOverlay) are implemented and pass the cross-plane conformance gate.

The full method-by-method breakdown is on the parity matrix page. Some methods behave differently by platform design: Compose and the View/Canvas host own the draw loop, so setRender and setOverlay are read by the host rather than driving a native canvas directly. scrollV is fed by the platform via feedScrollV(). These platform differences are documented in the parity matrix notes.

The deeper architectural story — why the same engine can run on the web, in WebGL, headless, and natively on Apple platforms and Android behind one host seam — is the same one the Swift port tells. The seam itself is the platform layer; the runtime handle is the FieldHandle.