Matter Field: one authored field for GPU (TSL) + CPU consumers - is this a useful contract?

This mostly came out of building Objekt 62

It has a ~40 km procedural dune world rendered through Three.js/TSL, while gameplay needs immediate CPU queries against that same terrain for collision, placement, vehicles, slope, etc. I ended up with the terrain math authored once against a small adapter surface so JS gameplay and the TSL renderer weren’t maintaining two different versions of the world.

That made me wonder if the pattern itself was useful enough to extract, and also where the assumptions in it actually stop being true.

Matter Field is that experiment.

The deal is deliberately narrow: author a closed-form field once against a restricted math vocabulary, then materialize that same definition as JS f64 for gameplay/Workers/server use, native TSL for WebGPU/WebGL2, and dual numbers for gradients.

The other part is that I don’t assume shared source means CPU/GPU float equality. It doesn’t. So there is an f32 diagnostic path plus hardware-scoped drift receipts over named samples instead of a “parity = true” claim.

Restriction is basically the point. No arbitrary JS, no data-dependent branches inside the field, no pretending every operation has portable-enough behavior just because both sides can spell it.

TypeGPU is obviously the closest overlap. I tried that path too and there is an optional TypeGPU integration in the repo. For a WebGPU-first app doing broader compute/resources/pipelines, TypeGPU may just be the better answer. Matter Field is testing a narrower contract around fields specifically: native TSL + WebGL2 fallback, immediate JS queries, generic CPU gradients, a restricted numeric surface, and measured drift.

I also don’t think this beats the normal approaches universally. If a texture/heightmap can be the authority, great. If the formula is small enough that maintaining CPU/GPU twins isn’t painful, also fine. Readback can be fine if latency doesn’t matter.

The case I’m interested in is more specifically: a procedural field evaluated densely on the GPU, queried sparsely and synchronously by gameplay, and complicated/changeable enough that having two independent implementations starts to feel sketchy.

Current examples are terrain/contact, swell/buoyancy, wind/forces, and a moving queryable volume. The receipts are finite regression evidence on named hardware, not global bounds, and they don’t claim the analytic field equals the rendered triangles between vertices either.

Matter Field source code

Live examples / assays

Objekt 62, which is where the architecture came from:

What I’d value from this forum specifically: is this actually a useful contract once you’ve lived with this problem, or is one of the standard approaches basically always the better engineering choice? Also very interested in what field/op/workload immediately makes the restriction not worth it.

1 Like

The restriction is the right instinct, but I think it is guarding the wrong thing. In my experience the vocabulary is rarely what breaks parity — argument magnitude is, and a 40 km world is exactly where that starts to bite.

Where the drift actually lives

f32 has a 24-bit mantissa, so the spacing between representable values at x is about x · 2⁻²³. At the edge of a 40 km world that is roughly 5 mm — fine for terrain height. But procedural fields do not evaluate at x. They evaluate at x · frequency, once per octave. An octave at 512× on a coordinate of 40 000 lands at ~2·10⁷, where f32 spacing is about 2 units of the noise domain. On the CPU in f64 that same octave is exact to well past the point of caring.

So the divergence between your two consumers is not a uniform epsilon you can quote once. It is near zero for the low octaves, garbage for the high ones, and it grows with distance from the origin. A drift receipt gathered near the origin will look excellent and tell you nothing about the far tiles — which is where the vehicle will end up.

The standard fix is the boring one: rebase coordinates before the field sees them, so the field is always evaluated near the origin and the large offset never enters the transcendental. Worth deciding whether that rebasing lives inside your contract or outside it, because it changes what “the same definition” means on the two sides.

The second source, which no vocabulary restriction can remove

Both GLSL and WGSL specify the transcendentals — sin, cos, exp, pow, inverseSqrt — with a bounded ULP error, not a fixed result. Two vendors can both be conformant and disagree, and they do. Any field built on trig noise inherits that disagreement no matter how narrow the vocabulary is, and it is the one term you cannot make deterministic by authoring it once. Marking expressions precise on GLSL backends stops contraction into FMA, which removes a different and smaller part of the gap; it does nothing for the transcendental tolerance.

That is not an argument against the contract. It is an argument for the diagnostic path being the product and the parity claim being the thing you never make — which is roughly what you already concluded, and I think you underrate how much of the value sits there.

The metric I would change

Drift over named samples measures the field where you mostly do not care. For collision and placement, the quantity that decides whether the game is broken is the sign disagreement rate in the band where |f| is within one voxel of zero — how often the CPU says inside and the GPU says outside on the same point. Mean absolute drift can be tiny while that number is bad, because error concentrates exactly where the field cancels, and that is the isosurface.

The gradient path deserves its own number for the same reason. Differentiating amplifies the discrepancy roughly in proportion to 1/|∇f|, so on the flat pans of a dune field — where the slope query matters most for vehicles — the two normals can disagree by more than the values ever do.

On the contract question you actually asked

I think it earns its keep exactly while the field stays closed-form, and that condition tends to expire. The moment there is an erosion pass, a hand-painted mask, or a baked heightmap tile, the definition stops being authorable once and the adapter surface has to grow a sampler — at which point you are back to two versions of the world, only now with an abstraction in between.

So the question I would want answered before extracting it is not whether the pattern is useful. It is where the baked data is going to live when it arrives.

Two things I would genuinely like to know:

  • What is the sign-disagreement rate in the near-zero band, at a far tile rather than near the origin?
  • Is coordinate rebasing inside the contract or outside it?

I work on browser 3D commercially — parametric and field-driven rather than authored geometry, so this is the corner I live in. The closest public thing I have on this exact subject is a graded gyroid lattice ray-marched from a distance field: no mesh, no texture, geometry straight out of a function. It is linked from my thread in Jobs (#93376) rather than here, since the forum will not take the host from a new-ish account.

1 Like

To preface, I do not think I described the implementation well enough.

The terrain in Objekt 62 is a continuous planet-space height function. The GPU samples it densely to build a moving terrain clipmap. Gameplay samples the same definition directly where it needs height or slope. The rendered triangles are not collision authority because they change with camera position and LOD and only approximate the field between vertices.

Matter Field is what I extracted from that pattern. It keeps the field composable while allowing the same definition to become a JS evaluator, TSL graph, gradient evaluator or diagnostic target.

I should probably stop using parity as shorthand. The claim is shared definition with measured drift, not identical results from every consumer.

On your sign-disagreement question, I do not have that number yet. The existing receipts are not limited to the origin. They use 300 points across the full +/- 20 km domain, including boundary and far-coordinate cases. But they measure value drift rather than contact decisions.

This is a heightfield rather than an SDF, so there is not a voxel band in quite the same sense. I think the equivalent test would be disagreement in the sign of y - h(x,z) for points inside the actual collision or placement tolerance. That would be a useful receipt to add.

Rebasing is outside the contract today. The caller supplies the coordinates. Objekt 62 rebases the render scene, but reconstructs the absolute planet coordinate before evaluating the authoritative terrain field. Subtracting an arbitrary origin inside the non-periodic field would change the terrain.

The high-frequency cosmetic layer is different. Its waves are defined as integer periods of a 64 metre tile. I reduce the large wind-space origin modulo 64 in CPU f64, then combine that with the bounded local coordinate on the GPU. A whole-tile shift only adds an integer multiple of 2 pi, so it preserves the wave while avoiding the world-scale argument. This is outside the gameplay field and does not affect collision or displacement.

One other clarification is that the current terrain field does not have a 512x octave. It uses two or three octaves with 2.03 lacunarity, so its highest relative octave multiplier is 2.03² = 4.1209. It also does not use sin, cos, pow or exp. Your argument-magnitude point is still valid, but it describes a failure mode the contract should declare and test rather than the current field inputs.

I also agree that baked data is an important boundary. If erosion, painted masks or heightmap data enter the system, I think they need to become an explicit versioned sampler rather than being presented as part of the same closed-form field.

Given that distinction, would you still put the coordinate transformation itself inside the contract, or would you leave it outside and make the domain, period and coordinate policy part of the field declaration? Also interested if you see another failure mode in the low-frequency restricted tree that I am still missing.