@threenative/core API

Every public function and class in @threenative/core. The small vanilla Three.js game runtime foundation.

On this page

Generated from the engine's capability manifest: 121 functions and 47 classes. Each entry is the exported signature, what it is for, and a working example. Start with the package overview.

addInSlices

function · import { addInSlices } from "@threenative/core"

Attach hundreds of built objects to the scene in slices, presenting a frame between each.

ts
async function addInSlices<T>( objects: Iterable<T>, add: (object: T, index: number) => void, options: IAddInSlicesOptions =

Use it to

  • add hundreds of built objects to the scene without one multi-second frame
  • stream a detail tier in behind a loading curtain without the page looking hung

Example

ts
const report = await addInSlices(objects, (object) => ctx.add(object), {
  onProgress: ({ added, total }) => setProgress(added / total),
  while: () => generation.live,
});

Good to know

  • the objects, and where each one goes, stay the game's; this decides only when each joins the graph
  • input order is the attach order and cannot be changed
  • a false while stops the run and is reported as stopped, never thrown

Options

  • sliceSize defaults to 256; marker: false silences the TN_ADD_SLICES line, not the report

addSpan

function · import { addSpan } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function addSpan(id: SpanId, ms: number): void

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

aerodynamicCoefficients

function · import { aerodynamicCoefficients } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
function aerodynamicCoefficients( alpha: number, flaps = 0, gear = 0, brakes = 0, ):

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

afterPhysics

function · import { afterPhysics } from "@threenative/core"

Register work that reads a body or camera after physics has moved it and before this frame draws. The engine owns the phase ordering; a callback cannot be misplaced by plugin-array order.

ts
function afterPhysics( context: IAfterPhysicsContext, callback: AfterPhysicsCallback, ): () => void

Use it to

  • read a body after physics has moved it
  • place a camera or aim from the solved character transform

Example

ts
afterPhysics(ctx, (dt) => camera.position.copy(player.mesh.position));

Good to know

  • register from a scene context; callbacks are cleared when that scene exits

aircraftMass

function · import { aircraftMass } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
function aircraftMass(state: IFlightState, airframe: IAircraftAirframe): number

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

airDensity

function · import { airDensity } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
function airDensity(y: number): number

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

alwaysRender

function · import { alwaysRender } from "@threenative/core"

Keep an object drawn even when the render camera cannot resolve it. The engine's projected-size gate is on by default: an object whose world bounding sphere projects to fewer than 0.5 raster pixels in the camera about to render it is not submitted, per camera. Mark the player's own cockpit, a nameplate, a quest marker, or anything a game never wants to pop out of the frame. alwaysRender(object, false) removes the marker. Camera-attached objects and shadow casters are already kept, and the number of marked objects is reported beside the cull in the TN_PROJECTION window rather than hidden. The threshold itself is renderer.minimumProjectedPixels — a larger number cuts more aggressively, false leaves every object drawn while still measuring.

ts
function alwaysRender(object: Object3D, enabled = true): void

Use it to

  • keep a small object drawn when the engine would skip it as too far to resolve
  • stop my cockpit, marker or player model popping out at distance
  • a tiny object disappeared at range and I need it always visible
  • widen or narrow the projected-size cull with a named threshold

Example

ts
import { alwaysRender } from "@threenative/core";
alwaysRender(ctx.camera.children[0]); // a camera-attached cockpit stays drawn

Good to know

  • the marker is per object and is reported as exemptMarked in the projection window
  • renderer.minimumProjectedPixels: false leaves the scene drawn and keeps the measurement on
  • the marker is per object and survives scene rebuilds only as long as the object does
  • disabling the gate (renderer.minimumProjectedPixels: false) keeps its measurement on

Options

  • renderer.minimumProjectedPixels sets the projected-pixel threshold, default 0.5

AnimationPlayer

class · import { AnimationPlayer } from "@threenative/core"

Play a skinned or sprite animation from game code. A locomotion clip's playback rate is matched to the ground the body actually covers, so feet do not skate or spin — on by default, strideSync: false to keep the authored rate, and player.stride reports the measurement either way. Name the body a game moves as strideRoot when the rig is a child of it. Clips authored in place — every ActorX and Unreal export, every Mixamo "in place" clip, every stock animal pack — are matched too: their stride is read off the ground a planted foot sweeps, and stride.inPlace says so.

ts
class AnimationPlayer

Use it to

  • play an animation on a character
  • switch a character between idle and attack clips
  • stop a walking character's feet from sliding or spinning
  • match a walk or run cycle to how fast a character is moving
  • match an in-place walk cycle with no root motion to the body's speed
  • find out what speed an animation clip was authored for

Example

ts
const player = new AnimationPlayer({ clips, root: rig, strideRoot: body });

Good to know

  • name the body a game moves as strideRoot when the animated rig is a child of it
  • a one-shot clip always plays at its authored rate; the matched rate re-times loops only
  • the matched rate is held inside 0.15x-3x; a speed outside what the clip's own stride supports is clamped, and stride.rate says so

Options

  • strideSync controls whether the matched rate is applied while stride is still measured

Atmosphere

class · import { Atmosphere } from "@threenative/core"

Own the compute lifetime and expose only parameter-driven atmosphere nodes. The class deliberately creates no mesh, material, or scene light. A template chooses all of those, and the same object remains useful when a game supplies a completely different look.

ts
class Atmosphere extends Group implements IComputeDriven

Use it to

  • render a sunrise that changes as time and place change
  • add distance haze from the depth of a scene pass

Example

ts
const atmosphere = new Atmosphere({ rayleigh, mie, ozone, planetRadius, atmosphereRadius });
ctx.add(atmosphere);

Good to know

  • supply rayleigh, mie, ozone, planetRadius, and atmosphereRadius; there is no Earth fallback
  • the game creates the sky object, surface, and sun from the returned nodes

Also found by

  • bright sky saturated green platforms

AtmosphereLuts

class · import { AtmosphereLuts } from "@threenative/core"

Own the transmittance, multi-scattering, and sky-view compute lookup textures.

ts
class AtmosphereLuts

Use it to

  • bake the three atmosphere LUTs once before a game shows its world

Example

ts
const luts = new AtmosphereLuts({ rayleigh, mie, ozone, planetRadius, atmosphereRadius });

Good to know

  • supply all physical parameters; this class creates no scene appearance

attachToBone

function · import { attachToBone } from "@threenative/core"

Attach a game-owned object to a named skeleton bone.

ts
function attachToBone(root: Object3D, boneName: string, child: Object3D): Object3D

Use it to

  • put a weapon in a character's hand
  • hold a rifle in a character's right hand
  • add an enemy that patrols the level, chases the player when it sees them, and holds a rifle in its right hand
  • attach an accessory to a skinned model
  • equip crew with swords and pistols
  • put a sword or pistol in a crew member's hand

Example

ts
import { attachToBone } from "@threenative/core";
attachToBone(character, "RightHand", rifle);

Good to know

  • when a request is to hold or attach a weapon to a hand, import and call attachToBone from @threenative/core; do not manually parent, position, or rotate the rifle
  • if a stock visual has no skeleton, add a portable Three.js Bone named RightHand under the character, then call attachToBone; do not replace the helper with manual parenting

attitudeAxes

function · import { attitudeAxes } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
function attitudeAxes(state: IFlightState): IFlightAxes

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

AudioBus

class · import { AudioBus } from "@threenative/core"

Route effects through a named audio bus.

ts
class AudioBus

Use it to

  • play a sound effect with a volume bus
  • mute or adjust a category of game audio
  • keep a gunshot audible at 20 metres by tuning positional falloff
  • hold a loud mix below clipping with a compressor on the bus
  • play cannon, wave, and ship sound effects

Example

ts
const effects = new AudioBus({ camera });

Good to know

  • create buses before playing clips and dispose them with the game
  • refDistance and rolloffFactor tune positional falloff and apply to playAt only
  • compressor takes threshold, knee, ratio, attack and release from the game and takes effect on the bus sum

Replaces new Audio(.

baseGeometryOf

function · import { baseGeometryOf } from "@threenative/core"

The full-detail geometry of a mesh the loader gave an automatic LOD chain. Selection swaps mesh.geometry, so a ray test or a collision body built from the current geometry would change with the camera. Framework picking and gameplay collide against this instead: the authored LOD0, which never changes as the camera moves.

ts
function baseGeometryOf(mesh: Mesh): BufferGeometry

Use it to

  • collide or ray-test the authored geometry of a mesh whose render detail changes with distance

Example

ts
const geometry = baseGeometryOf(mesh);

beginSpan

function · import { beginSpan } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function beginSpan(id: SpanId): void

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

Billboard3D

class · import { Billboard3D } from "@threenative/core"

Face a game-owned object toward a perspective or orthographic camera.

ts
class Billboard3D

Use it to

  • keep a world-space marker or nameplate facing the camera
  • billboard a tree, label, or effect under a rotated parent

Example

ts
const billboard = new Billboard3D(label, { camera });
billboard.update();

Good to know

  • call update from the owning scene; no global scene scan is installed
  • orthographic cameras use their forward direction, and lockAxis restricts world rotation

boneContact

function · import { boneContact } from "@threenative/core"

Measure whether a named bone reaches the object it is supposed to be touching, in metres.

ts
function boneContact( root: Object3D, boneName: string, target: Object3D, ): IBoneContactReport

Use it to

  • check that a seated character's hands reach the keyboard
  • check that a character's hips meet the chair it is sitting on
  • turn "the character is not touching the prop" into a number a scenario can assert

Example

ts
import { boneContact } from "@threenative/core";
const contact = boneContact(worker, "hand_r", keyboard);

Good to know

  • this walks the target's vertices; call it on a check or a debug sample, not every frame

boneLengthDeviations

function · import { boneLengthDeviations } from "@threenative/core"

Compare a rig's bone distances now against a captured snapshot and name every bone that moved.

ts
function boneLengthDeviations( root: Object3D, bind: IBoneLengthSnapshot, options: IBoneLengthDeviationsOptions =

Use it to

  • find out why a skinned character renders deformed
  • check that an animated pose keeps the skeleton rigid
  • name the bone that breaks a posed skeleton without taking a screenshot

Example

ts
import { boneLengths, boneLengthDeviations } from "@threenative/core";
const report = boneLengthDeviations(character, boneLengths(character));
if (!report.rigid && report.worst !== null) console.log(report.worst.bone, report.worst.ratio);

Good to know

  • a rigid skeleton preserves every parent→child distance under any pose; a named bone is a defect with an address
  • this is a diagnostic — it reports numbers and names; it moves nothing and decides no appearance

boneLengths

function · import { boneLengths } from "@threenative/core"

Measure a rig's parent→child bone distances, in world space, as it stands right now.

ts
function boneLengths(root: Object3D): IBoneLengthSnapshot

Use it to

  • capture a rig's bind-pose bone lengths before any clip plays
  • measure a skeleton for the bone-length invariance check

Example

ts
import { boneLengths } from "@threenative/core";
const baseline = boneLengths(character);

Good to know

  • the baseline and the later comparison must come from the same rig under the same ancestor transform, so a uniform scale cancels

bvhIntersectFirstHit

function · import { bvhIntersectFirstHit } from "@threenative/core"

Pack a selected static scene into TSL storage nodes for an upstream BVH ray query.

ts
bvhIntersectFirstHit = upstream.bvhIntersectFirstHit

Use it to

  • trace thousands of scene rays inside a TSL kernel
  • build a contact-occlusion or visibility query over loaded meshes

Example

ts
const bvh = ctx.add(new GPUSceneBVH(ctx.scene, { include: (object) => object.userData.traceable === true }));

Good to know

  • call rebuild() after a scene transform or geometry change; the snapshot is static by default
  • rebuild() is an explicit CPU SAH build proportional to selected triangles; process() is a no-op, and the game pays upstream traversal per shader ray

CameraShake

class · import { CameraShake } from "@threenative/core"

Produce a game-authored camera shake offset for a template-owned camera rig.

ts
class CameraShake

Use it to

  • add a hit, explosion, or landing shake to a camera
  • compose a transient camera offset after camera damping

Example

ts
const shake = new CameraShake({ amplitude, rotationAmplitude, frequency, decay, curve });

Good to know

  • amplitude, rotationAmplitude, frequency, decay, and curve are required game choices
  • update returns an offset and never writes to a camera

CanvasLayer

class · import { CanvasLayer } from "@threenative/core"

Manage a camera or screen-facing canvas layer.

ts
class CanvasLayer

Use it to

  • place a HUD layer above the Three.js scene
  • attach a canvas layer to a camera

Example

ts
const hud = new CanvasLayer(ctx.viewport);

captureMouse

function · import { captureMouse } from "@threenative/core"

Lock the pointer to the game's surface: the capture every first-person mouse look needs. A browser grants capture only from a user gesture, so call it from a click or a pointerdown handler. A relative binding such as look: { pointerRelative: true } already requests capture on the first canvas click; this is the same request for a game that starts capture from another named gesture, and ctx.input.captureMouse() is the map's own way to ask.

ts
function captureMouse(target: EventTarget): Promise<void> | undefined

Use it to

  • lock the mouse pointer so first-person mouse look keeps the cursor out of the way
  • stop the cursor leaving the window in the middle of a turn

Example

ts
canvas.addEventListener("click", () => captureMouse(canvas));

Good to know

  • the browser grants capture only from a user gesture and a refusal is reported, never swallowed
  • a relative binding requests capture on canvas click unless captureOnClick: false opts out

clipBoneCoverage

function · import { clipBoneCoverage } from "@threenative/core"

Report which bones of a character a clip does not drive.

ts
function clipBoneCoverage(root: Object3D, clip: AnimationClip): IClipCoverageReport

Use it to

  • find out why a character keeps the previous animation's hand shape
  • check how much of a rig a clip covers before shipping it

Example

ts
import { clipBoneCoverage } from "@threenative/core";
const coverage = clipBoneCoverage(character, clip);

Good to know

  • a track that binds nothing counts as driving nothing

clipPoseError

function · import { clipPoseError } from "@threenative/core"

Score a retargeted clip against the source it came from, per bone, in degrees.

ts
function clipPoseError( subject: IClipPoseSubject, reference: IClipPoseSubject, options: IClipPoseErrorOptions =

Use it to

  • find out why a retargeted animation looks wrong on a character
  • tell a fixed retarget from one that merely moved
  • catch a retarget that rolled every limb about its own axis

Example

ts
import { clipPoseError } from "@threenative/core";
const report = clipPoseError({ root: rig, clip: retargeted }, { root: source, clip: original });

Good to know

  • each bone is compared as a whole quaternion relative to its own rig's bind pose, so the two rigs never have to share a bind convention; a bone-direction check reports zero on the roll this catches
  • both rigs must face the same way in world space, and both are driven and then restored to the transforms they arrived with

Options

  • bones maps one rig's bone names onto the other's when they differ; samples sets how many poses are compared

clipTrackBindings

function · import { clipTrackBindings } from "@threenative/core"

Report which of a clip's tracks bind to nothing on a character.

ts
function clipTrackBindings(root: Object3D, clip: AnimationClip): IClipBindingReport

Use it to

  • find out why a character plays its bind pose instead of the animation
  • check that a loaded clip actually drives the model it was written for

Example

ts
import { clipTrackBindings } from "@threenative/core";
const bindings = clipTrackBindings(character, clip);

Good to know

  • the reason is Three.js's own, captured off its console hook so a scenario's console assertions stay clean

ClusteredBatch

class · import { ClusteredBatch } from "@threenative/core"

Draw many copies of one over-detailed body, each at the detail its own distance earns. InstancedBatch collapses repeated props into one draw and gives every copy the same triangles. This gives a copy two hundred metres away a coarser cut than one twelve metres away, and still submits one draw per distance group rather than one per copy — which is what makes four hundred scanned boulders affordable. The shape, the surface and every transform stay the game's.

ts
class ClusteredBatch

Use it to

  • draw hundreds of copies of a scanned or sculpted body without hundreds of draw calls
  • stop distant copies of a dense prop from costing their full triangle count

Example

ts
const boulders = new ClusteredBatch({ geometry, material, table });
boulders.place({ position: [x, y, z], rotation: [0, angle, 0] });
boulders.build({ name: "boulders", parent: ctx.scene });
// in the scene's update, before the render:
boulders.update(ctx.camera, ctx.renderer.domElement.height);

Good to know

  • the body's geometry must carry a cluster table from assets.models.virtual
  • every copy is placed before build(); place() after build() throws

Options

  • distanceRatio sets how wide one distance group is, default 1.25
  • errorPixels sets the screen-space error budget, default 1 pixel

ClusteredMesh

class · import { ClusteredMesh } from "@threenative/core"

Draw a model too detailed for the screen to resolve, without submitting the part it cannot. This is on, and a game does not call it. Any primitive of 65,536 triangles or more bakes to a cluster DAG in the asset pipeline, the loader returns a ClusteredMesh for it, and the engine takes the cut every frame before it renders. Each frame the mesh submits one draw holding only the clusters whose error projects to fewer than errorPixels screen pixels; a mesh nothing has cut yet draws in full, so the worst case is an ordinary Mesh. Geometry, surface and every appearance parameter stay the game's.

ts
class ClusteredMesh extends Mesh

Use it to

  • draw a model too detailed for the screen to resolve
  • import a scanned or sculpted mesh of millions of triangles and still hold the frame
  • stop a dense rock face or terrain body from costing its full triangle count up close

Example

ts
// Nothing to call: the loader returns one of these and the engine cuts it every frame.
const face = await ctx.assets.model("quarry-face.glb");
ctx.scene.add(face.scene);

Good to know

  • the bake happens in the asset pipeline, never at run time — there is no runtime flag
  • the payload costs about 3-4x a baked primitive's bytes; assets.models.virtual: "none" opts out and minSourceTriangles moves the 65,536 line
  • needs a perspective camera; a screen-space error has no meaning without one
  • one shadow cut is chosen at load and does not follow a shadow camera

Options

  • errorPixels sets the screen-space error budget, default 1 pixel
  • recutDistance sets how far the camera moves before the cut is retaken, default a thousandth of the mesh's radius

ComputeDrivenRegistry

class · import { ComputeDrivenRegistry } from "@threenative/core"

Register game-owned IComputeDriven objects with the shared compute lifetime.

ts
class ComputeDrivenRegistry

Use it to

  • use IComputeDriven for a cloth, fluid, boid, or other GPU simulation
  • run a cloth or fluid simulation with ordered GPU passes
  • keep IComputeDriven kernels warm before the first visible frame

Example

ts
const registry = new ComputeDrivenRegistry(); registry.add(cloth, ctx.renderer.raw);

Good to know

  • add the object through ctx.add so it attaches, dispatches, and releases with its scene

counterDeviceOf

function · import { counterDeviceOf } from "@threenative/core"

Count the frame's host-boundary crossings and the bytes it writes into GPU buffers, off unless TN_FRAME_SPANS asks for them. On the frame budget's own window as counters, so a crossing count and a millisecond split describe the same frames.

ts
function counterDeviceOf(raw: unknown): unknown

Use it to

  • decide whether a CPU-bound frame is paying for the V8-to-host boundary
  • measure how many bytes a frame writes into GPU buffers, and how many commands it issues

Example

ts
const counters = FrameCounters.install(counterDeviceOf(renderer.raw));

Good to know

  • counts command-encoder and queue methods only; mapAsync and the presentation path are named, not folded in
  • gpuBytes is queue.writeBuffer exactly, so it reconciles against a driver; texture uploads are not included
  • jsAllocBytes needs performance.memory and stays absent where the platform lacks it

createAssetLoader

function · import { createAssetLoader } from "@threenative/core"

Create the portable asset loader a scene also receives as ctx.assets.

ts
function createAssetLoader(options: IAssetLoaderOptions =

Use it to

  • preload models, textures, or audio before a scene enters
  • load assets from a nonstandard base path or a compiled asset manifest

Example

ts
const assets = createAssetLoader({ basePath: "/assets" });
const rock = await assets.texture("rock.png");

Good to know

  • reuse the loader handed to scenes as ctx.assets instead of building parallel caches

Also found by

  • different props in each area
  • first playable screen external assets

createPipelineCensus

function · import { createPipelineCensus } from "@threenative/core"

Read the renderer's bounded, versioned pipeline capture in a diagnostic or playtest tool.

ts
function createPipelineCensus(options: IPipelineCensusOptions): PipelineCensus

Use it to

  • inspect shader and pipeline creation work during a real launch
  • correlate pipeline creation with material, object, pass, and shader identities

Example

ts
const capture = game.runtime.pipelineCensus?.();
// The game normally reaches this through `runtime.pipelineCensus`; direct construction exists
// for renderer adapters and contract tests, not for gameplay.

Good to know

  • the capture is bounded and incomplete when the backend cannot expose an observation

createRandom

function · import { createRandom } from "@threenative/core"

Create a deterministic random source for portable gameplay.

ts
function createRandom(seed?: number): IRandom

Use it to

  • get a seeded deterministic random number generator — the same mulberry32 a game would hand-roll
  • seed enemy patrol choices
  • reproduce the same procedural level in a playtest

Example

ts
const random = createRandom(42);

Good to know

  • use the returned source instead of Math.random for replayable behavior

Replaces Math.random(.

createReplayDriver

function · import { createReplayDriver } from "@threenative/core"

Record or replay deterministic game input and state.

ts
function createReplayDriver( recording: Recording, target: EventTarget, pointerTarget = target, )

Use it to

  • reproduce a gameplay bug from recorded input
  • run a deterministic replay in a playtest

Example

ts
const driver = createReplayDriver(recording, ctx.renderer.domElement);

Good to know

  • use a seeded random source and fixed-step simulation for meaningful replays

Also found by

  • fixed seed fixed-step simulation

Daylight

class · import { Daylight } from "@threenative/core"

An outdoor daylight rig: physical sky, one sun with open-world shadows that follow the eye, hemisphere fill, sky-coloured haze and the AgX tone curve. Every value is the game's.

ts
class Daylight extends Group implements IComputeDriven

Use it to

  • daytime sky, sun and shadows for a large outdoor map
  • distant terrain should fade into the sky instead of a coloured wall
  • match a Blender look-dev scene's sun, sky and exposure in the game

Example

ts
const daylight = new Daylight({ follow: ctx.camera, sunDirection, sunColor, sunIntensity: 4, shadowExtents: [24, 96, 320], sky: { turbidity: 3, rayleigh: 1.4, mieCoefficient: 0.004, mieDirectionalG: 0.8 }, fill: { sky, ground, intensity: 1.1 }, haze: { color: horizon, density: 0.0011 }, exposure: 2 ** -0.6, skySize: 1600 });
ctx.add(daylight);

Good to know

  • every value is required; there is no default sun, sky, haze or exposure
  • skySize must keep the sky box's corners inside the camera's far plane
  • shadowExtents follow VirtualShadowNode: half-widths, finest first, strictly increasing

Options

  • sky uniforms stay live on daylight.sky; the light and fill are daylight.sun and daylight.fill

debugFlag

function · import { debugFlag } from "@threenative/core"

Read a debug switch from the URL, or from TN_DEBUG_* in the environment on a native launch.

ts
function debugFlag(name: string): boolean

Use it to

  • read a debug toggle from the URL or an environment variable

Example

ts
import { debugFlag } from "@threenative/core";
if (debugFlag("freeCam")) camera.flyMode = true;

Good to know

  • a name in camelCase becomes UPPER_SNAKE: debugFlag("freeCam") reads ?freeCam or TN_DEBUG_FREE_CAM
  • 0 and false are off, so a saved URL cannot turn a switch back on

defineGame

function · import { defineGame } from "@threenative/core"

Define the portable game entry shared by web and native.

ts
function defineGame<TState extends Record<string, unknown>, TPhysics = undefined>( config: IGameConfig<TState, TPhysics>, ): IGame<TState, TPhysics>

Use it to

  • start a ThreeNative game from src/game.ts
  • register physics and gameplay plugins
  • let the player zoom the camera with a wheel, pinch, or gamepad axis
  • frame a camera behind the player

Example

ts
const game = defineGame({ input: { zoom: { scroll: true, pinch: true } }, scenes: { Play }, start: "Play" });

Good to know

  • keep DOM and React mounting in src/main.ts
  • bind scroll or pinch and read the intent with ctx.input.axis(name); do not add a window wheel listener
  • scroll: true uses the DOM wheel sign on browser and native: negative deltaY toward the user is positive intent

Also found by

  • firing line nearest target crosshair
  • third-person camera
  • restart the run without a page reload
  • field of view while aiming

describeSceneShape

function · import { describeSceneShape } from "@threenative/core"

Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as TN_SCENE_WARNING, and silent on a scene that is honestly GPU-bound.

ts
function describeSceneShape( window: IFrameBudgetWindow, cull: IRenderCameraCullReport | undefined, ): ISceneShape | undefined

Use it to

  • find out whether a slow frame is the scene's shape or the device
  • tell an authoring agent what to reduce before it promises a merge

Example

ts
defineGame({ display: { maxFps: 60 }, scenes: { Play } });

Good to know

  • the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant
  • no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence
  • the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking

describeSceneWarning

function · import { describeSceneWarning } from "@threenative/core"

Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as TN_SCENE_WARNING, and silent on a scene that is honestly GPU-bound.

ts
function describeSceneWarning(warning: ISceneWarning): string

Use it to

  • find out whether a slow frame is the scene's shape or the device
  • tell an authoring agent what to reduce before it promises a merge

Example

ts
defineGame({ display: { maxFps: 60 }, scenes: { Play } });

Good to know

  • the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant
  • no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence
  • the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking

directionalTransmittance

function · import { directionalTransmittance } from "@threenative/core"

Approximate direct transmittance for a ray leaving the game surface.

ts
function directionalTransmittance( parameters: IAtmosphereParameters | IResolvedAtmosphereParameters, direction: Vector3, ): Vector3

Use it to

  • colour a game-owned sun from atmosphere extinction

Example

ts
const transmittance = directionalTransmittance(parameters, sunDirection);

Good to know

  • pass a non-zero direction; coefficients and radii come from the game

directionFromSolarPosition

function · import { directionFromSolarPosition } from "@threenative/core"

Convert solar elevation and azimuth degrees into a normalized Three.js direction.

ts
function directionFromSolarPosition(elevation: number, azimuth: number): Vector3

Use it to

  • aim a template's sun from solarPosition output

Example

ts
const direction = directionFromSolarPosition(sun.elevation, sun.azimuth);

Good to know

  • elevation and azimuth must be finite degrees

displayPeriodMs

function · import { displayPeriodMs } from "@threenative/core"

Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as TN_SCENE_WARNING, and silent on a scene that is honestly GPU-bound.

ts
function displayPeriodMs( declaredTargetFps: number | undefined, ):

Use it to

  • find out whether a slow frame is the scene's shape or the device
  • tell an authoring agent what to reduce before it promises a merge

Example

ts
defineGame({ display: { maxFps: 60 }, scenes: { Play } });

Good to know

  • the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant
  • no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence
  • the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking

endSpan

function · import { endSpan } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function endSpan(id: SpanId): void

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

ensureVelocityOutput

function · import { ensureVelocityOutput } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
function ensureVelocityOutput(pass: IVelocityRenderPass): MRTNode

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • add a velocity target before a temporal stage consumes a scene pass

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it
  • call only when a temporal stage is active

exposeDebug

function · import { exposeDebug } from "@threenative/core"

Publish one game object under __THREENATIVE__.debug for a capture script or the console.

ts
function exposeDebug(name: string, value: unknown): void

Use it to

  • expose a game object to a capture script or the console in dev builds

Example

ts
import { exposeDebug } from "@threenative/core";
exposeDebug("player", player);
// then from the console: __THREENATIVE__.debug.player

Good to know

  • development builds only; a production build publishes nothing

FlightModel

class · import { FlightModel } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
class FlightModel<TState extends IFlightState = IFlightState>

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

FluidField2D

class · import { FluidField2D } from "@threenative/core"

Simulate a deterministic 2D velocity-and-dye field on the GPU while exposing its data to game-owned rendering.

ts
class FluidField2D extends Group

Use it to

  • simulate smoke, fire, fog, wind, or fluid response on a grid
  • inject a touch, pointer, or gameplay impulse into a fluid field
  • sample fluid dye or velocity in a game-owned render node
  • simulate ocean currents and wind affecting a sailing ship
  • simulate ocean fluid dynamics and currents that affect a ship

Example

ts
const field = new FluidField2D({ resolution: 256, pressureIterations: 20 });
ctx.add(field);
field.splat({ x: 0.5, y: 0.5 }, { x: 0.2, y: 0 }, 1);

Good to know

  • add the field through ctx.add so renderer attachment, fixed-step dispatch, and release are automatic
  • dye and velocity are numeric samplers; appearance stays in the game's src/render/ code
  • the conformance sample measures mean absolute velocity divergence at 0.001732 after four 32² steps with pressureIterations 2, below the 0.0025 threshold

Options

  • pressureIterations, viscosity, vorticity, and splatRadius tune the solver without changing its pass order

FluidParticles3D

class · import { FluidParticles3D } from "@threenative/core"

Simulate liquid as GPU particles (pour, splash, dam break, waterfall) and expose positions, a density volume and a surface height while the game owns every look.

ts
class FluidParticles3D extends Group

Use it to

  • pour, splash, or dam-break water that fills a container and flows around obstacles
  • simulate water or another liquid as particles in a fluid simulation
  • drop a ball or box into liquid and let it displace and float on the water
  • emit a stream or waterfall of particle fluid and drain it somewhere else
  • sample particle-fluid density or surface height in a game-owned render node

Example

ts
const water = new FluidParticles3D({ capacity: 6000 });
ctx.add(water);
water.fill([-2.8, 0.1, -1.5], [-0.6, 3, 1.5]);

Good to know

  • add the fluid through ctx.add so renderer attachment, fixed-step dispatch, and release are automatic
  • a renderer without WebGPU compute throws a named error at attach; it never draws nothing
  • emit recycles the oldest slot once capacity slots have been used; fill stops at capacity
  • sample and stats read a throttled GPU copy and report staleFrames; they are never live

Options

  • iterations, viscosity, cohesion, vorticity, gravity and maxSpeed tune the solver without changing its pass order

formatSceneWarning

function · import { formatSceneWarning } from "@threenative/core"

Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as TN_SCENE_WARNING, and silent on a scene that is honestly GPU-bound.

ts
function formatSceneWarning(warning: ISceneWarning): string

Use it to

  • find out whether a slow frame is the scene's shape or the device
  • tell an authoring agent what to reduce before it promises a merge

Example

ts
defineGame({ display: { maxFps: 60 }, scenes: { Play } });

Good to know

  • the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant
  • no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence
  • the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking

formatSpansWindow

function · import { formatSpansWindow } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function formatSpansWindow(window: ISpanWindow): string

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

formatValidationReport

function · import { formatValidationReport } from "@threenative/core"

Prove that nothing a cache skipped changed the picture: TN_RENDERLIST_VALIDATE=1 recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.

ts
function formatValidationReport(report: IValidationReport): string

Use it to

  • prove a static freeze did not leave a stale transform on screen
  • gate a scene in CI against silent transform divergence

Example

ts
if (renderListValidationRequested()) console.log(formatValidationReport(report));

Good to know

  • off by default and expensive by construction — it does the work it is checking, twice
  • it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads
  • it proves the frames it ran on and nothing else

FrameBudget

class · import { FrameBudget } from "@threenative/core"

Read where the frame's milliseconds went, per presented frame, on any platform; each TN_FRAME_BUDGET window also carries the GPU time per resolved frame and the draw calls and triangles each render pass submitted.

ts
class FrameBudget

Use it to

  • show an on-screen frame time meter with p50, p95 and p99 percentiles
  • find out why a game runs slowly on a phone
  • attribute a frame to present wait, simulation, three.js render, or overlay
  • tell whether the GPU is the frame's constraint from a per-frame series, not one lagged timestamp
  • split a frame's draw calls and triangles per render pass (main, shadow, reflection)
  • tell a shadow or reflection pass's cost from the main colour pass

Example

ts
defineGame({ frameBudget: { reportEvery: 120 }, scenes: { Play } });

Good to know

  • on by default and printed as TN_FRAME_BUDGET; defineGame({ frameBudget: false }) silences the marker, not the measurement
  • per-pass numbers are attributed to the innermost active render call, so nested shadow and reflection passes do not read as main
  • GPU is a mean/p50/p95/max series over resolved frames (gpu) with gpuStale counting frames that had no fresh reading; absent means no timestamps, never zero

FrameCounters

class · import { FrameCounters } from "@threenative/core"

Count the frame's host-boundary crossings and the bytes it writes into GPU buffers, off unless TN_FRAME_SPANS asks for them. On the frame budget's own window as counters, so a crossing count and a millisecond split describe the same frames.

ts
class FrameCounters

Use it to

  • decide whether a CPU-bound frame is paying for the V8-to-host boundary
  • measure how many bytes a frame writes into GPU buffers, and how many commands it issues

Example

ts
const counters = FrameCounters.install(counterDeviceOf(renderer.raw));

Good to know

  • counts command-encoder and queue methods only; mapAsync and the presentation path are named, not folded in
  • gpuBytes is queue.writeBuffer exactly, so it reconciles against a driver; texture uploads are not included
  • jsAllocBytes needs performance.memory and stays absent where the platform lacks it

gearClearance

function · import { gearClearance } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
function gearClearance(state: IFlightState): number

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

getPlatform

function · import { getPlatform } from "@threenative/core"

Read the host platform without reaching for browser globals.

ts
function getPlatform(): Readonly<IPlatformInfo>

Use it to

  • branch a portable game on web or native
  • choose touch controls for a mobile device
  • can I raytrace on native

Example

ts
if (isMobile()) showTouchControls();

Good to know

  • only use the returned platform facts; native globals are host-owned
  • ray tracing is unavailable on native until buffer-to-texture copy-out interop exists; mystralRT.traceRays refuses instead of resolving success without a readable result

GPUParticles3D

class · import { GPUParticles3D } from "@threenative/core"

Dispatch a game-owned particle surface and process function through a pooled system.

ts
class GPUParticles3D extends Sprite implements IComputeDriven

Use it to

  • emit sparks, smoke, or other transient effects
  • update many small visual particles
  • trail dust, exhaust, or spray behind a moving object
  • emit cannon smoke and muzzle flash particles
  • fire a cannonball projectile with cannon smoke particles
  • splash spray droplets with lifetime and gravity
  • spawn water droplets, mist, or foam bubbles above a fluid surface
  • falling snowflakes, rain or ash around the player that thicken into a windy storm or blizzard
  • kick up a spray of powder snow or dust where a foot or a ball lands

Example

ts
const particles = new GPUParticles3D(particleOptions);

Good to know

  • geometry, color, and timing remain supplied by the game

GPUReadback

class · import { GPUReadback } from "@threenative/core"

Copy a GPU buffer back to the CPU on a throttle, and report how old each sample is.

ts
class GPUReadback

Use it to

  • read a GPU simulation on the CPU without stalling the frame
  • float a body on a wave field whose height only exists on the GPU
  • count GPU-side survivors for a diagnostic without blocking drawing
  • keep a ship floating on simulated ocean waves

Example

ts
const heights = new GPUReadback({ attribute: field.value, everyFrames: 4 });

Good to know

  • the copy is asynchronous, so every sample carries staleFrames and is never this frame
  • WebGPU only; the seam throws on a WebGL2 renderer rather than returning nothing
  • one copy is in flight at a time and requests made during one are dropped, not queued

GPUSceneBVH

class · import { GPUSceneBVH } from "@threenative/core"

Pack a selected static scene into TSL storage nodes for an upstream BVH ray query.

ts
class GPUSceneBVH extends Group implements IComputeDriven

Use it to

  • trace thousands of scene rays inside a TSL kernel
  • build a contact-occlusion or visibility query over loaded meshes

Example

ts
const bvh = ctx.add(new GPUSceneBVH(ctx.scene, { include: (object) => object.userData.traceable === true }));

Good to know

  • call rebuild() after a scene transform or geometry change; the snapshot is static by default
  • rebuild() is an explicit CPU SAH build proportional to selected triangles; process() is a no-op, and the game pays upstream traversal per shader ray

GroundSnap

class · import { GroundSnap } from "@threenative/core"

Keep a rendered model's feet on a surface while preserving an auditable override.

ts
class GroundSnap

Use it to

  • keep a character's feet on the floor
  • correct visual grounding after an animation update

Example

ts
import { GroundSnap } from "@threenative/core";
const snap = new GroundSnap(character, { enabled: true });

Good to know

  • import from @threenative/core; this moves the rendered model, not its physics collider

Options

  • enabled controls whether correction is applied while clearance is still measured

InputMap

class · import { InputMap } from "@threenative/core"

The map a game reads input through: named actions, 2D vectors and scalar axes resolved from keyboard, gamepad, mouse, wheel, pinch and touch. defineGame({ input }) builds one and hands it to the running game as ctx.input, so the usual route is a binding in the config and ctx.input.axis("move") in the update. Construct one directly to drive a menu, a replay or a test outside a running game.

ts
class InputMap

Use it to

  • map WASD keys to a movement axis instead of reading the held key set in the update loop
  • read a jump, a fire or a reload as one named action bound to key, gamepad button and mouse button together
  • read mouse look, wheel zoom or a two-finger pinch as an axis the frame loop already ticks

Example

ts
const game = defineGame({ input: { move: { up: ["KeyW"], down: ["KeyS"], left: ["KeyA"], right: ["KeyD"] } }, scenes: { Play } });
// inside the scene, per frame: ctx.input.axis("move") is 0 at rest and 1 at full tilt

Good to know

  • buttons is the gamepad and mouseButtons the mouse; up/down/left/right are the directions of vector(name), not the keys that press it
  • scroll, pinch and pointer-relative sources are declared on the binding and read through axis(name); a game adds no window listener of its own

installSpanProbes

function · import { installSpanProbes } from "@threenative/core"

Attach the span probes to a renderer: three's render, _projectObject, the render list's sort, the per-draw submission, and the scene-graph walk. Installed for you when TN_FRAME_SPANS asks; exported so a harness can wrap a renderer it owns.

ts
function installSpanProbes(target: ISpanProbeTarget, root: Object3D): () => void

Use it to

  • measure which part of three's render path costs the frame
  • attach the span tree to a renderer a test or a tool constructed itself

Example

ts
const uninstall = installSpanProbes(renderer.raw, scene);

Good to know

  • returns an uninstall that restores every wrapper, asserted by test
  • a renderer whose internals have moved loses that span rather than throwing, and it is absent from the report rather than zero
  • only the outermost _projectObject opens a span, because three's recurses per child

InstancedBatch

class · import { InstancedBatch } from "@threenative/core"

Collapse many copies of one game-authored shape into a single draw, without counting them first. new InstancedMesh(geometry, material, count) needs the count before anything is placed, so a procedural builder ends up walking its layout twice or over-allocating. Place as you go and build() once; the shape, the surface and every transform stay the game's, and the built mesh is returned so instances can still be animated by the index place and span hand back.

ts
class InstancedBatch

Use it to

  • draw hundreds of repeated props without hundreds of draw calls
  • draw thousands of identical instanced blocks or obstacles in one mesh
  • automatically select instanced prop detail from projected screen error using baked AutoLOD chains
  • place repeated props when the count is not known until the layout has been walked
  • build a chain, railing, cable, or tie rod out of point-to-point segments

Example

ts
const curbs = new InstancedBatch({ geometry: new BoxGeometry(1, 1, 1), material });
curbs.place({ position: [x, 0.08, z], rotation: [0, angle, 0], scale: [length, 0.18, 0.42] });
curbs.build({ castShadow: true, name: "curbs", parent: ctx.scene });

Good to know

  • geometry and material are required and come from the game; the batch chooses neither
  • span stretches along +Y, so its geometry must be unit-height and centred on the origin
  • placing after build() throws, and build() returns undefined when nothing was placed
  • baked AutoLOD chains survive geometry clone/transform preparation and partition instances automatically into spatially bounded draws in the engine frame loop at 4 px projected error; public instance slots remain stable
  • unavailable authored levels report TN_INSTANCED_LOD_FAILED once naming the batch; a million-triangle batch with no chain reports TN_INSTANCED_LOD_UNAVAILABLE

Options

  • autoLod: false leaves selection to the game; autoLod.maxPixelError and hysteresis override the measured-camera budget; lods supplies authored distance/geometry levels and wins over the baked chain
  • castShadow and receiveShadow pass through to the built mesh and default to Three.js's own false

Also found by

  • landmarks points of interest
  • obstacles collectibles increasing pace

invalidateStatic

function · import { invalidateStatic } from "@threenative/core"

Stop recomposing the transforms of a subtree nobody moves. markStatic(root) composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and invalidateStatic(object) announces a write deeper inside one.

ts
function invalidateStatic(object: Object3D): number | undefined

Use it to

  • cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves
  • keep a frozen subtree correct when the game does move it after all

Example

ts
invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

isMobile

function · import { isMobile } from "@threenative/core"

Read the host platform without reaching for browser globals.

ts
function isMobile(): boolean

Use it to

  • branch a portable game on web or native
  • choose touch controls for a mobile device
  • can I raytrace on native

Example

ts
if (isMobile()) showTouchControls();

Good to know

  • only use the returned platform facts; native globals are host-owned
  • ray tracing is unavailable on native until buffer-to-texture copy-out interop exists; mystralRT.traceRays refuses instead of resolving success without a readable result

isNative

function · import { isNative } from "@threenative/core"

Read the host platform without reaching for browser globals.

ts
function isNative(): boolean

Use it to

  • branch a portable game on web or native
  • choose touch controls for a mobile device
  • can I raytrace on native

Example

ts
if (isMobile()) showTouchControls();

Good to know

  • only use the returned platform facts; native globals are host-owned
  • ray tracing is unavailable on native until buffer-to-texture copy-out interop exists; mystralRT.traceRays refuses instead of resolving success without a readable result

isStatic

function · import { isStatic } from "@threenative/core"

Stop recomposing the transforms of a subtree nobody moves. markStatic(root) composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and invalidateStatic(object) announces a write deeper inside one.

ts
function isStatic(root: Object3D): boolean

Use it to

  • cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves
  • keep a frozen subtree correct when the game does move it after all

Example

ts
invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

isTouchscreenAvailable

function · import { isTouchscreenAvailable } from "@threenative/core"

Read the host platform without reaching for browser globals.

ts
function isTouchscreenAvailable(): boolean

Use it to

  • branch a portable game on web or native
  • choose touch controls for a mobile device
  • can I raytrace on native

Example

ts
if (isMobile()) showTouchControls();

Good to know

  • only use the returned platform facts; native globals are host-owned
  • ray tracing is unavailable on native until buffer-to-texture copy-out interop exists; mystralRT.traceRays refuses instead of resolving success without a readable result

isWeb

function · import { isWeb } from "@threenative/core"

Read the host platform without reaching for browser globals.

ts
function isWeb(): boolean

Use it to

  • branch a portable game on web or native
  • choose touch controls for a mobile device
  • can I raytrace on native

Example

ts
if (isMobile()) showTouchControls();

Good to know

  • only use the returned platform facts; native globals are host-owned
  • ray tracing is unavailable on native until buffer-to-texture copy-out interop exists; mystralRT.traceRays refuses instead of resolving success without a readable result

loadAll

function · import { loadAll } from "@threenative/core"

Load a list with bounded concurrency, returning results in the input's order.

ts
async function loadAll<TIn, TOut>( items: readonly TIn[], load: (item: TIn, index: number) => Promise<TOut>, options: ILoadAllOptions =

Use it to

  • load many models or textures in parallel instead of one at a time
  • keep a loading screen moving while a list of assets downloads

Example

ts
const species = await loadAll(names, (name) => ctx.assets.model(`flora/${name}.glb`));

Good to know

  • results are written to each item's own index and never appended, so a positional lookup finds the same asset on every load
  • the first rejection rejects the call and no lane starts a load it had not begun

Options

  • concurrency defaults to 6; marker: false silences the TN_LOAD_ALL line, not onProgress

lodPixelScale

function · import { lodPixelScale } from "@threenative/core"

The screen pixels one world unit covers at depth for this camera and viewport. Perspective divides the projected scale by the depth; orthographic has no depth term and uses the frustum height instead. This is the number a level of detail is chosen against: multiply a level's world-space error by it and you have the on-screen error a player can see, which is the comparison updateModelLods makes from the baked chain.

ts
function lodPixelScale(camera: Camera, viewportHeight: number, depth: number): number

Use it to

  • pick a level of detail from an object's projected size in pixels on screen
  • know how many screen pixels a world-space error covers at a given distance

Example

ts
const pixels = lodPixelScale(camera, canvas.clientHeight, mesh.position.distanceTo(camera.position));

Good to know

  • a non-positive viewport height, a non-positive frustum height or an unprojectable camera throws
  • a non-positive depth has no projected scale and returns Infinity

markStatic

function · import { markStatic } from "@threenative/core"

Freeze a subtree nobody moves: markStatic(root) composes its transforms once and stops the per-frame recompose. It is the call a game makes on scenery, terrain, buildings and props. The engine re-arms a root whose own transform the game changes; a write deeper inside a frozen subtree is announced with invalidateStatic(object).

ts
function markStatic(root: Object3D): number

Use it to

  • freeze a static mesh or subtree so its matrices are not recomputed every frame
  • stop the engine recomposing the transforms of props, terrain and buildings each frame

Example

ts
markStatic(island); invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

MatrixWorldPass

class · import { MatrixWorldPass } from "@threenative/core"

Walk the scene graph's world matrices each frame without recursing into a hidden subtree. On by default as renderer.matrixWorld: "visible". three's updateMatrixWorld recurses into every child whatever its visible flag, so a hidden LOD body, a merged stand-in and a parked model cost a world-matrix multiply each while nothing under them can draw. This pass mirrors three exactly for every visible node and defers a hidden node's subtree until the frame it shows again. A class that overrides updateMatrixWorld (SkinnedMesh, Camera) runs its own, and a hidden node that holds bones is walked, so no skeleton or view matrix goes stale.

ts
class MatrixWorldPass

Use it to

  • update the world matrices of the visible objects each frame instead of the whole scene
  • the per-frame world matrix walk is hot in a profile
  • stop multiplying matrices for hidden models, LOD levels and merged stand-ins
  • a game needs every node walked, exactly as three's own updateMatrixWorld does

Example

ts
import { MatrixWorldPass } from "@threenative/core";
const pass = new MatrixWorldPass(); // renderer.matrixWorld defaults to "visible"

Good to know

  • a game that reads a hidden object's matrixWorld directly must use getWorldPosition or updateWorldMatrix(true, false) first
  • renderer.matrixWorld: "all" visits every node; TN_PROJECTION reports the visited count either way

Options

  • renderer.matrixWorld: "all" runs three's full walk instead of the visible-only default

measureThreePose

function · import { measureThreePose } from "@threenative/core"

Measure a Three.js pose for grounded or attachment-aware checks.

ts
function measureThreePose( object: Object3D, options: IMeasureThreePoseOptions =

Use it to

  • inspect a skinned model's posed bounds
  • verify a character's visual pose

Example

ts
const measurement = measureThreePose(model);

Good to know

  • precise per-vertex measurement is opt-in and not for frame loops

mergeByMaterial

function · import { mergeByMaterial } from "@threenative/core"

Bake a hierarchy's static meshes into one mesh per material, with their transforms baked in. already made; nothing here decides appearance tree: add them to root and remove the sources yourself, or both draw vertices are not its own to bake texture mapping; a missing normal is recomputed

ts
function mergeByMaterial(root: Object3D, options: IMergeByMaterialOptions): Mesh[]

Use it to

  • collapse a building or ship of dozens of boxes into one draw call per material
  • consolidate the static parts of a group before adding it to the scene

Example

ts
const [hull, deck] = mergeByMaterial(ship, { label: "ship" });
// a piece that must keep moving at run time:
const [steady] = mergeByMaterial(ship, { label: "ship", skip: (mesh) => mesh.name === "radar" });

Good to know

  • the material is the game's own instance and the split follows the materials the game
  • the meshes come back in root's local space and unparented, with the originals still in the
  • a skinned or instanced mesh, and a mesh with several materials, is left out — its
  • a group where only some meshes carry uv throws naming the label rather than losing the

Options

  • skip leaves one mesh out of its group and out of the result

mergeParts

function · import { mergeParts } from "@threenative/core"

Merge pieces a game authored out of primitives into one buffer, keeping each piece's own look. InstancedBatch collapses many copies of one shape; this collapses many different shapes that never move relative to each other — a building, a ship, a character built from boxes, or the static parts of an imported glTF model. Two things go wrong every time and neither is about how any of it looks. mergeGeometries hands back null on mismatched inputs instead of throwing, and the usual mismatch is invisible: one ExtrudeGeometry is non-indexed while every other primitive is indexed, so the merge fails at the first piece and the scene never loads. And a merged buffer draws with one surface, so per-piece colour is gone unless every piece carries a flat color attribute written before the merge. Both are mechanical. By default every part is stripped to position and the normals are recomputed from the merged buffer; pass preserve to keep authored normals and texture UVs while baking each part's object transform. Geometry, placement, colour and the surface it draws with all stay the game's.

ts
function mergeParts( parts: Iterable<IMergePart>, options: IMergePartsOptions, ): BufferGeometry

Use it to

  • bake a building, ship or character authored out of primitives into one draw call
  • merge multiple static Three.js meshes into one mesh per material
  • consolidate the static parts of an imported glTF model into one buffer
  • preserve texture UV coordinates and authored normals while baking object transforms
  • merge many small geometries and keep each piece's own colour
  • stop mergeGeometries from silently returning null on an extruded shape

Example

ts
const wall = new Mesh(mergeParts(pieces, { label: "gatehouse" }), stone);
// pieces are meshes, or { geometry, matrix, color } when the colour is per piece:
const banner = mergeParts([{ color: 0x8b2f1a, geometry: cloth, matrix: placement }], { label: "banner" });
// keep a model's texture UVs and authored normals while baking its transforms:
const hull = mergeParts(hullParts, { label: "hull", preserve: ["uv", "normal"] });

Good to know

  • every part is de-indexed; without preserve it is stripped to position and normals are recomputed from the merged buffer
  • preserve keeps the listed channels, transforming position and normal by the part's placement matrix while UV values are retained unchanged
  • a part that does not carry a listed preserve channel throws naming the label, the part and the channel
  • either every part names a color or none does, and a mix throws
  • an empty part list throws, and a merge three.js refuses throws naming the label

Options

  • color is per part and optional; without it no colour attribute is written and the surface alone decides
  • preserve is optional and empty by default: position-only merge with recomputed normals, exactly as before

normaliseToMetres

function · import { normaliseToMetres } from "@threenative/core"

Scale an asset to a real-world measurement and return the applied factor.

ts
function normaliseToMetres(object: Object3D, options: INormaliseToMetresOptions): number

Use it to

  • make a character exactly 1.8 metres tall
  • normalize a prop or weapon to a known longest axis

Example

ts
normaliseToMetres(character, { metres: 1.8, axis: "height" });

Good to know

  • skinned height uses a crown bone; game-specific asset expectations stay in render code

Replaces new Box3().setFromObject(.

onLaunchFailure

function · import { onLaunchFailure } from "@threenative/core"

Called for every launch failure the engine notices, with the message to show the player.

ts
function onLaunchFailure(listener: (failure: ILaunchFailure) => void): () => void

Use it to

  • show the player why the game stopped loading instead of leaving the loading screen up
  • report a stalled launch or a lost GPU device in the game's own UI

Example

ts
const off = onLaunchFailure((failure) => shell.loading({ failure: failure.message }));

parseReplayRecording

function · import { parseReplayRecording } from "@threenative/core"

Validate and parse a replay recording file.

ts
function parseReplayRecording(value: unknown): IReplayRecording

Use it to

  • validate a recording before replaying it in another host

Example

ts
const recording = parseReplayRecording(rawRecording);

Good to know

  • recordings are version 1; the parser fails closed with TN_REPLAY_* codes

PathFollow3D

class · import { PathFollow3D } from "@threenative/core"

Move an object along a Three.js curve with Godot-style path following.

ts
class PathFollow3D

Use it to

  • move an enemy or prop along a patrol path
  • sample a racing line from a curve

Example

ts
const follower = new PathFollow3D({ points: patrolPoints, loop: true, speed: 3 });

PipelineCensus

class · import { PipelineCensus } from "@threenative/core"

Read the renderer's bounded, versioned pipeline capture in a diagnostic or playtest tool.

ts
class PipelineCensus

Use it to

  • inspect shader and pipeline creation work during a real launch
  • correlate pipeline creation with material, object, pass, and shader identities

Example

ts
const capture = game.runtime.pipelineCensus?.();
// The game normally reaches this through `runtime.pipelineCensus`; direct construction exists
// for renderer adapters and contract tests, not for gameplay.

Good to know

  • the capture is bounded and incomplete when the backend cannot expose an observation

PointerEvents3D

class · import { PointerEvents3D } from "@threenative/core"

Dispatch portable pointer events from the game surface to registered Three.js objects.

ts
class PointerEvents3D implements IPointerEvents3D

Use it to

  • let the player click on a thing in the world
  • show a 3D object while a pointer hovers over it
  • handle touch and mouse taps on a loaded model without naming its child meshes
  • drag a crate or prop with the mouse or a finger

Example

ts
ctx.pointer.on(tile, "tapped", (event) => place(event.point));

Good to know

  • listeners are side-table registrations; Three.js prototypes are never patched
  • one raycast serves each active pointer and no raycast runs when nothing is registered

posedBounds

function · import { posedBounds } from "@threenative/core"

Measure a Three.js pose for grounded or attachment-aware checks.

ts
function posedBounds(root: Object3D, meshes?: readonly Object3D[]): IThreePoseBounds

Use it to

  • inspect a skinned model's posed bounds
  • verify a character's visual pose

Example

ts
const measurement = measureThreePose(model);

Good to know

  • precise per-vertex measurement is opt-in and not for frame loops

prewarm

function · import { prewarm } from "@threenative/core"

Keep transient render surfaces in the renderer's pipeline cache before first use.

ts
function prewarm(object: Object3D | readonly Object3D[]): void

Use it to

  • prewarm a projectile, tracer, particle, or other transient effect
  • avoid a long first-use frame for a newly visible effect

Example

ts
prewarm(tracerPool);

Good to know

  • keep the surface visible with zero opacity; do not hide it with visible = false

Replaces .visible = false.

ProbeVolume

class · import { ProbeVolume } from "@threenative/core"

Bake static diffuse irradiance that reaches surfaces from outside the camera view.

ts
class ProbeVolume extends Object3D implements IComputeDriven

Use it to

  • light bouncing from a room I cannot see
  • light a wall with an off-screen emitter

Example

ts
const probes = new ProbeVolume({ bounds, density: 0.5 }); ctx.add(probes); void probes.requestBake(scene);

Good to know

  • request a bake after static geometry and lights are authored; this is static-lighting-first, not fully dynamic relighting
  • add the volume with ctx.add() so its incremental work is measured in the render phase
  • call sample() or sampleNode() from a game-owned material before screen-space GI; the volume owns no light, material, or colour

Options

  • density, bounds, bakeBudgetMs, and bounces are game-owned choices

Also found by

  • readable world lighting

readProbeVolumeObservation

function · import { readProbeVolumeObservation } from "@threenative/core"

Read the most recent observation from a probe volume.

ts
function readProbeVolumeObservation(value: unknown): IProbeVolumeObservation | undefined

Use it to

  • inspect the latest probe bake observation

Example

ts
const observation = readProbeVolumeObservation(probes);

Good to know

  • the returned observation is a measurement; it does not own lighting or materials

readRenderChainObservation

function · import { readRenderChainObservation } from "@threenative/core"

Compose game-provided render nodes in a measured, fail-closed chain. Authored stages use an opaque id and anchor before or after a built-in or another supplied stage; the engine does not need to know the effect's visual vocabulary.

ts
function readRenderChainObservation( renderer: unknown, ): IRenderChainMarker["applied"] | undefined

Use it to

  • compose screen-space effects in a canonical order
  • insert a game-authored post stage into the measured render chain
  • report which render tier and velocity route actually ran

Example

ts
const chain = new RenderChain(renderer, { input: colour, stages, request: { stages: ["bloom"], tier: "auto" } });

Good to know

  • stage factories own colour, strength, and all other appearance choices
  • authored stages declare exactly one before or after anchor

readRenderChainReport

function · import { readRenderChainReport } from "@threenative/core"

Compose game-provided render nodes in a measured, fail-closed chain. Authored stages use an opaque id and anchor before or after a built-in or another supplied stage; the engine does not need to know the effect's visual vocabulary.

ts
function readRenderChainReport(renderer: unknown): IRenderChainMarker | undefined

Use it to

  • compose screen-space effects in a canonical order
  • insert a game-authored post stage into the measured render chain
  • report which render tier and velocity route actually ran
  • expose the render tier and dropped-stage reasons to a playtest
  • inspect whether a temporal pass received velocity

Example

ts
const chain = new RenderChain(renderer, { input: colour, stages, request: { stages: ["bloom"], tier: "auto" } });

Good to know

  • stage factories own colour, strength, and all other appearance choices
  • authored stages declare exactly one before or after anchor
  • an absent value means no chain was installed and must fail a chain assertion

readVelocityPreviousBoneMatrices

function · import { readVelocityPreviousBoneMatrices } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
function readVelocityPreviousBoneMatrices(object: Object3D): Float32Array | undefined

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • inspect the previous skinned pose at a renderer adapter boundary

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it

readVelocityPreviousMatrices

function · import { readVelocityPreviousMatrices } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
function readVelocityPreviousMatrices(object: Object3D): Float32Array | undefined

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • inspect the previous instance frame at a renderer adapter boundary

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it

readVelocityPreviousWorldMatrix

function · import { readVelocityPreviousWorldMatrix } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
function readVelocityPreviousWorldMatrix(object: Object3D): Matrix4 | undefined

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • inspect the previous rigid transform at a renderer adapter boundary

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it

readVirtualShadowMarker

function · import { readVirtualShadowMarker } from "@threenative/core"

Parse a TN_VIRTUAL_SHADOW console line back into its complete stats, or undefined.

ts
function readVirtualShadowMarker(line: string): IVirtualShadowStats | undefined

Use it to

  • inspect virtual shadow cache and mover counters from a renderer log

Example

ts
const stats = readVirtualShadowMarker(line);
if (stats !== undefined) console.log(stats.reuseRatio);

Good to know

  • non-marker lines and markers with incomplete or non-numeric stats return undefined

reconcileMirroredClips

function · import { reconcileMirroredClips } from "@threenative/core"

Repair an exported rig whose animation clips are z-mirrored against its own bind pose.

ts
function reconcileMirroredClips(root: Object3D, clips: readonly AnimationClip[]): boolean

Use it to

  • find out why a skinned character renders deformed
  • repair an imported character that walks backwards with its spine folded
  • load a rigged GLB through a custom loader and keep the framework's repair

Example

ts
import { reconcileMirroredClips } from "@threenative/core";
if (reconcileMirroredClips(gltf.scene, gltf.animations)) console.info("clips were z-mirrored; repaired");

Good to know

  • the model loader already applies this automatically; only a game loading GLBs around it needs the call
  • detection votes per tracked bone and converts only on an overwhelming signature; a file that does not carry it is left byte-identical

refreshStaticTransforms

function · import { refreshStaticTransforms } from "@threenative/core"

Stop recomposing the transforms of a subtree nobody moves. markStatic(root) composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and invalidateStatic(object) announces a write deeper inside one.

ts
function refreshStaticTransforms(): void

Use it to

  • cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves
  • keep a frozen subtree correct when the game does move it after all

Example

ts
invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

RenderChain

class · import { RenderChain } from "@threenative/core"

Compose game-provided render nodes in a measured, fail-closed chain. Authored stages use an opaque id and anchor before or after a built-in or another supplied stage; the engine does not need to know the effect's visual vocabulary.

ts
class RenderChain

Use it to

  • compose screen-space effects in a canonical order
  • insert a game-authored post stage into the measured render chain
  • report which render tier and velocity route actually ran

Example

ts
const chain = new RenderChain(renderer, { input: colour, stages, request: { stages: ["bloom"], tier: "auto" } });

Good to know

  • stage factories own colour, strength, and all other appearance choices
  • authored stages declare exactly one before or after anchor

renderListValidationRequested

function · import { renderListValidationRequested } from "@threenative/core"

Prove that nothing a cache skipped changed the picture: TN_RENDERLIST_VALIDATE=1 recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.

ts
function renderListValidationRequested(): boolean

Use it to

  • prove a static freeze did not leave a stale transform on screen
  • gate a scene in CI against silent transform divergence

Example

ts
if (renderListValidationRequested()) console.log(formatValidationReport(report));

Good to know

  • off by default and expensive by construction — it does the work it is checking, twice
  • it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads
  • it proves the frames it ran on and nothing else

RenderListValidator

class · import { RenderListValidator } from "@threenative/core"

Prove that nothing a cache skipped changed the picture: TN_RENDERLIST_VALIDATE=1 recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.

ts
class RenderListValidator

Use it to

  • prove a static freeze did not leave a stale transform on screen
  • gate a scene in CI against silent transform divergence

Example

ts
if (renderListValidationRequested()) console.log(formatValidationReport(report));

Good to know

  • off by default and expensive by construction — it does the work it is checking, twice
  • it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads
  • it proves the frames it ran on and nothing else

replay

function · import { replay } from "@threenative/core"

Record or replay deterministic game input and state.

ts
function replay< TState extends Record<string, unknown> = Record<string, unknown>, TPhysics = undefined, >(options: IReplayOptions =

Use it to

  • reproduce a gameplay bug from recorded input
  • run a deterministic replay in a playtest

Example

ts
const driver = createReplayDriver(recording, ctx.renderer.domElement);

Good to know

  • use a seeded random source and fixed-step simulation for meaningful replays

Also found by

  • fixed seed fixed-step simulation

resetAudioCueLedger

function · import { resetAudioCueLedger } from "@threenative/core"

Forgets every recorded cue.

ts
function resetAudioCueLedger(): void

Use it to

  • clear the recorded audio cue counts between tests so one test cannot read another's plays

Example

ts
resetAudioCueLedger();

resetStaticTransforms

function · import { resetStaticTransforms } from "@threenative/core"

Stop recomposing the transforms of a subtree nobody moves. markStatic(root) composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and invalidateStatic(object) announces a write deeper inside one.

ts
function resetStaticTransforms(): void

Use it to

  • cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves
  • keep a frozen subtree correct when the game does move it after all

Example

ts
invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

resolveAtmosphereLutResolutions

function · import { resolveAtmosphereLutResolutions } from "@threenative/core"

Resolve the three LUT dimensions, allowing a game to trade startup cost for resolution.

ts
function resolveAtmosphereLutResolutions( resolutions: Partial<IAtmosphereLutResolutions> | undefined, ): IAtmosphereLutResolutions

Use it to

  • choose atmosphere LUT dimensions for a measured startup budget

Example

ts
const resolutions = resolveAtmosphereLutResolutions({ skyView: { width: 128, height: 72 } });

Good to know

  • every width and height must be a positive integer; the dimensions are not a named fidelity tier

resolveAtmosphereParameters

function · import { resolveAtmosphereParameters } from "@threenative/core"

Validate and clone game-owned atmosphere coefficients.

ts
function resolveAtmosphereParameters( options: IAtmosphereParameters, ): IResolvedAtmosphereParameters

Use it to

  • validate atmosphere coefficients before a game creates its sky

Example

ts
const parameters = resolveAtmosphereParameters({ rayleigh, mie, ozone, planetRadius, atmosphereRadius });

Good to know

  • provide all three coefficient vectors and both radii; omitted fields are errors

resolveTargetFps

function · import { resolveTargetFps } from "@threenative/core"

Read what frame rate a game gets when its config does not say, and why. display.maxFps follows the display — capped at 120 on desktop and web, 60 on mobile — rather than the 60 every template used to ship, and an explicit number still wins with 0 still uncapping. The engine calls this itself; a game calls it when it needs the same number for its own frame-rate-dependent work, and TN_FRAME_BUDGET reports the resolved target and its source on every window.

ts
function resolveTargetFps( config: ITargetFpsConfig | undefined, platform: ITargetFpsPlatform | undefined, measuredRefreshHz?: number, ): ITargetFps

Use it to

  • my game does frame-rate-dependent work and must not hardcode 60

Example

ts
resolveTargetFps(config, getPlatform()).targetFps;

Good to know

  • pass the measured display rate when you have one; without it the answer is the 60 fallback and says so

RippleField

class · import { RippleField } from "@threenative/core"

Propagate a disturbance across a patch of water surface and let it fade. step is 1/60s or the CFL stability limit for the given resolution and speed, whichever is smaller

ts
class RippleField

Use it to

  • make a splash or explosion ripple outward across water
  • show the sea reacting to a bomb, shell, or torpedo hitting it
  • leave a foam trail behind something moving through water
  • disturb a water surface the player can see respond
  • spread and drift foam on a water surface over time

Example

ts
const ripples = new RippleField({ resolution: 128, size: 400 });
ripples.impulse(hit.x, hit.z, 6, -40, 0.5);
ripples.advance(dt);
const lift = ripples.heightAt(boat.x, boat.z);

Good to know

  • it draws nothing; the game supplies the mesh, the material and every colour
  • add its height to an analytic swell, never in place of one
  • the patch is finite and its rim absorbs; call recenter to keep it over the action
  • there is no obstacle mask, because a mask is only correct for a body that never moves

Options

  • speed, damping, foamHalfLife, current, step and maxSteps tune the solve; the default

Scene

class · import { Scene } from "@threenative/core"

Implement a portable Godot-shaped game scene lifecycle.

ts
abstract class Scene< TState extends Record<string, unknown> = Record<string, unknown>, TPhysics = undefined, >

Use it to

  • add a playable level or menu scene
  • move scene setup and per-frame gameplay out of the entry point
  • run scene work once per actual world draw, after the frame's last fixed update and before the projection packs

Example

ts
class Play extends Scene { update(ctx, dt) {} }
ctx.beforeRender(() => packBatches()); // cleared on scene change and stop, like ctx.afterPhysics

Good to know

  • scene code must stay portable across web and native

ScenePicker

class · import { ScenePicker } from "@threenative/core"

Raycast the game scene using the framework's picker.

ts
class ScenePicker

Use it to

  • select an object under the pointer
  • interact with the first collider or mesh hit

Example

ts
const picker = new ScenePicker({ camera: ctx.camera, scene: ctx.scene, pointer: () => ctx.input.raw.pointer, viewport: ctx.viewport });

Replaces new Raycaster(.

sceneWarning

function · import { sceneWarning } from "@threenative/core"

Warn the agent that built the scene before a human plays it: a frame whose GPU is idle while its JS render phase is longer than the display's own period is a scene-shape problem, and the engine already has the shape. On by default, printed at most once per reported window as TN_SCENE_WARNING, and silent on a scene that is honestly GPU-bound.

ts
function sceneWarning( window: IFrameBudgetWindow, shape: ISceneShape | undefined, declaredTargetFps: number | undefined, ): ISceneWarning | undefined

Use it to

  • find out whether a slow frame is the scene's shape or the device
  • tell an authoring agent what to reduce before it promises a merge

Example

ts
defineGame({ display: { maxFps: 60 }, scenes: { Play } });

Good to know

  • the rule is derived from the frame's own numbers; the display period comes from the host's presentation cap or the game's declared target, never a constant
  • no verdict without a measured GPU reading and a pass census — an absent measurement is not evidence
  • the record carries objects considered, draws per pass, triangles per draw and shadow-exempt casters, so the reader is not dependent on the ranking

Scheduler

class · import { Scheduler } from "@threenative/core"

Schedule delayed and repeating callbacks or tween numeric properties with game-owned cleanup.

ts
class Scheduler

Use it to

  • delay an enemy patrol transition
  • run a callback every simulation tick
  • tween a numeric property with a game-owned curve
  • a countdown timer: end the level when its time limit runs out

Example

ts
const door = { y: 0 };
await ctx.tween(door, { y: 2.4 }, 0.5, { ease: (t) => 1 - (1 - t) ** 3 });

Good to know

  • dispose returned handles when the owning scene exits
  • ease receives progress in the range 0 to 1 and its return value is the interpolation factor

Also found by

  • tower defense game
  • spawn waves

setAttitude

function · import { setAttitude } from "@threenative/core"

Fly a fixed-wing aircraft with a real force balance instead of a steered velocity.

ts
function setAttitude( state: IFlightState, heading = 0, pitch = 0, roll = 0, ): IFlightQuaternion

Use it to

  • fly an airplane with lift, drag, stall and control authority
  • launch an aircraft off a moving carrier deck
  • apply component damage or a loadout to an aircraft's performance

Example

ts
const model = new FlightModel({ airframe: sbd, state: aircraft, wind: seaWind });
model.step(1 / 60, { turn: -1, pitch: 0.4 });

Good to know

  • every mass, area, power and inertia value comes from the game's airframe
  • damage, stores and configuration arrive as the game's own modifier sample

setSpanRecorder

function · import { setSpanRecorder } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function setSpanRecorder(next: SpanRecorder | undefined): void

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

SkeletalMesh3D

class · import { SkeletalMesh3D } from "@threenative/core"

Shared preparation for an imported rigged character. Instances the rig with a skeleton-safe clone, normalises size with skin-aware measurement, validates requested clips against the file and rig at load time, and sets up AnimationPlayer with honest stride-root accounting.

ts
class SkeletalMesh3D extends AnimationPlayer

Use it to

  • put an animated character in the scene
  • my imported character renders deformed
  • instance an imported rigged character
  • validate animation clips on a character rig at load time
  • prepare a skinned character with safe skeleton cloning and stride sync

Example

ts
import { SkeletalMesh3D } from "@threenative/core";
const character = new SkeletalMesh3D({
  source: gltf.scene, clips: gltf.animations, requiredClips: ["idle", "walk"],
  size: { metres: 1.8, axis: "height" }, strideRoot: body,
});
body.add(character.root); character.play("idle");
function update(dt: number): void { character.update(dt); }

Good to know

  • use strideRoot to name the body moved by game code when the rig is parented under it
  • requiredClips fails closed at load time if any requested clip is missing or binds 0 tracks

Options

  • strideSync controls whether locomotion playback rate matches ground covered
  • size normalises the instance to real-world metres with skin-aware measurement

skeletonBones

function · import { skeletonBones } from "@threenative/core"

List the names of every bone in a character hierarchy.

ts
function skeletonBones(root: Object3D): readonly string[]

Use it to

  • inspect the available bones before attaching a game-owned object
  • debug a missing skeleton bone name

Example

ts
import { skeletonBones } from "@threenative/core";
const bones = skeletonBones(character);

snapRefreshRate

function · import { snapRefreshRate } from "@threenative/core"

Read what frame rate a game gets when its config does not say, and why. display.maxFps follows the display — capped at 120 on desktop and web, 60 on mobile — rather than the 60 every template used to ship, and an explicit number still wins with 0 still uncapping. The engine calls this itself; a game calls it when it needs the same number for its own frame-rate-dependent work, and TN_FRAME_BUDGET reports the resolved target and its source on every window.

ts
function snapRefreshRate(refreshHz: number): number

Use it to

  • my game does frame-rate-dependent work and must not hardcode 60

Example

ts
resolveTargetFps(config, getPlatform()).targetFps;

Good to know

  • pass the measured display rate when you have one; without it the answer is the 60 fallback and says so

SoftBody3D

class · import { SoftBody3D } from "@threenative/core"

Simulate an ordinary game-authored triangle mesh as cloth on the existing fixed-step GPU lane. The mesh supplies every visible choice. This class welds exporter duplicates, owns spring and position buffers, and replaces only the cloned material's position node. It adds no material, colour, texture, wind, stiffness, damping, or pinning default.

ts
class SoftBody3D extends Mesh<BufferGeometry, NodeMaterial> implements IComputeDriven

Use it to

  • make a flag, cape, or curtain move as cloth
  • simulate a deforming surface while keeping one edge pinned
  • simulate cloth sails blowing in the wind
  • make cloth sails billow in wind on a ship

Example

ts
const flag = new SoftBody3D(flagMesh, { pinned: topEdge, stiffness: 35, damping: 1.8, gravity: [0, -9.81, 0], wind: [1.5, 0, 0.4] });

Good to know

  • the mesh must use one Three.js node material and contain complete triangles
  • pinned, stiffness, damping, gravity, and wind are required game-owned inputs
  • Pixel 8 steady upper bound for the shipped 45-vertex pennant with readback every two frames: whole-starter update p95 4.66 ms, render p95 3.56 ms, and GPU timer 0.05 ms across three 300-frame final-rung windows at 552x248 with 4x MSAA; these whole-scene numbers are not isolated solver cost

Options

  • timeStep follows the engine 1/60-second convention unless the game overrides it
  • readbackEveryFrames enables an explicitly stale CPU position sample; zero disables it

softCircleDataTexture

function · import { softCircleDataTexture } from "@threenative/core"

Build a soft round sprite as pixel data instead of painting a canvas.

ts
function softCircleDataTexture(size = 64, hardness = 0.25): DataTexture

Use it to

  • give smoke, flash, or glow sprites a radial alpha falloff
  • generate sprite images that render identically under every backend

Example

ts
const puff = softCircleDataTexture(64, 0.25);

Good to know

  • canvas-painted images sample black under WebGPURenderer; write sprites as pixel data there

solarPosition

function · import { solarPosition } from "@threenative/core"

Calculate solar elevation and azimuth from time, latitude, and longitude.

ts
function solarPosition(input: ISolarPositionInput, target?: ISolarPosition): ISolarPosition;

Use it to

  • move a sun across a real day at a game's latitude and longitude
  • run a day and night cycle over the game's sky

Example

ts
const sun = solarPosition({ date, latitude: 49.28, longitude: -123.12, utcOffset: -8 });

Good to know

  • dates are interpreted as UTC unless utcOffset is supplied; no fixed sun direction is assumed
  • pass a mutable { azimuth, elevation } target to reuse the result object in a steady frame loop

solarPositionAt

function · import { solarPositionAt } from "@threenative/core"

Calculate solar elevation and azimuth for one UTC date.

ts
function solarPositionAt( date: Date | string, latitude: number, longitude: number, ): ISolarPosition

Use it to

  • calculate a sun direction from a timestamp and a game location

Example

ts
const sun = solarPositionAt(new Date(), 49.28, -123.12);

Good to know

  • dates are interpreted as UTC; use solarPosition for an explicit local offset

spanNow

function · import { spanNow } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function spanNow(): number

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

spanRecorder

function · import { spanRecorder } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function spanRecorder(): SpanRecorder | undefined

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

SpanRecorder

class · import { SpanRecorder } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
class SpanRecorder

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

spansRequested

function · import { spansRequested } from "@threenative/core"

Attribute the render phase to 100% with a nested span tree, off unless TN_FRAME_SPANS asks. Every non-leaf reports its own time minus the spans inside it, so an unmeasured part shows up as a residual instead of being filed under "other"; a child that outlives its parent reports a negative residual rather than a clamped zero.

ts
function spansRequested(): boolean

Use it to

  • find out what inside the render phase is actually costing the frame
  • tell a shadow pass's traversal from the main pass's, with the residual computed
  • price an optimisation against a measured part of the phase rather than the whole of it

Example

ts
if (spansRequested()) setSpanRecorder(new SpanRecorder());

Good to know

  • off by default and installed by TN_FRAME_SPANS=1; unset, every call site is one guarded return
  • the tree is closed against the frame budget's own render phase, so TN_FRAME_SPANS and TN_FRAME_BUDGET describe the same frames
  • measurement only: no span changes what is drawn, in what order, or with which renderer

SpectralOcean

class · import { SpectralOcean } from "@threenative/core"

Simulate a spectral ocean — cascaded wave spectra inverse-transformed on the GPU each frame.

ts
class SpectralOcean extends Object3D implements IComputeDriven

Use it to

  • make an ocean whose surface is the view rather than a background
  • float a boat on waves the GPU is drawing
  • drive a water material from a wave simulation without writing an FFT
  • sail a ship on simulated ocean waves with buoyancy

Example

ts
const sea = ctx.add(new SpectralOcean(oceanOptions));

Good to know

  • it draws nothing; the game supplies the mesh, the material and every colour
  • CPU height is a throttled copy carrying staleFrames, never this frame and never exact
  • an exact free CPU height needs an analytic wave field instead; that is a different contract
  • cascades are ordered largest patchSize first and every tuning number is required

SpriteAnimator3D

class · import { SpriteAnimator3D } from "@threenative/core"

Advance a game-owned non-uniform sprite atlas on the fixed step.

ts
class SpriteAnimator3D

Use it to

  • play an animated pickup or sprite-sheet effect
  • sequence atlas frames with different authored durations

Example

ts
const animator = new SpriteAnimator3D({ texture: atlas, frames, mode: "pingPong" });

Good to know

  • the game supplies the atlas texture, surface, filtering, layout, and every frame duration
  • update is driven by the scene fixed step; no wall clock or default frame rate is used

staticTransformCensus

function · import { staticTransformCensus } from "@threenative/core"

Stop recomposing the transforms of a subtree nobody moves. markStatic(root) composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and invalidateStatic(object) announces a write deeper inside one.

ts
function staticTransformCensus(): IStaticTransformCensus

Use it to

  • cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves
  • keep a frozen subtree correct when the game does move it after all

Example

ts
invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

TracerPool3D

class · import { TracerPool3D } from "@threenative/core"

Pool travelling bullet-streak meshes for hitscan shots.

ts
class TracerPool3D

Use it to

  • show where a hitscan round went
  • show each round a weapon fires, one tracer per trigger press
  • draw incoming fire without spawning projectiles

Example

ts
const tracers = new TracerPool3D(ctx.scene, tracerOptions);
tracers.spawn(muzzle, shotDirection, hit.distance);

Good to know

  • the surface comes from the game; pooling, travel, and fading belong to the engine
  • update once per frame and dispose with the owning scene

unmarkStatic

function · import { unmarkStatic } from "@threenative/core"

Stop recomposing the transforms of a subtree nobody moves. markStatic(root) composes the subtree once and freezes it; the engine re-arms a root whose own transform the game changes, and invalidateStatic(object) announces a write deeper inside one.

ts
function unmarkStatic(root: Object3D): void

Use it to

  • cut the per-frame matrix work of terrain, buildings, props and other scenery that never moves
  • keep a frozen subtree correct when the game does move it after all

Example

ts
invalidateStatic(drawbridge);

Good to know

  • staticness is authored, never guessed; no heuristic watches gameplay and decides for you
  • it deletes the local and world matrix composes, not the walk itself — three 0.185 recurses into every child regardless
  • a write deeper inside a frozen subtree must call invalidateStatic; TN_RENDERLIST_VALIDATE=1 is what proves it happened

updateAtmosphereParameters

function · import { updateAtmosphereParameters } from "@threenative/core"

Apply a partial game-owned atmosphere change while preserving validation.

ts
function updateAtmosphereParameters( current: IResolvedAtmosphereParameters, patch: IAtmosphereParameterPatch, ): IResolvedAtmosphereParameters

Use it to

  • change scattering coefficients and rebake an atmosphere

Example

ts
atmosphere.setAtmosphere({ rayleigh: [0.008, 0.016, 0.04] });

Good to know

  • patches cannot introduce omitted, negative, or non-finite physical values

updateClusteredMeshes

function · import { updateClusteredMeshes } from "@threenative/core"

Take every clustered mesh under a root through this frame's cut, before the render. The engine already does this for the scene it renders; reach for it only to cut a subtree the engine does not render, such as one staged for a camera of your own.

ts
function updateClusteredMeshes( root:

Use it to

  • cut a virtual-geometry subtree the engine does not render itself

Example

ts
updateClusteredMeshes(stagedRoot, myCamera, ctx.renderer.domElement.height);

updateModelLods

function · import { updateModelLods } from "@threenative/core"

Draw a model at the detail its projected geometric error earns, from a chain the asset cook baked. This is engine-owned, and a game does not call it. assets.lod: {} opts in — the default-on front door opens after qualification — the model pass bakes TN_discrete_lod into eligible models, the loader registers the reader, and the engine runs the selection every frame before it renders. Each frame the mesh picks the cheapest baked level whose measured error projects to fewer than the resolved pixel budget, taking the camera's own projection, zoom, viewport and a conservative nearest depth into account. Refinement is immediate; coarsening waits for the resolved hysteresis. A mesh with no baked chain draws its full geometry.

ts
function updateModelLods( root:

Use it to

  • draw a vehicle or hull built from many small meshes at distance without its full triangles
  • stop a distant model from costing its authored mesh count and triangle count
  • one source model, right detail by default, no hand-authored LOD files

Example

ts
// Nothing to call: the loader returns a mesh with the chain and the engine selects every frame.
const hull = await ctx.assets.model("hull.glb");
ctx.scene.add(hull.scene);

Good to know

  • the chain is baked by the asset cook; there is no runtime generation and no runtime flag
  • assets.lod: false opts out globally and assets.lod.overrides per asset, with no runtime controller installed
  • only static indexed triangles are eligible; skinned, morphed, alpha-blended or authored-LOD meshes keep full detail
  • the real gate is measured benefit: a level must save at least minSaving (20% default) of its predecessor, and a mesh that cannot is skipped, not forced

Options

  • assets.lod.generation.maxLevels, .minTriangles, .minTrianglesScope, .minSaving and .errorTargets move the bake's ceiling, pre-filter, its scope, its saving rule and its error ladder; assets.lod.runtime.maxPixelError and .hysteresis move the runtime budget and coarsen band; all by project, preset or asset

validateWorldMatrices

function · import { validateWorldMatrices } from "@threenative/core"

Prove that nothing a cache skipped changed the picture: TN_RENDERLIST_VALIDATE=1 recomputes every world matrix the long way, every frame, and throws on the first element that disagrees with what the frame is about to draw.

ts
function validateWorldMatrices(root: Object3D):

Use it to

  • prove a static freeze did not leave a stale transform on screen
  • gate a scene in CI against silent transform divergence

Example

ts
if (renderListValidationRequested()) console.log(formatValidationReport(report));

Good to know

  • off by default and expensive by construction — it does the work it is checking, twice
  • it throws on the first divergence rather than logging; a validation mode that continues is one nobody reads
  • it proves the frames it ran on and nothing else

velocityTexture

function · import { velocityTexture } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
function velocityTexture(pass: IVelocityRenderPass): Node

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • read screen-space motion for a temporal stage

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it

VelocityTracker

class · import { VelocityTracker } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
class VelocityTracker

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • retain previous transforms for animated and instanced renderables

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it
  • call update() after gameplay writes and commit() after the render

VirtualShadowNode

class · import { VirtualShadowNode } from "@threenative/core"

One directional shadow for a whole open world: camera-centred clip levels, each snapped to its own texel grid and re-rendered only when its window moves. Tracked casters draw into a per-level mover map every frame, so animated casters do not invalidate the cached levels after the one refresh caused by tracking or untracking them. Plugs into three's own light.shadow.shadowNode slot, so every material receives it.

ts
class VirtualShadowNode extends ShadowBaseNode

Use it to

  • crisp shadows close to the player across a large outdoor level
  • shadow map too coarse over a big terrain
  • one directional light shadow for a whole open world
  • shadows shimmer when the camera moves

Example

ts
const sun = new DirectionalLight(0xffffff, 3);
sun.castShadow = true;
sun.shadow.shadowNode = new VirtualShadowNode(sun, { clipExtents: [12, 40, 120] });

Good to know

  • the light must be a DirectionalLight with castShadow and a target in the scene
  • clipExtents are half-widths in world units, finest first, strictly increasing
  • call trackCaster(object) for movers; it enables layer VIRTUAL_SHADOW_MOVER_LAYER on the object and its descendants, tracking or untracking refreshes cached levels once, and subsequent mover movement refreshes only when a window moves
  • call object.layers.set(VIRTUAL_SHADOW_CASTER_LAYER) for a mesh that exists only to cast; the level cameras already render that layer and the main camera never does

Options

  • bias, biasNode, normalBias, intensity, radius, blurSamples, mapType and filterNode stay on light.shadow; mapSize and the other options here have defaults, and marker: false silences the TN_VIRTUAL_SHADOW line, not the measurement
  • bias, biasNode, normalBias, intensity, radius, blurSamples, mapType and filterNode stay on light.shadow; mapSize and the other options here have defaults
  • followViewFocus: false keeps eye follow; receiverPlaneBias: false uses only authored bias

warmUpScene

function · import { warmUpScene } from "@threenative/core"

Warms up scene for camera, in slices, presenting a frame between each. Fail closed on a nonsensical slice size rather than quietly choosing one: a zero or negative slice would loop forever, and a caller that passed it has a bug worth seeing now.

ts
async function warmUpScene( renderer: IWarmUpRenderer, scene: Object3D, camera: Camera, options: IWarmUpOptions =

Use it to

  • compile a scene's shaders during the loading screen instead of on the first frame
  • stop a native launch freezing for seconds inside its first rendered frame

Example

ts
await warmUpScene(renderer, scene, camera, { onProgress: (p) => setLoading(p) });

WaterSurface3D

class · import { WaterSurface3D } from "@threenative/core"

Give a horizontal water surface the world mirrored in it, the world beneath it, and the metres of water between them.

ts
class WaterSurface3D

Use it to

  • reflect the sky and the shoreline in a lake, pond or river
  • see the bed through the water and have the shallows fade at the shore
  • know how deep the water is under a pixel without a second render pass
  • stop a water surface repeating in visible bands or stripes
  • keep a crowd of small actors out of the water's reflection so the frame can afford it
  • stop the water reflection redrawing the whole world every frame
  • automatically reflect terrain and large casters without redrawing instanced small props

Example

ts
const surface = new WaterSurface3D({ level: 0, maxThickness: 3, reflection: { resolutionScale: 0.5 } });
material.colorNode = mix(surface.refractionAt(offset), surface.reflectionAt(offset), fresnel);

Good to know

  • it draws nothing; the game supplies the mesh, the material and every colour
  • the material must be transparent so the frame beneath it is already drawn
  • thickness is metres, saturating at maxThickness; sky behind the surface reads deep
  • one reflection is a second draw of the world; resolutionScale is its pixels only
  • omitted reflection.layers automatically reflects terrain-sized surfaces and large non-instanced casters, excluding instanced/skinned props; TN_WATER_REFLECTION_DEFAULT reports the set once
  • reflection.refreshInterval is in presented frames; 1 is every frame, and the default
  • the mirror plane is level, from level alone; do not parent target to a scaled mesh

Options

  • reflection.layers is an explicit Three Layers mask and wins over automatic filtering, including layer-0 mask 1; reflection.minSize overrides the default 10 metre minimum extent

WaveField

class · import { WaveField } from "@threenative/core"

Evaluate analytic waves on CPU and displace game-owned vertices with the matching TSL graph. wanted, and the warp jacobian, every slope and both allocations are skipped

ts
class WaveField

Use it to

  • float a boat on waves
  • make water move
  • find the water surface height at a point

Example

ts
const field = new WaveField({ waves });
const { height, normal } = field.sample(x, z, elapsed);
const lift = field.heightAt(x, z, elapsed);

Good to know

  • supply every wave amplitude, wavelength, direction, speed and warp value
  • call setTime for the default graph clock when the game advances its own time
  • sample allocates a result and a normal vector; ask heightAt when only the height is

withVelocityContext

function · import { withVelocityContext } from "@threenative/core"

Provision screen-space motion data for temporal nodes and keep per-instance history at the frame boundary.

ts
function withVelocityContext<T>(node: T, source: Node): T

Use it to

  • keep a skinned character or instanced crowd stable in a temporal stage
  • add the velocity output to a Three.js scene pass
  • hand the provisioned velocity source through a composed temporal graph

Example

ts
const scenePass = pass(scene, camera);
ensureVelocityOutput(scenePass);
renderer.setOutputNode(scenePass);
const tracker = new VelocityTracker();
tracker.update(scene);
renderer.render(scene, camera);
tracker.commit(scene);

Good to know

  • call VelocityTracker.update() before the render and commit() after it
  • a pass is only given a velocity target when a temporal stage consumes it

zenithTransmittance

function · import { zenithTransmittance } from "@threenative/core"

Return direct vertical transmittance for the supplied atmosphere.

ts
function zenithTransmittance( parameters: IAtmosphereParameters | IResolvedAtmosphereParameters, ): AtmosphereRgb

Use it to

  • check a supplied atmosphere's direct vertical transmittance

Example

ts
const zenith = zenithTransmittance({ rayleigh, mie, ozone, planetRadius, atmosphereRadius });

Good to know

  • use the returned value as a validation oracle; the rendered path samples the LUT

acceptHotUpdate

function · import { acceptHotUpdate } from "@threenative/core/hot"

Register hot-reload state preservation for a game.

ts
function acceptHotUpdate<TState extends Record<string, unknown>, TPhysics>( game: IGame<TState, TPhysics>, hot: IImportMeta["hot"], ): void

Use it to

  • keep game state while editing source in development
  • diagnose state shape changes during hot reload

Example

ts
acceptHotUpdate(game, import.meta.hot);

Good to know

  • use only in the web development entry

assertPortableState

function · import { assertPortableState } from "@threenative/core/hot"

Validate that hot-reload state can cross the Vite boundary.

ts
function assertPortableState(state: unknown): void

Use it to

  • preserve JSON-shaped state during hot reload
  • reject a non-portable game state before reload

Example

ts
assertPortableState(game.state.getState());

Good to know

  • state must contain finite numbers and plain objects only

connect

function · import { connect } from "@threenative/core/net"

Open a bounded, authenticated WebTransport message channel shared by browser and native games.

ts
function connect(url: string, options: INetworkOptions): Promise<INetworkConnection>

Use it to

  • connect two game clients over the portable WebTransport seam
  • send ordered actions and bounded unreliable state messages between game clients
  • exchange multiplayer messages without putting replication or gameplay in the engine

Example

ts
const connection = await connect("https://game.example/game", { applicationProtocol: "my-game/1", credential, channels: [{ id: 1, delivery: "unreliable" }] });

Good to know

  • the URL must use HTTPS and the credential is supplied by the game's identity flow; this API never issues credentials
  • channels, message sizes, and queues are validated before WebTransport opens, and reliable overflow returns false
  • native qualification depends on the installed host WebTransport bridge; iOS remains unverified

Options

  • connectTimeoutMs, maxReliableMessageBytes, maxQueuedReliableBytes, and maxQueuedDatagrams are named per-connection limits

playtest

function · import { playtest } from "@threenative/core/playtest"

Install the playtest bridge into a portable game.

ts
function playtest< TState extends Record<string, unknown> = Record<string, unknown>, TPhysics = undefined, >(options: IPlaytestOptions =

Use it to

  • expose movement and world observations to a playtest
  • connect a game to the ThreeNative scenario runner

Example

ts
const game = defineGame({ plugins: [playtest()] });

Good to know

  • install once in the game's plugin list

createReactOverlay

function · import { createReactOverlay } from "@threenative/core/react"

@threenative/core/react — React that renders on a phone, with no DOM and no WebView. A subpath on purpose. react and react-reconciler are optional peers, and nothing in @threenative/core's main entry imports either of them, so a game that never mounts a React overlay pays nothing and core stays consumable from React Three Fiber. Importing this module is the opt-in. The element vocabulary is two components, borrowed from React Native rather than invented: View is a rectangle, Text is a run of glyphs. They are components and not lowercase intrinsics because view and text are already SVG tags in @types/react, and a HUD element that silently means <svg:text> on one platform is exactly the kind of quiet divergence this path exists to end.

ts
function createReactOverlay(options: IReactOverlayOptions): IReactOverlay

Use it to

  • render a React HUD on Android or iOS without a WebView
  • show the same React component on web and on a phone
  • show a React HUD on Android, iOS or desktop native
  • render the same React component on web and on a phone without a WebView

Example

ts
const overlay = createReactOverlay({ canvasLayer: ctx.canvasLayer });

Good to know

  • styling is the style prop; Tailwind class names are CSS and cannot cross
  • import react, never react-dom, from the portable native entry
  • import react, never react-dom, from a native entry

measureText

function · import { measureText } from "@threenative/core/react"

Width in pixels of a glyph run at a given cell height.

ts
function measureText(text: string, fontSize: number, letterSpacing = 0): number

Use it to

  • measure native React HUD text before laying it out

Example

ts
const scoreWidth = measureText("SCORE 10", 24)

supportedGlyphs

function · import { supportedGlyphs } from "@threenative/core/react"

Every character this glyph set can draw, for error messages and for the templates' AGENTS.md.

ts
function supportedGlyphs(): string

Use it to

  • discover which characters a native React HUD can draw

Example

ts
supportedGlyphs().includes("A")

supportedStyleKeys

function · import { supportedStyleKeys } from "@threenative/core/react"

Every style key the overlay implements, for the templates' AGENTS.md and for error messages.

ts
function supportedStyleKeys(): readonly string[]

Use it to

  • discover which React HUD style properties work on native

Example

ts
supportedStyleKeys().includes("centerX")

Text

function · import { Text } from "@threenative/core/react"

A run of bitmap glyphs, drawn as one instanced quad per lit pixel.

ts
function Text(props: ITextProps): ReactNode

Use it to

  • show text in a native React HUD without a DOM

Example

ts
<Text style={{ color: "#ffffff", fontSize: 24 }}>SCORE 10</Text>

Also found by

  • objective panel journal

View

function · import { View } from "@threenative/core/react"

A rectangle. Paints when its style has a background; otherwise it only positions children.

ts
function View(props: IViewProps): ReactNode

Use it to

  • group and position native React HUD elements

Example

ts
<View style={{ centerX: true, top: 24 }}><Text>READY</Text></View>

connectUiBridge

function · import { connectUiBridge } from "@threenative/core/ui-layer"

Open the message channel between a game and its UI, whatever host is underneath.

ts
function connectUiBridge(options: IConnectOptions): IUiBridge

Use it to

  • write a UI that talks to the game on web and on a phone alike
  • send a message from a HUD rendered over the game surface
  • connect game code to a platform-owned UI realm
  • share one UI bridge implementation across web, Android, iOS, and desktop

Example

ts
const bridge = connectUiBridge({ end: "ui" });

Good to know

  • the transport is discovered, never configured; no game names the web view

onUiIntent

function · import { onUiIntent } from "@threenative/core/ui-layer"

Handle the actions a UI sends back to the game.

ts
function onUiIntent( bridge: IUiBridge, listener: (intent: string, payload: unknown) => void, ): () => void

Use it to

  • restart or pause a game from a button in its HUD
  • handle a HUD button action in game code
  • route native and web UI commands through one listener

Example

ts
onUiIntent(bridge, (intent) => { if (intent === "restart") game.goto("Play"); });

Good to know

  • prefer game.ui.onIntent, which connects the bridge for you

publishHitRegions

function · import { publishHitRegions } from "@threenative/core/ui-layer"

Tell the native input host where a UI's touchable controls are.

ts
function publishHitRegions(options: IRegistryOptions): IHitRegionRegistry

Use it to

  • let a touch on empty HUD space reach the game instead of the UI
  • make a HUD button receive taps on a phone
  • give native input hosts the rectangles claimed by UI controls
  • keep touch hit testing aligned with a moving web or native HUD

Example

ts
publishHitRegions({ bridge });

Good to know

  • mark controls with data-tn-interactive; pointer-events is not the mechanism

publishUiState

function · import { publishUiState } from "@threenative/core/ui-layer"

Publish the game's state so a UI in another process can mirror it.

ts
function publishUiState<T>( bridge: IUiBridge, store: IPublishableStore<T>, options: IPublishOptions =

Use it to

  • show score or health in a UI rendered over the game surface
  • keep a HUD in step with the game without re-rendering on the loop
  • publish game state to a HUD in another realm
  • keep a web and native UI mirror on the same coalesced state stream

Example

ts
publishUiState(bridge, game.state);

Good to know

  • publishes once per rendered frame unless stateFlushMs selects a slower interval, and not at all with no UI listening

Also found by

  • journal objective panel
  • readable HUD

sendUiIntent

function · import { sendUiIntent } from "@threenative/core/ui-layer"

Send a player action from the UI back to the game.

ts
function sendUiIntent(bridge: IUiBridge, intent: string, payload?: unknown): void

Use it to

  • wire a Restart button in a HUD to the running game
  • pause a game from a menu drawn over its surface
  • send a button or menu action from a UI HUD to game code
  • keep UI input portable across web and native hosts

Example

ts
sendUiIntent(bridge, "restart");

Good to know

  • one-way; the game decides what each name means and may ignore one

subscribeUiState

function · import { subscribeUiState } from "@threenative/core/ui-layer"

Mirror the game's published state on the UI side.

ts
function subscribeUiState<T>(bridge: IUiBridge): IUiStateMirror<T>

Use it to

  • read game state from a HUD that runs in the platform's web view
  • read published game state from a UI HUD
  • subscribe a web or native UI to the game's mirrored state

Example

ts
const mirror = subscribeUiState(bridge);

Good to know

  • returns undefined until the game publishes its first state

cellPlacements

function · import { cellPlacements } from "@threenative/core/world"

Borrow the run's placement records as a live view over the placement buffer.

ts
function cellPlacements(placements: ArrayBuffer, run: IWorldRun): Float32Array

Use it to

  • feed one cell's instance transforms into a batch without copying

Example

ts
const records = cellPlacements(buffer, { asset: "tree", offset: 0, count: 120 });

Good to know

  • the returned view aliases the caller's buffer; writing to it mutates the source

getWorldCapabilities

function · import { getWorldCapabilities } from "@threenative/core/world"

Resolve the active world-generation path from host capability facts. The function accepts the adapter facts instead of reaching through a renderer-specific global, so browser and native hosts can report the same object. Missing limits are not treated as infinite: a host must either provide a valid GPU limit report or explicitly choose CPU fallback. GPU generation remains unavailable until a GPU readback can own the canonical field; a host adapter report therefore never upgrades a CPU fallback into a GPU generation claim.

ts
function getWorldCapabilities(options: IWorldCapabilitiesOptions =

Use it to

  • decide whether generated terrain can use GPU compute
  • report why terrain generation is using a reduced CPU fallback

Example

ts
const capabilities = getWorldCapabilities({ limits: adapter.limits, cpuFallbackIterations: 8 });

Good to know

  • unsupported is returned when compute limits are unknown or below the requirement; callers must not silently continue

Options

  • minimumWorkgroupsPerDimension, minimumStorageBufferBindingSize, and cpuFallbackIterations

Heightfield

class · import { Heightfield } from "@threenative/core/world"

One height buffer shared by world queries, rendered geometry, and a physics heightfield. The game supplies every value, so changing the terrain's shape never requires a package edit. fromSampler evaluates that game function exactly once at each vertex and retains only the resulting numbers. Queries interpolate those same numbers instead of evaluating the function again.

ts
class Heightfield extends Group implements IComputeDriven

Use it to

  • build terrain geometry and collision from one game-authored height function
  • generate a terrain a player can walk across
  • query the same ground height or normal that a player sees and collides with
  • ask how high the ground is here
  • build islands and coastlines from terrain
  • keep a collider or other copy of a deforming terrain in step without rescanning the whole field (trackChanges)

Example

ts
const field = Heightfield.fromSampler({ rows: 65, columns: 65, width: 64, depth: 64, origin: { x: 0, z: 0 }, sampleHeight: terrainHeight });

Good to know

  • sampleHeight owns the terrain shape and stays in game source; the framework stores and interpolates its output
  • rows and columns are vertex counts; geometry is row-major z-then-x and collider export transposes once into Rapier's column-major matrix order

Options

  • rows, columns, width, depth, origin, and sampleHeight are explicit on every field

heightSamplerFromHeightmap

function · import { heightSamplerFromHeightmap } from "@threenative/core/world"

Build a game-usable sampleHeight from a raw v1 heightmap. The returned function interpolates bilinearly in world units and clamps to the map edges, so it plugs straight into Heightfield.fromSampler and TerrainTiles. Height is heightMin + v / 65535 * (heightMax - heightMin) at vertex (column, row).

ts
function heightSamplerFromHeightmap( terrain: IWorldTerrain, extent: IWorldExtent, data: Uint16Array, ): (x: number, z: number) => number

Use it to

  • turn an exported raw heightmap into terrain collision and rendering
  • query ground height from a Blender-authored world package

Example

ts
const sampleHeight = heightSamplerFromHeightmap(terrain, extent, await loadWorldHeightmap(url));

Good to know

  • the sampler reads the game's data; the framework never selects a terrain shape

loadTerrainSplat

function · import { loadTerrainSplat } from "@threenative/core/world"

The splat terrain surface a world package describes, for WorldCells.load({ surface }). Layers blend over a base by mask channels read as linear data (the masks ship raw, beside the heightmap, so no cook moves a blend threshold), with noise-broken edges and macro brightness variation. Texture sets tile in world metres on the package's ground plane (x, -z: a Z-up authoring tool's x and y), cliffs can be triplanar, and the base plus any layer that asks carries a normal map. Nothing here is a look choice: textures, tiles, tints, thresholds and noise scales all come from the package's table, which the game authors once and its DCC shares.

ts
async function loadTerrainSplat(options: ILoadTerrainSplatOptions): Promise<Material>

Use it to

  • terrain textured by splat masks exported from Blender with the world package
  • the game's terrain should match the DCC's terrain material without a second copy

Example

ts
const surface = await loadTerrainSplat({ assets: ctx.assets, url: "world/world.json" });
const world = await WorldCells.load({ assets: ctx.assets, url: "world/world.json", surface, follow, ring: 2 });

Good to know

  • the package's world.json must carry terrain.layers.table and terrain.layers.splat, written by the export_terrain_layers.py recipe
  • WebGPU allows 16 sampled textures per stage: planes + diffuse maps + normal maps must fit

Options

  • every value comes from the package's table; the returned material is the game's to adjust

loadWorldHeightmap

function · import { loadWorldHeightmap } from "@threenative/core/world"

Fetch a raw little-endian uint16 heightmap and expose it as samples.

ts
async function loadWorldHeightmap(url: string): Promise<Uint16Array>

Use it to

  • load a world package's heightmap once before building terrain

Example

ts
const data = await loadWorldHeightmap("/world/terrain/heightmap.u16");

Good to know

  • a non-OK response throws; bytes are byte-swapped only on a big-endian host

snowDiscFootprint

function · import { snowDiscFootprint } from "@threenative/core/world"

A circular contact footprint, sized from a radius the caller measures. The shape a sphere, a ball, a wheel or a probe presses into snow, and the default the physics binding derives from a sphere's contact geometry. Flat inside the radius, eased over the softness band, with a raised rim just outside it.

ts
function snowDiscFootprint( radius: number, options: ISnowDiscFootprintOptions =

Use it to

  • press a ball, wheel or probe into snow
  • give a sphere a physically sized snow contact instead of a boot shape

Example

ts
const footprint = snowDiscFootprint(0.25);

Good to know

  • radius is metres; the footprint never grows with load, only deeper

Options

  • softness widens the eased edge without changing the contact radius

SnowField

class · import { SnowField } from "@threenative/core/world"

Persistent snow deformation over one canonical heightfield. Snow keeps four channels per cell — indentation, displaced bank, compaction and disturbance — and writes the combined surface (terrain + depth + bank - indent) back into the heightfield it was given. Queries, rendered geometry and collider export therefore all read one surface, and no second terrain representation exists. The field is numeric only: it never chooses a boot, a tread, a particle, a material or a camera. Games supply terrain heights, the snow depth, the contact profile and the response coefficients; this class owns the storage, the load-dependent penetration and the bounded recovery. It is a heightfield approximation — not granular snow, displaced-volume conservation, avalanches, melting or a calibrated material law.

ts
class SnowField

Needs

  • @threenative/core/world Heightfield as the canonical terrain and surface

Use it to

  • leave footprints, tracks and tyre ruts in snow that persist and fill in over time
  • let a pushed sphere carve a connected track and a dropped one settle into a crater
  • store snow deformation that rendered geometry and collision both read
  • reset a snowfield between rounds or change its depth at runtime
  • show how packed the snow is where people have walked or objects have rested

Example

ts
import { Heightfield } from "@threenative/core/world";
const terrain = Heightfield.fromSampler({ rows: 129, columns: 129, width: 64, depth: 64, origin: { x: 0, z: 0 }, sampleHeight: (x, z) => Math.sin(x * 0.1) * 0.5 });
const snow = new SnowField({ field: terrain, depth: 0.28, hardness: 0.3 });
snow.stamp({ x: 0, z: 0, area: 0.074, load: 784, duration: 0.1, footprint: snowDiscFootprint(0.12) });
snow.recover(1 / 60, 0.0012, 0.4);

Good to know

  • the field composes onto a Heightfield; construct the terrain first and let this own the surface
  • zero depth is bare ground: contacts register no indentation at all
  • out-of-region heightAt and normalAt follow Heightfield's error contract; sample returns zeros

Options

  • depth, hardness, yieldFraction, maxBank and responseTime name the response coefficients

TerrainTiles

class · import { TerrainTiles } from "@threenative/core/world"

Stream a bounded square of game-authored heightfields and keep their render and physics units together. The class composes ordinary THREE.LOD objects and leaves frustum/projection culling to the renderer's existing scene path.

ts
class TerrainTiles extends Object3D implements IComputeDriven

Use it to

  • stream terrain without cracks
  • keep generated terrain resident around a moving player
  • put a generated terrain tile into a game-owned physics world

Example

ts
const tiles = new TerrainTiles({ sampleHeight, surface: gameSurface(), tileSize: 256, tileResolution: 129, residentTileBudget: 25, residentByteBudget: 32_000_000 });

Good to know

  • sampleHeight and surface are required game choices; no landform or surface preset is installed
  • residentTileBudget and residentByteBudget are hard caps; a tile that cannot fit throws
  • seam gap, LOD pop and the rendered-vertex finiteness scan are measurements that are off by default; TN_TERRAIN_VALIDATE=1, ?tnTerrainValidate=1 or validate: true runs them, and maxSeamGap, maxVisualSeamGap and maxLodPop report undefined while they are off

Options

  • tileSize, tileResolution, lodFactors, lodDistances, skirtDepth, streamRadius, colliderRadius, mergeTiles, validate, and budgets

Also found by

  • stream terrain across chunks

terrainValidationRequested

function · import { terrainValidationRequested } from "@threenative/core/world"

Whether TN_TERRAIN_VALIDATE asks for terrain validation on this launch.

ts
function terrainValidationRequested(): boolean

Use it to

  • turn terrain's per-frame seam, LOD pop and vertex checks on for one run
  • assert the terrain geometry a game streams before it ships

Example

ts
// Reads its own the way `renderListValidationRequested` does: a native launch sets the
// environment variable, a browser asks with the query string, a test or harness sets the
// global. `0` and `false` are off, so a saved URL that enabled it still says "off".
const tiles = new TerrainTiles({ ...options, validate: terrainValidationRequested() });

Good to know

  • off by default: it is the work it checks, every frame

validateWorldPackage

function · import { validateWorldPackage } from "@threenative/core/world"

Validate a world.json manifest against the v1 contract. Never throws on garbage input: a non-object manifest is WORLD_MALFORMED. Every problem is collected, so an exporter sees the complete list at once.

ts
function validateWorldPackage( manifest: unknown, options: IWorldPackageValidationOptions, ):

Use it to

  • check a Blender-exported world package before the runtime attaches anything
  • report why a world package cannot be streamed

Example

ts
const { ok, errors } = validateWorldPackage(json, { placementsByteLength: buffer.byteLength });

Good to know

  • validation only checks structure and ranges; it never fetches the heightmap or GLBs

WorldCells

class · import { WorldCells } from "@threenative/core/world"

Stream a Blender-authored world package by cell and keep it resident around a followed point. The class composes TerrainTiles for the package's heightmap, builds one InstancedBatch per resident cell asset run, distance level and mesh part, and loads hand-placed chunk GLBs through loadAll + addInSlices. Ring residency, per-asset maxDistance filtering, the per-asset lods levels, hard budgets and generation-tokened cancellation all live here; every geometry, material and surface still comes from the package's GLBs and the game. An asset is drawn per part, not per model: a GLB with several primitives is one InstancedBatch each, and a scattered part whose own material is transparent draws as an alpha cutout unless the game asks for blending, because an InstancedMesh cannot sort its instances. An asset whose package entry names no lods is drawn at the levels its own model carries a baked AutoLOD chain for: the levels an instanced draw cannot reach by itself, switched at the distance their error projects over the autoLod viewport. Authored lods win; a chain is only a fallback.

ts
class WorldCells extends Group implements IComputeDriven

Use it to

  • stream a large Blender-authored world by cell instead of one huge GLB
  • keep scattered props and hand-placed chunks resident around a moving player
  • honour per-asset draw distances and hard streaming budgets without a mid-frame throw

Example

ts
const world = await WorldCells.load({ url: "/world/world.json", surface, follow, ring: 1, budgets: { residentCells: 25, instances: 20000, bytes: 8000000 } });
scene.add(world);
world.update();

Good to know

  • surface is the game's; this class creates no material, colour or geometry
  • budgets are hard caps that report pressure instead of over-committing
  • model loads are bounded by concurrency (default 12) across every resident cell, not per cell
  • refilters are bounded by rebuildsPerUpdate (default 16) per update, nearest cell first
  • admission is bounded by admissionBudgetMs (default 2) per update across every path, plus at most one unit each for terrain and props; while props are queued terrain takes at most half, so neither starves the other, and a deferred cell keeps drawing what it has
  • SkinnedMesh parts are skipped; an instanced copy would draw one rest pose
  • a baked chain's switch distances are measured against autoLod (default 4 px of error over 60° and 1080 raster rows), because an instanced draw cannot select a level per instance; an asset with authored lods never consults it
  • prewarmed resolves once every prewarmed shared batch has been drawn; a game with a loading screen waits on it, and stats().pendingPrewarm is the same gate as a number
  • every asset:level:part is one InstancedMesh for the main pass, plus one caster InstancedMesh per world-grid square of clusterSize on the shadow caster layer, so the main pass draws one mesh per key and a shadow level submits only the squares it covers
  • two definitions the asset loader resolves to one model — the same cooked glb, the same lods at the same distances, the same maxDistance and bounds — are one asset under the lexicographically smallest id: one model load, one set of asset:level:part keys, one prewarm and one refcount, released when the last cell holding any member of the group leaves the ring; TN_WORLD_ASSET_ALIAS reports how many of the package's assets are really distinct
  • the main pass mesh draws only the squares the render camera's frustum covers — on by default, narrowed once per frame for every main batch by the engine's render-cadence dispatch, never for an orthographic camera — a batch with nothing to draw is hidden rather than submitted at count 0, and TN_WORLD_MAIN_CULL reports both every five seconds
  • a loaded chunk is merged by material before it is added, so it submits one draw per material rather than one per node; a skinned, multi-material or morph-target mesh, one carrying a baked AutoLOD chain, and an instanced mesh past chunkMergeMaxTriangles (default 43,690 triangles) or with a shape over 2,048 triangles, all keep their own geometry; a material group crossing 131,072 vertices (4 MiB of position + normal + uv) is split into several meshes in traversal order instead of one giant upload, indexed parts keep their index, and TN_WORLD_CHUNK_MERGE reports what the merge did and the bytes it left
  • shadows.castDistance is accepted and ignored (clusters replaced it); shadows.invalidate is called at most once a second after streamed records changed

Options

  • ring, budgets, terrain tile size/resolution, terrain stream and collider radius, transparentScatter, clusterSize and shadows.invalidate, load concurrency, rebuildsPerUpdate, admissionBudgetMs and the package's per-asset maxDistance

renderer.matrixWorld

function · src/game.ts

The per-frame world-matrix walk, on by default as "visible": a hidden subtree — a full-detail body behind a merged stand-in, a hidden LOD level, a parked or hangared model — is not recursed into, so nothing that cannot draw pays a world-matrix multiply. Every visible node is composed exactly as three does, a class that overrides updateMatrixWorld (SkinnedMesh, Camera) runs its own, and a hidden node that holds bones is still walked. "all" restores three's every-node walk.

ts
renderer.matrixWorld?: "visible" | "all"

Use it to

  • updateMatrixWorld and multiplyMatrices are hot in a profile
  • the per-frame matrix walk is slow with many hidden LOD bodies or paired full/hull models
  • stop paying for matrices of models nothing can draw
  • a skinned mesh or camera goes stale because its world matrix was not refreshed
  • restore three's own full updateMatrixWorld walk
  • compare how many scene nodes the engine walks per frame

Example

ts
renderer: { matrixWorld: "all" } // in threenative.config.ts; omit for the visible-only default

Good to know

  • Unset is the shipping behaviour: "visible", which does not recurse into a hidden subtree. "all" visits every node exactly as three's own updateMatrixWorld does.
  • A game that reads a hidden object's matrixWorld directly must not rely on the walk reaching it: use getWorldPosition/getWorldQuaternion/getWorldScale or call object.updateWorldMatrix(true, false) first.
  • The walk is the engine's either way, so three's renderer never walks the scene a second time; the count of nodes visited is reported as matrixWorld in every TN_PROJECTION window.
  • Bones are never skipped: a hidden node that holds a Bone is walked, because a visible SkinnedMesh draws with its skeleton's matrices wherever the armature sits.

Options

  • renderer.matrixWorld: "all" restores three's every-node walk; the visited count still reports

renderer.minimumProjectedPixels

function · src/game.ts

Do not submit what the render camera cannot resolve. On by default at a conservative 0.5 projected pixel; an object below it is skipped per render camera. Raise the number to cull more, set false to leave every object drawn — the count of what was skipped still reports in TN_PROJECTION.

ts
renderer.minimumProjectedPixels?: number | false

Use it to

  • my frame is slow with many distant objects
  • draw count is high but the screen is mostly empty
  • far away models, aircraft, boats or props cost draw calls but are specks
  • a large roster or fleet drops the frame rate while barely visible
  • stop submitting objects smaller than a pixel to the camera
  • cull by how big something looks to the camera rather than how far it is from the player
  • tune how aggressively distant objects are skipped
  • a small object I need disappeared at range

Example

ts
renderer: { minimumProjectedPixels: 2 } // in threenative.config.ts

Good to know

  • Unset is the shipping behaviour: the gate runs at 0.5 px. A game that wants the cut a shipped title tuned names 2; false leaves every object drawn.
  • The decision reads the render camera's projection and viewport, not the player's distance — a camera far from the player still culls its own specks.
  • It writes only object.visible, which the projection's batch key ignores; castShadow, layers and frustumCulled are never flipped, because that churns batch grouping.
  • Shadow casters and objects attached to the render camera are never dropped on the main view alone. Exempt any other object with alwaysRender.
  • Turning the gate off with false does not turn its measurement off: TN_PROJECTION still reports considered and skipped counts as cull.

Options

  • alwaysRender(object) keeps one object drawn whatever the render camera resolves
  • renderer.minimumProjectedPixels: false leaves the scene drawn and keeps the measurement on

renderer.projection

function · src/game.ts

The engine's scene-render projection — an internal mirror that collapses repeated draws, including animated skinned rigs that share a geometry and material into one palette draw per pass — on by default. Set renderer.projection: false to decline it, or projection: { materialChecks: 'everyFrame' } to keep it and pay for a per-material check on every frame.

ts
renderer.projection?: boolean | { materialChecks?: 'spread' | 'everyFrame' }

Use it to

  • a crowd of animated characters draws slowly
  • many SkinnedMesh copies of one rig, each its own draw call
  • the game got slower after the projection engaged
  • turn off the render projection, batching, or the instanced mirror
  • draw count fell but frame time did not
  • a multi-second freeze when the mirror first engages
  • opt out of an engine render optimizer
  • thousands of props each with their own material, one colour apart
  • a material edit takes a few frames to show up
  • check every batched material every frame anyway

Example

ts
renderer: { projection: false } // in threenative.config.ts

Good to know

  • Unset is the shipping behaviour: the projection runs. Only an explicit false declines it.
  • An opted-out game builds no mirror and runs no eligibility scan; the authored scene is what renders, so declining costs nothing rather than being re-judged each frame.
  • TN_RENDER_PROJECTION still reports the verdict, with reasonCode disabled rather than one of the measured declines.
  • materialChecks: 'spread' is the default: a bounded slice of the batched materials is proved per frame instead of all of them, so a frame of 4,096 colour-only materials costs 512 checks rather than 4,096. A base-colour edit is never delayed — that write is O(1) per member.
  • The price of spread is staleness on every other material edit: a material that gains a roughness, a map or a define still leaves its group and is drawn exactly, up to materialCheckStaleFrames frames later. TN_RENDER_PROJECTION reports that bound; materialChecks: 'everyFrame' sets it to 0 and restores the per-member, per-frame check.
  • Any other materialChecks value throws at startup rather than falling back to a default.

Options

  • renderer.projection: false declines the whole mirror and costs nothing to decline
  • renderer: { projection: { materialChecks: 'everyFrame' } } proves every batched material every frame instead of the default bounded slice
View source on GitHub ↗