Skip to content

@gameable/character ​

Classes ​

CharacterUnsupportedError ​

Thrown by createCharacter when the renderer is not on the WebGPU backend.

Characters are a WebGPU-only feature by construction: the rig deform, the lift and the decoders all run as compute on renderer.backend.device, and the WebGL fallback backend has no device to borrow. There is no CPU path — the POC had one and it cost seconds per frame, which reads as a hung avatar rather than as a slow one. Check engine.caps.characters and substitute a placeholder.

Example ​

ts
import { CharacterUnsupportedError, createCharacter } from 'gameable/character';

try {
  await createCharacter(bundle, { renderer, scene, sink });
} catch (err) {
  if (err instanceof CharacterUnsupportedError) console.warn(err.reason);
}

Extends ​

  • Error

Constructors ​

Constructor ​
ts
new CharacterUnsupportedError(reason): CharacterUnsupportedError;
Parameters ​
ParameterType
reasonstring
Returns ​

CharacterUnsupportedError

Overrides ​
ts
Error.constructor

Properties ​

name ​
ts
readonly name: "CharacterUnsupportedError" = 'CharacterUnsupportedError';
Overrides ​
ts
Error.name
reason ​
ts
readonly reason: string;

Why the renderer could not host a character.


DebugVertexLift ​

One isotropic gaussian per rig vertex, in a sink's slot range.

Example ​

ts
import { DebugVertexLift } from 'gameable/character';

const lift = new DebugVertexLift({
  device,
  sink,
  vertsBuffer: backend.vertsBuffer,
  vertexCount: backend.vertexCount,
});
const encoder = device.createCommandEncoder();
backend.encode(encoder);
lift.encode(encoder);
device.queue.submit([encoder.finish()]);
sink.markGaussiansChanged();

Constructors ​

Constructor ​
ts
new DebugVertexLift(options): DebugVertexLift;

Build the pipeline and reserve the slots.

Parameters ​
ParameterTypeDescription
optionsDebugVertexLiftOptionsSee DebugVertexLiftOptions.
Returns ​

DebugVertexLift

Throws ​

When the supplied range is smaller than the vertex count, which would silently draw part of a head.

Properties ​

range ​
ts
readonly range: SlotRange;

The slots this preview owns. Fixed for its lifetime.

vertexCount ​
ts
readonly vertexCount: number;

Vertices it draws.

Accessors ​

params ​
Get Signature ​
ts
get params(): Readonly<DebugLiftParams>;

The params as they will next be uploaded. Read-only; use the setters.

Returns ​

Readonly<DebugLiftParams>

The current parameters.

Methods ​

dispose() ​
ts
dispose(): void;

Release the GPU buffers and, when it allocated the range, the slots. Idempotent.

Returns ​

void

encode() ​
ts
encode(encoder): void;

Record the pass. The rig's own pass must already be in this encoder, or an earlier submission: one device means one queue, so ordering alone is the synchronisation.

Parameters ​
ParameterTypeDescription
encoderGPUCommandEncoderThe frame's encoder.
Returns ​

void

setShading() ​
ts
setShading(shading): void;

Change the colour source.

Parameters ​
ParameterTypeDescription
shadingDebugShadingtint, normal or flat.
Returns ​

void

setSigma() ​
ts
setSigma(sigma): void;

Change the gaussian radius.

Parameters ​
ParameterTypeDescription
sigmanumberRadius in object space, metres.
Returns ​

void

setTransform() ​
ts
setTransform(transform, centroid?): void;

Change the rig -> object placement.

Parameters ​
ParameterTypeDescription
transformreadonly number[]Row-major 3x4 affine, 12 numbers.
centroid?readonly [number, number, number]Object-space centre for normal shading. Left alone when omitted.
Returns ​

void


GnmRigBackend ​

GNM as a RigBackend.

Implements ​

Constructors ​

Constructor ​
ts
new GnmRigBackend(options?): GnmRigBackend;
Parameters ​
ParameterType
optionsGnmRigBackendOptions
Returns ​

GnmRigBackend

Properties ​

kind ​
ts
readonly kind: "gnm";

Which implementation this is.

Implementation of ​

RigBackend.kind

Accessors ​

assets ​
Get Signature ​
ts
get assets(): AosRigPack;

The parsed pack — the topology and UVs a caller may want for a preview mesh.

Returns ​

AosRigPack

The pack parsed by init.

controlNames ​
Get Signature ​
ts
get controlNames(): readonly string[];

The control space this backend drives, in setControls order.

Returns ​

readonly string[]

The control space this backend drives, in setControls order.

Implementation of ​

RigBackend.controlNames

vertexCount ​
Get Signature ​
ts
get vertexCount(): number;

Vertices produced per pose.

Returns ​

number

Vertices produced per pose.

Implementation of ​

RigBackend.vertexCount

vertsAABB ​
Get Signature ​
ts
get vertsAABB(): VertsAABB;

Bounds of the neutral pose, for the sink's bounding sphere.

Returns ​

VertsAABB

Bounds of the neutral pose, for the sink's bounding sphere.

Implementation of ​

RigBackend.vertsAABB

vertsBuffer ​
Get Signature ​
ts
get vertsBuffer(): GPUBuffer;

The posed vertices, in the rig's own frame and units. Valid after the encoded pass has executed; the lift's copyBufferToBuffer is what reads it.

Returns ​

GPUBuffer

The posed vertices, in the rig's own frame and units. Valid after the encoded pass has executed; the lift's copyBufferToBuffer is what reads it.

Implementation of ​

RigBackend.vertsBuffer

Methods ​

bytes() ​
ts
bytes(): number;

Approximate GPU + wasm bytes this backend holds, for memoryReport().

Returns ​

number

Implementation of ​

RigBackend.bytes

dispose() ​
ts
dispose(): void;

Release the wasm heap and every GPU buffer. Idempotent.

Returns ​

void

Implementation of ​

RigBackend.dispose

encode() ​
ts
encode(encoder): void;

Record the per-vertex pass into encoder. No submit, no fence, no readback.

Parameters ​
ParameterType
encoderGPUCommandEncoder
Returns ​

void

Implementation of ​

RigBackend.encode

frameValues() ​
ts
frameValues(): object;

This frame's values for the blend, as encode would upload them: the expression coefficients (unscaled, padded to whole vec4s), the params block (eye positions at floats 4..6 and 8..10, the two gaze quaternions at 12..19) and the skin rows (three row-major vec4 per joint). The arrays are this backend's own; do not keep them.

Returns ​

object

Views over the backend's arrays.

expr ​
ts
expr: Float32Array;
params ​
ts
params: Float32Array;
skinRows ​
ts
skinRows: Float32Array;
init() ​
ts
init(options): Promise<void>;

Build the wasm/GPU resources. Idempotent; throws loudly on a bad asset.

Parameters ​
ParameterType
optionsRigBackendInit
Returns ​

Promise<void>

Implementation of ​

RigBackend.init

initWithoutDevice() ​
ts
initWithoutDevice(options): Promise<void>;

Parse the pack and set up everything the CPU side needs, without building the WGSL pipeline: for a head pass built elsewhere (the TSL node, gnmNode.ts), which reads frameValues each frame. encode and vertsBuffer stay unavailable.

Parameters ​
ParameterTypeDescription
optionsOmit<RigBackendInit, "device">As init, without the device.
Returns ​

Promise<void>

Nothing.

runCpu() ​
ts
runCpu(controls): Promise<Float32Array<ArrayBufferLike>>;

The CPU reference, for calibration. Never per frame — see gnmReference.ts.

Parameters ​
ParameterTypeDescription
controlsFloat32ArrayA head_ext vector; a short one is zero-padded and a long one truncated, since this runs off the calibration path rather than the hot one.
Returns ​

Promise<Float32Array<ArrayBufferLike>>

The posed vertices as (V,3) in METRES, skinned against the joint worlds currently set.

Implementation of ​

RigBackend.runCpu

setControls() ​
ts
setControls(controls): void;

Set the whole head_ext vector.

A shorter vector is zero-padded, which is exactly what the reduced ML view (68 floats) means — block.expand pads per region, so a caller holding the reduced view must widen it first rather than passing it here.

Parameters ​
ParameterTypeDescription
controlsFloat32ArrayUp to head_ext.dim floats: the expression coefficients, then the four gaze angles in radians. A longer vector throws.
Returns ​

void

Implementation of ​

RigBackend.setControls

setJointOverrides() ​
ts
setJointOverrides(overrides): boolean;

Per-joint rotation overrides, by name — the procedural head-aim path.

Applied as a parent-relative rotation on top of the joint's rest, then propagated down the pack's own parent chain, so aiming head carries the eyes with it exactly as skinning would.

Parameters ​
ParameterTypeDescription
overridesreadonly JointOverride[]Joint name plus a (w, x, y, z) rotation. A name this pack does not carry is skipped silently — the body rig has joints the head does not. Each call rebuilds from the rest pose, so overrides do not accumulate.
Returns ​

boolean

True when this call actually moved a joint. False on a repeat of the rotations already pushed, which is what an idle head sends 60 times a second — and on a repeat NOTHING is recomputed, so the caller may leave its own rig dirty flag down.

Implementation of ​

RigBackend.setJointOverrides

setJointWorlds() ​
ts
setJointWorlds(worlds): void;

Where the body rig's joints are this frame, J*16 row-major world matrices in the pack's own compact joint order.

Defaults to the pack's rest, which makes the skinning the identity — a head posed at bind. The animation layer supplies the real matrices; the head does not solve them, because neck and head rotation are the BODY's joints.

Parameters ​
ParameterTypeDescription
worldsFloat32ArrayJ*16 row-major world matrices, in METRES, in the pack's compact joint order. The skin rows are recomputed here, not per frame.
Returns ​

void


OrlRigBackend ​

OpenRigLogic as a RigBackend, in skinning or rigid-shell mode.

Implements ​

Constructors ​

Constructor ​
ts
new OrlRigBackend(options): OrlRigBackend;
Parameters ​
ParameterType
optionsOrlRigBackendOptions
Returns ​

OrlRigBackend

Properties ​

kind ​
ts
readonly kind: "orl";

Which implementation this is.

Implementation of ​

RigBackend.kind

Accessors ​

controlNames ​
Get Signature ​
ts
get controlNames(): readonly string[];

The control space this backend drives, in setControls order.

Returns ​

readonly string[]

The control space this backend drives, in setControls order.

Implementation of ​

RigBackend.controlNames

isDriving ​
Get Signature ​
ts
get isDriving(): boolean;

True once a shell-mode backend has adopted a frame and bound a shell.

Returns ​

boolean

True in skin mode always, and in shell mode once adoptRigSpace has matched at least one shell to a joint.

vertexCount ​
Get Signature ​
ts
get vertexCount(): number;

Vertices produced per pose.

Returns ​

number

Vertices produced per pose.

Implementation of ​

RigBackend.vertexCount

vertsAABB ​
Get Signature ​
ts
get vertsAABB(): VertsAABB;

Bounds of the neutral pose, for the sink's bounding sphere.

Returns ​

VertsAABB

Bounds of the neutral pose, for the sink's bounding sphere.

Implementation of ​

RigBackend.vertsAABB

vertsBuffer ​
Get Signature ​
ts
get vertsBuffer(): GPUBuffer;

The posed vertices, in the rig's own frame and units. Valid after the encoded pass has executed; the lift's copyBufferToBuffer is what reads it.

Returns ​

GPUBuffer

The posed vertices, in the rig's own frame and units. Valid after the encoded pass has executed; the lift's copyBufferToBuffer is what reads it.

Implementation of ​

RigBackend.vertsBuffer

Methods ​

adoptRigSpace() ​
ts
adoptRigSpace(lin, offset): boolean;

Place this branch's shells in RIG space, using the frame a sibling branch already fitted (the head's calibration).

This branch cannot fit that frame itself: its mesh is not in the DNA, so there is no correspondence to fit against. The head's frame is the right one to borrow because both branches are baked in a single bundle space — measured, the borrowed frame lands these shells 0.11-0.35 cm from the DNA's own eye and teeth meshes.

MUST run before calibration. Returns false when the branch stays neutral.

Parameters ​
ParameterTypeDescription
linFloat32ArrayThe sibling's fitted rig -> bundle linear part, nine floats in the row-vector convention v·lin + offset. Inverted here to map this branch's neutral back into rig space.
offsetFloat32ArrayThe matching translation, three floats.
Returns ​

boolean

True once at least one shell is bound to a joint. False — and the branch stays at its neutral pose — in skin mode, before init, when the pack carries no joint names, when the sibling's transform is singular, or when no shell centroid landed near an eye/teeth joint.

bytes() ​
ts
bytes(): number;

Approximate GPU + wasm bytes this backend holds, for memoryReport().

Returns ​

number

Implementation of ​

RigBackend.bytes

dispose() ​
ts
dispose(): void;

Release the wasm heap and every GPU buffer. Idempotent.

Returns ​

void

Implementation of ​

RigBackend.dispose

encode() ​
ts
encode(encoder): void;

Record the per-vertex pass into encoder. No submit, no fence, no readback.

Parameters ​
ParameterType
encoderGPUCommandEncoder
Returns ​

void

Implementation of ​

RigBackend.encode

init() ​
ts
init(options): Promise<void>;

Build the wasm/GPU resources. Idempotent; throws loudly on a bad asset.

Parameters ​
ParameterType
optionsRigBackendInit
Returns ​

Promise<void>

Implementation of ​

RigBackend.init

runCpu() ​
ts
runCpu(controls): Promise<Float32Array<ArrayBufferLike>>;

Calibration path. Deliberately uses the CPU deform even when the GPU one is live: corr = neutral - runCpu(baseRig) is added to every GPU-produced frame, so the two must agree.

Parameters ​
ParameterTypeDescription
controlsFloat32ArrayOne value per name in controlNames.
Returns ​

Promise<Float32Array<ArrayBufferLike>>

A fresh array of posed vertices as xyz triples, in CENTIMETRES — the rig's own mesh in skin mode, this branch's rigid-shell pose in shell mode. Never the reused scratch, since the caller keeps it.

Implementation of ​

RigBackend.runCpu

setControls() ​
ts
setControls(controls): void;

Solve at controls. CPU-only and synchronous — there is no inference call to await — so a caller may call it several times a frame and only encode once.

Parameters ​
ParameterType
controlsFloat32Array
Returns ​

void

Implementation of ​

RigBackend.setControls

setJointOverrides() ​
ts
setJointOverrides(overrides): boolean;

No addressable joint namespace yet — head aim rides the body rig, not the DNA.

Parameters ​
ParameterTypeDescription
overridesreadonly JointOverride[]Ignored. A non-empty list logs once, so a caller wondering why its aim does nothing is told rather than left guessing.
Returns ​

boolean

Always false: nothing moved, so a caller gating its rig dirty flag on this must NOT re-run a pass. That is the whole reason the method reports a result rather than returning void — an inert override used to force a full decode of an idle head every single frame.

Implementation of ​

RigBackend.setJointOverrides


SlotAllocator ​

A deterministic first-fit allocator over [0, capacity).

Example ​

ts
import { SlotAllocator } from 'gameable/character';

const slots = new SlotAllocator(262144);
const head = slots.allocate(65536); // { offset: 0, count: 65536 }
slots.free(head);

Constructors ​

Constructor ​
ts
new SlotAllocator(capacity): SlotAllocator;
Parameters ​
ParameterType
capacitynumber
Returns ​

SlotAllocator

Properties ​

capacity ​
ts
readonly capacity: number;

Accessors ​

available ​
Get Signature ​
ts
get available(): number;

Slots not currently owned by any range.

Returns ​

number

The total free slot count, which may be spread over several blocks and so is an upper bound on the largest allocation that can still succeed.

Methods ​

allocate() ​
ts
allocate(count): SlotRange;

Reserve count contiguous slots.

Throws rather than returning null: a branch that cannot be placed renders nothing at all, and a silent null would surface as a character missing one region with no error anywhere.

Parameters ​
ParameterTypeDescription
countnumberHow many splat slots the branch needs; a positive integer.
Returns ​

SlotRange

The reserved range: offset is the branch's slot offset into the sink's storage buffers and count echoes the request.

blocks() ​
ts
blocks(): SlotRange[];

The free list, for tests and memoryReport(). Copied, never the live array.

Returns ​

SlotRange[]

The free blocks in ascending offset order, each a fresh object, so a caller cannot corrupt the allocator by mutating what it reads.

free() ​
ts
free(range): void;

Release a range and coalesce with its neighbours.

Overlap with an already-free range throws: it means two owners believe they hold the same slots, which renders one branch's gaussians at another's pose.

Parameters ​
ParameterTypeDescription
rangeSlotRangeThe range to give back, exactly as SlotAllocator.allocate returned it; it must lie inside the capacity and must not already be free.
Returns ​

void

Interfaces ​

AosrigCharacterBuild ​

A built character: its runtime and the sinks it writes.

Type Parameters ​

Type Parameter
S extends AosrigHostSink

Properties ​

runtime ​
ts
readonly runtime: AosrigSplatRuntime;
sink ​
ts
readonly sink: S;

The character's splats; hidden until the host first shows it.

teethSink ​
ts
readonly teethSink: S | null;

The teeth's splats, when the mouth was built; else null.


AosrigCorrectiveFiles ​

One correction's files, as the package carries them.

Properties ​

bindings ​
ts
bindings: Uint8Array;
fade ​
ts
fade: Uint8Array;
info ​
ts
info: CorrectiveInfo;
points ​
ts
points: Uint8Array;
pointsSide ​
ts
pointsSide: Uint8Array;
side ​
ts
side: Uint8Array;

AosrigCorrectiveState ​

A character's pose corrections while it draws (corrective/, see corrective.ts).

Properties ​

angles ​
ts
readonly angles: Float32Array;

Each drive's arm elevation on the last frame, degrees from hanging: correction 0's left and right, then correction 1's.

corrections ​
ts
readonly corrections: readonly object[];

The corrections played, in the index's order: name, new splats, own splats that fade.

enabled ​
ts
enabled: boolean;

Play the corrections. Off, every weight is 0: the character draws exactly as one without them.

weights ​
ts
readonly weights: Float32Array;

The weights the GPU was given on the last frame, in the same order.


AosRigHeader ​

The header JSON, parsed.

Properties ​

bindTransform ​
ts
bindTransform: number[];

Row-major 4x4: head-local -> the body's bind space. See the header comment.

buffers ​
ts
buffers: AosRigBuffer[];
coeffCount ​
ts
coeffCount: number;
eyes ​
ts
eyes: object;
names ​
ts
names: string[];
headExt ​
ts
headExt: HeadExtLayout;
joints ​
ts
joints: AosRigJoint[];
maxInfluence ​
ts
maxInfluence: number;
model ​
ts
model: "gnm";
mouth? ​
ts
optional mouth?: AosRigMouthEntry;

The mouth's inside, when the bake carried it. See the file comment.

source? ​
ts
optional source?: Record<string, unknown>;

Free-form provenance the packer writes; never read by the runtime.

units ​
ts
units: "m" | "cm";
version ​
ts
version: number;
vertexCount ​
ts
vertexCount: number;

AosrigMouth ​

A character's mouth interior, while it draws.

Properties ​

enabled ​
ts
enabled: boolean;

Draw the mouth interior at all. Off, the character draws exactly as one without it.

layers ​
ts
layers: object;

Turn the mesh or the teeth's points off (for comparisons); the mask stays.

mesh ​
ts
mesh: boolean;
teeth ​
ts
teeth: boolean;
object3D ​
ts
readonly object3D: Object3D;

The inside laid over the window; already under the character's splat object.

open ​
ts
readonly open: boolean;

Whether the inside was drawn on the last frame.

opening ​
ts
readonly opening: number;

How far apart the lips' points were in the middle of the mouth on the last frame, metres.

rect ​
ts
readonly rect: object;

The mouth's rectangle of the screen on the last open frame, drawing-buffer pixels.

height ​
ts
height: number;
width ​
ts
width: number;
x ​
ts
x: number;
y ​
ts
y: number;
stats ​
ts
readonly stats: object;

Sizes: the teeth's points, the mesh, the rim, the lips' edge points and what moving them reads.

coefficients ​
ts
coefficients: number;
edgePoints ​
ts
edgePoints: number;
edgeVertices ​
ts
edgeVertices: number;
points ​
ts
points: number;
rim ​
ts
rim: number;
triangles ​
ts
triangles: number;
vertices ​
ts
vertices: number;
strength ​
ts
readonly strength: number;

How much of the inside showed on the last frame, 0..1.

teeth ​
ts
readonly teeth: SplatSink;

The teeth's splat sink (drawn into the inside's own small render).

thresholds ​
ts
thresholds: object;

How far apart the lips' points must be in the middle of the mouth for the inside to show, metres: nothing below start, all from full. The package's defaults are OPEN_START_M and OPEN_FULL_M; a page may tune them.

full ​
ts
full: number;
start ​
ts
start: number;

Methods ​

dispatch() ​
ts
dispatch(): void;

Run the two passes (teeth, mesh) with renderer.compute, after the head pass.

Returns ​

void

dispose() ​
ts
dispose(): void;

Free what the mouth owns and take its objects out of the scene.

Returns ​

void

drawOffscreen() ​
ts
drawOffscreen(): void;

The two small renders (mask, inside), after the dispatches were submitted.

Returns ​

void

outline() ​
ts
outline(): object;

The outline on the last open frame, in the world: the outer ring (mask 0) and the inner ring (mask 1), x y z per rim point.

Returns ​

object

Copies of both.

inner ​
ts
inner: Float32Array;
outer ​
ts
outer: Float32Array;
update() ​
ts
update(
   controls, 
   skin, 
   camera
): boolean;

Follow this frame's face, head joint and camera. Allocation-free.

Parameters ​
ParameterTypeDescription
controlsArrayLike<number>The head's control vector (expression first).
skinArrayLike<number>The head joint's skin matrix, column-major 4x4, into the splat's frame.
cameraCameraThe camera the frame is drawn with.
Returns ​

boolean

Whether the inside is drawn this frame.


AosrigMouthHiddenState ​

The closed mouth's inside points (mouth_hidden.bin) while the character draws.

Properties ​

band ​
ts
readonly band: number;
count ​
ts
readonly count: number;

Listed splats, and how many of them are the lip line's plugs and the lips' band.

enabled ​
ts
enabled: boolean;

Fade them (and move the plugs) with the lips' gap. Off, they draw as any other splat.

plugs ​
ts
readonly plugs: number;

AosrigMouthOptions ​

What the host supplies for a character's mouth.

Properties ​

sink ​
ts
sink: SplatSink;

A second splat sink for the teeth's points, with room for teeth.json's count.

Methods ​

onError()? ​
ts
optional onError(error): void;

Told when the mouth cannot be built; the character then draws without it.

Parameters ​
ParameterType
errorunknown
Returns ​

void


AosRigPack ​

A parsed pack: the header plus typed views over the blobs.

Properties ​

basis ​
ts
basis: Uint32Array;

Packed fp16 pairs, vertex-major (V,E,3). Feed to the shader verbatim.

basisScale ​
ts
basisScale: Float32Array;

(E,) f32 per-coefficient scale.

bindTransform ​
ts
bindTransform: Float32Array;

Row-major 4x4 head-local -> body bind space. Identity when the bake had none.

byteLength ​
ts
byteLength: number;

Total bytes of the pack, for memoryReport().

coeffCount ​
ts
coeffCount: number;
eyePositions ​
ts
eyePositions: Float32Array;

(2,3) f32.

eyeWeights ​
ts
eyeWeights: Float32Array;

(2,V) f32.

faces ​
ts
faces: Uint32Array;
ts
header: AosRigHeader;
jointParents ​
ts
jointParents: Int32Array;

(J,) i32 parents, -1 for a root.

maxInfluence ​
ts
maxInfluence: number;
mouth? ​
ts
optional mouth?: AosRigMouth;

The teeth, gums, tongue and mouth bag. Absent when the bake had none.

neutral ​
ts
neutral: Float32Array;

(V,3) f32, metres.

quads? ​
ts
optional quads?: Uint32Array<ArrayBufferLike>;
restWorld ​
ts
restWorld: Float32Array;

(J,16) f32 row-major rest world matrices.

skinIndex ​
ts
skinIndex: Uint16Array;

(V,4) u16 into header.joints.

skinWeight ​
ts
skinWeight: Float32Array;

(V,4), expanded from the packed f16 lanes at load.

stitchLocal? ​
ts
optional stitchLocal?: Float32Array<ArrayBufferLike>;

(V,3) f32 neck-seam displacement, head-local. Absent when the bake had no seam.

uv? ​
ts
optional uv?: Float32Array<ArrayBufferLike>;
vertexCount ​
ts
vertexCount: number;

AosrigSinkRequest ​

What a host's sink maker is asked for.

Properties ​

capacity ​
ts
readonly capacity: number;

Splats it must hold.

colorSpace ​
ts
readonly colorSpace: "srgb" | "linear";

The package's colours: sRGB unless its descriptor says otherwise.

kernel? ​
ts
readonly optional kernel?: "studio";

The Gameable studio's splat kernel, for the character's own splats (not the teeth's).


AosrigSplatBundle ​

Parsed, integrity-checked assets. Version 1's rig.glb is consumed by GLTFLoader.

Properties ​

corrective? ​
ts
optional corrective?: AosrigCorrectiveFiles[];

The pose corrections (corrective/index.json and each correction's five files), when the package carries them; validated here as far as their sizes and hashes go, and dropped with a warning rather than failing the character. Absent for a package without them.

descriptor ​
ts
descriptor: AosrigSplatDescriptor;
faceArkit? ​
ts
optional faceArkit?: object;

The face's ARKit table (face.arkit and its file, hash-checked), when the package carries it. A table that does not come or does not match its hash is said, and the face falls back to the hand-made stopgap (arkitToGnmDefault.ts).

bytes ​
ts
bytes: Uint8Array;
info ​
ts
info: ArkitFaceTableInfo;
files ​
ts
files: Map<string, Uint8Array<ArrayBufferLike>>;
mouthHidden? ​
ts
optional mouthHidden?: Uint8Array<ArrayBufferLike>;

mouth_hidden.bin, the closed mouth's inside points the lips' opening takes away, when the package carries it beside its files (see mouthHidden.ts). Absent otherwise.

shared? ​
ts
optional shared?: Map<string, Uint8Array<ArrayBufferLike>>;

With keepShared: the head and skeleton files, by SHA-256, for a fuller copy's reuse.

soma? ​
ts
optional soma?: object;

Version 2's body: the skeleton, and its clips (the package's own, then those of any shared clip files the host asked for, a name the package already has left out).

clips ​
ts
clips: SomaClip[];
skeleton ​
ts
skeleton: SomaSkeleton;
teeth? ​
ts
optional teeth?: AosrigTeethFiles;

The teeth's own points, when the package carries them beside its five files (teeth.json naming teeth.ply and teeth.bin; not in character.json, so not hash-checked, and validated when the mouth is built). Absent for a package without them.


AosrigSplatDescriptor ​

The creator's decoder-free, rig-bound Gaussian character format.

Version 2 (rig aosrig-v2) is the body every new package has: SOMA's skeleton in skeleton.json and the character's clips in clips.json (see soma.ts). Version 1 (rig aosrig_v0, a rig.glb holding the MHR skeleton and its clips) is still read.

Properties ​

bounds ​
ts
bounds: object;
center ​
ts
center: [number, number, number];
radius ​
ts
radius: number;
clips? ​
ts
optional clips?: string[];

Version 2: clips.json's clip names, in its order.

colorSpace? ​
ts
optional colorSpace?: "srgb" | "linear";

What the PLY's colours (f_dc and the harmonics) mean. Absent or 'srgb': display-ready sRGB, as a character trained against photographs stores them, which the renderer draws in its sRGB pass so the screen shows the file's colours. 'linear': already linear light.

corrective? ​
ts
optional corrective?: object;

The pose corrections' index, when the package carries corrective/ and says so (the studio's exporter writes it; an older package's folder is still found beside character.json). sha256 is the index file's.

index ​
ts
index: string;
names? ​
ts
optional names?: string[];
sha256? ​
ts
optional sha256?: string;
face? ​
ts
optional face?: object;

The face's ARKit table (arkit_to_gnm.bin), when the package carries it: what the studio's own face does with 52 ARKit weights. See ArkitFaceTableInfo.

arkit? ​
ts
optional arkit?: ArkitFaceTableInfo;
files ​
ts
files: Record<string, {
  bytes?: number;
  sha256: string;
  src: string;
}>;
format ​
ts
format: "aosrig-splat";
headVertexCount ​
ts
headVertexCount: number;
jointNames ​
ts
jointNames: string[];
package? ​
ts
optional package?: object;

The studio's listing of every file a player may fetch beside character.json (the shared clip pack's ../ path included), with its size: what load progress counts against.

bytes? ​
ts
optional bytes?: number;
files? ​
ts
optional files?: object[];
plyToCharacter ​
ts
plyToCharacter: number[];

Row-major affine; the PLY's axes are kept and only the origin is translated.

renderer ​
ts
renderer: "webgpu";
rig ​
ts
rig: "aosrig-v2" | "aosrig_v0";
sharedClips? ​
ts
optional sharedClips?: object;

Version 2: the shared clip pack the package plays besides its own clips (a soma-clips file every character of the batch names, beside the packages: src is relative to character.json). Hash-checked, fetched once per page; a name in clips.json wins.

bytes? ​
ts
optional bytes?: number;
clips? ​
ts
optional clips?: string[];
format ​
ts
format: "soma-clips";
sha256 ​
ts
sha256: string;
src ​
ts
src: string;
splatCount ​
ts
splatCount: number;
units ​
ts
units: "m";
version ​
ts
version: 1 | 2;

AosrigSplatLoadOptions ​

Which of a package's optional files to fetch, and how.

Properties ​

clipFiles? ​
ts
optional clipFiles?: readonly string[];

Version 2 only: more clip files of the package's clips.json format (soma-clips) to play on this character, absolute URLs or relative to character.json. A clip plays on every character of the same skeleton, so a game's shared clips live in one file.

corrective? ​
ts
optional corrective?: boolean;

Version 1's pose corrections (corrective/); version 2 carries none. Default true.

fetch? ​
ts
optional fetch?: (input, init?) => Promise<Response>;

The fetch every file goes through (the descriptor, the package's files, the shared clips), for a package on a private store: createAamResolver(client).fetch adds the asset manager's key. Defaults to the global fetch.

Parameters ​
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns ​

Promise<Response>

keepShared? ​
ts
optional keepShared?: boolean;

Keep the files a fuller copy of the same character shares with this one (its head and skeleton) in the bundle's shared, by SHA-256, for that copy's reuse.

mouth? ​
ts
optional mouth?: boolean;

The mouth's inside: teeth.json and the files it names, and mouth_hidden.bin. Default true.

onProgress? ​
ts
optional onProgress?: (loadedBytes, totalBytes) => void;

Told as the files arrive: the bytes so far, and the bytes the loader expects in all (the sizes character.json lists for the files it fetches, the shared clip pack included). Both count the files as they are, not as they travel: a server that compresses them sends fewer bytes. The last call has loaded === total. A package that lists no sizes reports a total of 0 until that last call.

Parameters ​
ParameterType
loadedBytesnumber
totalBytesnumber
Returns ​

void

pause? ​
ts
optional pause?: () => Promise<void>;

Awaited between blocks of turning packed files back into plain ones, so frames keep drawing while a character loads behind one already on screen. Omit to do it in one go.

Returns ​

Promise<void>

reuse? ​
ts
optional reuse?: ReadonlyMap<string, Uint8Array<ArrayBufferLike>>;

Files already downloaded and checked, by their SHA-256 (another copy of the same character sharing its head and skeleton): used instead of fetching them again.

sharedClips? ​
ts
optional sharedClips?: boolean;

Version 2 only: fetch the shared clip pack the package names (sharedClips). Default true; a game that brings its own clip file (clipFiles) may skip the whole pack.

teeth? ​
ts
optional teeth?: boolean;

With mouth, the teeth's points (teeth.json and the files it names). Default true; false keeps mouth_hidden.bin (the closed mouth's inside points) for a host that draws no teeth.


AosrigSplatRuntime ​

A GPU character driven by the engine body animator and GNM expression controls.

Properties ​

corrective ​
ts
readonly corrective: AosrigCorrectiveState | null;

The pose corrections, when the package carries corrective/; else null.

hiddenPoints ​
ts
readonly hiddenPoints: AosrigMouthHiddenState | null;

The closed mouth's inside points, when the package carries mouth_hidden.bin; else null.

mapper ​
ts
readonly mapper: object;

Native GNM expression mapping used by the body animator's face layer.

dim ​
ts
dim: number;
dropped ​
ts
dropped: string[];
map ​
ts
map: (arkit, out) => void;
Parameters ​
ParameterType
arkitFloat32Array
outFloat32Array
Returns ​

void

terms ​
ts
terms: number;
mouth ​
ts
readonly mouth: AosrigMouth | null;

The inside of the mouth (teeth, gums, tongue, and the teeth's own points), when the package carries them, the head pack its mouth and the host asked for it; else null.

Methods ​

dispose() ​
ts
dispose(): void;

Free owned GPU resources and slot range. The caller owns the sink.

Returns ​

void

render() ​
ts
render(
   expression, 
   gaze, 
   camera
): void;

Pose face, body and splats after AnimationMixer and procedural aim have run.

Parameters ​
ParameterType
expressionFloat32Array
gazeArrayLike<number>
cameraCamera
Returns ​

void


AosrigTeethFiles ​

The teeth's files, as the package carries them.

Properties ​

binding ​
ts
binding: Uint8Array;
info ​
ts
info: TeethInfo;
ply ​
ts
ply: Uint8Array;

ArkitFaceTableInfo ​

character.json's face.arkit: the face's ARKit table, fitted by gnm-arkit against the rig the head was made from.

The file is float32 little-endian: A (channels.length x coeffs), then the correctives C (pairs.length x coeffs). For ARKit weights c (in channels' order) the head's expression coefficients are c A + sum_k clamp(c[i_k], 0, 1) clamp(c[j_k], 0, 1) C[k], the linear part not clamped. gaze turns the eyeballs: per eyeLook* channel, radians per unit weight (the weight clamped to [0, 1]); pitch + turns the eye up, yaw + toward the character's left.

Properties ​

ts
optional blinks?: string[];

The blink channels (eyeBlinkLeft, eyeBlinkRight).

bytes ​
ts
bytes: number;
channels ​
ts
channels: string[];

The ARKit channel of each row of A, as Apple spells them (eyeBlinkLeft).

coeffs ​
ts
coeffs: number;

Expression coefficients per row: the head's (383).

gaze ​
ts
gaze: Record<string, {
  axis: "pitch" | "yaw";
  eye: "left" | "right";
  radians: number;
}>;
pairs ​
ts
pairs: [number, number][];

Each corrective's two channels, as indices into channels.

sha256 ​
ts
sha256: string;
src ​
ts
src: string;
version ​
ts
version: 1;

ArkitGnmTerm ​

One coefficient an ARKit channel drives.

Properties ​

gain ​
ts
gain: number;

Coefficient units per unit of ARKit weight. Signed.

index ​
ts
index: number;

Coefficient index WITHIN that region.

region ​
ts
region: string;

head_ext region: left_eye, right_eye, lower_face, tongue or pupils.


ArkitToGnmOptions ​

Options for createArkitToGnmMap.

Properties ​

clamp? ​
ts
optional clamp?: number;

Clamp on every produced coefficient. Default 4.

gaze? ​
ts
optional gaze?: boolean;

Also fill the four gaze angles from the EyeLook* channels. Default true.

The gaze half of head_ext is angles in radians, not coefficients, so it is not in the table: EyeLookUp/Down/In/Out are converted here at GAZE_FULL_SCALE radians per unit weight.

table? ​
ts
optional table?: Readonly<Record<string, readonly ArkitGnmTerm[]>>;

Replace the default table.


BodyPose ​

A body pose the animation layer hands over.

Properties ​

bones? ​
ts
optional bones?: readonly JointOverride[];

Per-joint parent-relative rotations, by the rig's own joint names.

rootPos? ​
ts
optional rootPos?: readonly [number, number, number];

Root translation in scene metres. Moves the character's object3D.


CapturedLightOptions ​

Options for capturedLightSamples.

Properties ​

maxGap? ​
ts
readonly optional maxGap?: number;

Gaussians farther than this from any rig vertex are skipped, in metres. Defaults to 0.12.

maxSamples? ​
ts
readonly optional maxSamples?: number;

Gaussians visited, spread evenly over the package. Defaults to 12 000.

minOpacity? ​
ts
readonly optional minOpacity?: number;

Gaussians fainter than this opacity are skipped. Defaults to 0.3.


CapturedLightSamples ​

One sample per gaussian visited, ready for estimateKeyLightFromSurfels.

Properties ​

count ​
ts
readonly count: number;

Samples kept.

groups ​
ts
readonly groups: Uint16Array;

Main joint x 8 + colour class, per sample.

luminance ​
ts
readonly luminance: Float32Array;

Linear luminance per sample.

medianGap ​
ts
readonly medianGap: number;

Median distance from a sample to its nearest rig vertex, in metres: how well the two line up.

normals ​
ts
readonly normals: Float32Array;

Outward unit normals, three per sample.

weights ​
ts
readonly weights: Float32Array;

Opacity per sample.


Character ​

A live splat character.

Properties ​

branches ​
ts
readonly branches: readonly string[];

The branches that came alive, in manifest order.

expressionSpace ​
ts
readonly expressionSpace: ExpressionSpace;

What the animation layer is supposed to send.

object3D ​
ts
readonly object3D: Object3D;

The scene node the character hangs under.

rigKind ​
ts
readonly rigKind: string;

Which rig posed the face.

Methods ​

dispose() ​
ts
dispose(): void;

Release every GPU buffer, ORT session, wasm heap and slot range. Idempotent.

Returns ​

void

memoryReport() ​
ts
memoryReport(): MemoryReport;

Where this character's memory went.

Returns ​

MemoryReport

setBodyPose() ​
ts
setBodyPose(pose): void;

Pose the body: per-joint rotations for the rig, plus a root translation.

Parameters ​
ParameterType
poseBodyPose | null
Returns ​

void

setExpression() ​
ts
setExpression(weights): void;

Drive the face in the bundle's declared expression_space.

ARKit-52 for an arkit52 bundle, head_ext (387) or the reduced view (68) for a GNM one. An ARKit vector reaching a GNM bundle with no arkit_map is REFUSED, loudly: the two spaces are unrelated, and a silent reinterpretation is a face that moves wrongly with nothing to see.

Parameters ​
ParameterType
weightsFloat32Array
Returns ​

void

setLookAt() ​
ts
setLookAt(target): void;

Look at a world point, or null to return to rest.

Parameters ​
ParameterType
targetreadonly [number, number, number] | null
Returns ​

void

setRig() ​
ts
setRig(controls): void;

Drive the rig directly, in the bundle's OWN control space.

This is the raw vector the decoders were trained on — rig_names.json order for an ORL bundle, head_ext for a GNM one. Prefer setExpression unless you are authoring against a specific character.

Parameters ​
ParameterType
controlsFloat32Array
Returns ​

void

settled() ​
ts
settled(): Promise<void>;

Resolves when every in-flight pass has drained. For tests and screenshots.

Returns ​

Promise<void>

update() ​
ts
update(dt, camera): void;

Advance one frame.

Cheap and allocation-free when nothing changed: it compares the camera against the last decoded view and re-runs the appearance pass only when it has genuinely moved. The epsilon is 2 mm plus a turn/fov term, because a pass is an appr decode plus a lift.

Parameters ​
ParameterType
dtnumber
cameraPerspectiveCamera
Returns ​

void


CharacterBundle ​

A loaded, ready-to-instantiate character.

Properties ​

byteLength ​
ts
byteLength: number;

Sum of every resident file's bytes.

bytes ​
ts
bytes: BundleBytes;

Every resident file, by bundle-relative name.

manifest ​
ts
manifest: CharacterManifest;

Manifest + the engine's two added blocks.

preferFp16 ​
ts
preferFp16: boolean;

True when at least one decoder ships an fp16 sibling.

resolver ​
ts
resolver: AssetResolver;

How a lazily-needed file is fetched.

scene ​
ts
scene: MultiRegionScene;

Per-branch mesh topology, in manifest order.


CharacterManifest ​

The engine-side manifest: the exporter's scene plus the two blocks above.

Properties ​

expressionSpace ​
ts
expressionSpace: ExpressionSpace;
rig ​
ts
rig: RigManifest;
rigNames ​
ts
rigNames: string[] | null;

rig_names.json, when the bundle ships one.

scene ​
ts
scene: SceneManifest;

CharacterOptions ​

Tuning a game may pass at creation.

Properties ​

log? ​
ts
optional log?: (message) => void;

Log sink; defaults to console.log.

Parameters ​
ParameterType
messagestring
Returns ​

void

poseAnchors? ​
ts
optional poseAnchors?: boolean;

Apply the bundle's baked per-pose anchors when it ships them. DEFAULT OFF.

They correct where the head IS — the training export fitted a rigid transform per FRAME, and a face control set has no rigid head controls, so head placement is not a function of the rig at all. The correction is real (the single row-0 anchor is out by a p50 of 9.1 mm at the other trained poses) but the blend moves the head whenever the nearest trained poses change, and during speech that happens on a BLINK: measured, a blink steps the head 5.10 mm in ONE frame. A ~4.6 mm mean correction does not pay for a head pop synchronised with blinking, so this is opt-in until the neighbour search can ignore the lids.

preferFp16? ​
ts
optional preferFp16?: boolean;

Prefer the bundle's fp16-internal decoders. Default true.


CorrectiveDrive ​

One side's drive: which arm, read in which joint's frame, and the angles the blend runs between.

Properties ​

bone ​
ts
bone: [string, string];

The upper arm's joint and the joint after it (its direction is the arm's).

curve ​
ts
curve: "smoothstep";
frame ​
ts
frame: string;

The joint whose rest orientation the arm's elevation is read in (a lean of the torso does not count).

fromDeg ​
ts
fromDeg: number;

The weight leaves 0 here...

restDeg ​
ts
restDeg: number;

The arm's elevation at rest, degrees from hanging straight down.

side ​
ts
side: 0 | 1;

0 the character's left, 1 its right.

toDeg ​
ts
toDeg: number;

...and reaches 1 here (and stays 1 past it).


CorrectiveInfo ​

One correction, as corrective/index.json lists it.

Properties ​

drives ​
ts
drives: CorrectiveDrive[];
files ​
ts
files: Record<CorrectiveFileName, string>;

Paths relative to corrective/.

label ​
ts
label: string;
name ​
ts
name: string;
points ​
ts
points: number;

Its own new splats.

sha256 ​
ts
sha256: Partial<Record<CorrectiveFileName, string>>;

Hashes, when the index carries them.

splatCount ​
ts
splatCount: number;

The character's splat count it was made for (must be the package's).

trainedDeg ​
ts
trainedDeg: number | null;

The pose it was made at, degrees, when the index says.


CreateCharacterOptions ​

What createCharacter takes.

Properties ​

options? ​
ts
optional options?: CharacterOptions;
renderer ​
ts
renderer: RendererLike;
scene ​
ts
scene: Object3D;

The scene the character's root is added to.

sink ​
ts
sink: SplatSink;

The splat object this character writes its gaussians into.


CreateRigPreviewOptions ​

What createRigPreview takes.

Properties ​

backend ​
ts
backend: RigBackend;

The backend to preview. Constructed, not necessarily initialised.

controlNames? ​
ts
optional controlNames?: string[];

Control names for backend.init. Defaults to none, which GNM does not need.

device? ​
ts
optional device?: GPUDevice;

The device, when the caller already has it from prepareLiftDevice.

fetchBytes? ​
ts
optional fetchBytes?: (name) => Promise<Uint8Array<ArrayBufferLike>>;

Lazily fetch a file the eager pass skipped, handed to backend.init.

Parameters ​
ParameterType
namestring
Returns ​

Promise<Uint8Array<ArrayBufferLike>>

getBytes? ​
ts
optional getBytes?: (name) => Uint8Array<ArrayBufferLike> | undefined;

Bundle-relative file bytes. Supply them and the preview calls backend.init itself; omit them and the backend is assumed to be initialised already.

Parameters ​
ParameterType
namestring
Returns ​

Uint8Array<ArrayBufferLike> | undefined

height? ​
ts
optional height?: number;

Object-space height the rig's bounds are scaled to. Defaults to 0.35 m.

offset? ​
ts
optional offset?: readonly [number, number, number];

Object-space position of the fitted centre. Defaults to the origin.

range? ​
ts
optional range?: SlotRange;

Slots to draw into. Defaults to a fresh allocation of vertexCount.

renderer? ​
ts
optional renderer?: RendererLike;

The engine's renderer. Either this or device; the renderer owns the device.

setBounds? ​
ts
optional setBounds?: boolean;

Set the sink's bounding sphere from the fitted bounds. Defaults to true.

shading? ​
ts
optional shading?: DebugShading;

Colour source. Defaults to tint when a tint is supplied, normal otherwise.

sigma? ​
ts
optional sigma?: number;

Gaussian radius in object space, metres. Defaults to 2 mm.

sink ​
ts
sink: SplatSink;

Where the gaussians go — an AnimatedSplat from @gameable/splat.

tint? ​
ts
optional tint?: Uint32Array<ArrayBufferLike>;

One packed RGBA per vertex; see vertexTint.ts.

transform? ​
ts
optional transform?: readonly number[];

An explicit rig -> object affine (row-major 3x4), instead of the fit.


DebugLiftParams ​

Everything the params uniform carries.

Properties ​

centroid ​
ts
centroid: readonly [number, number, number];

Object-space centre the normal shading points away from.

opacity ​
ts
opacity: number;

Alpha written into every gaussian, [0, 1].

shading ​
ts
shading: DebugShading;

Colour source.

sigma ​
ts
sigma: number;

Gaussian radius in object space, metres.

slotCount ​
ts
slotCount: number;

Slots the range owns.

slotOffset ​
ts
slotOffset: number;

First slot of the range this preview owns.

transform ​
ts
transform: readonly number[];

Rig -> sink-object affine, row-major 3x4 (12 numbers).

vertexCount ​
ts
vertexCount: number;

Vertices in the rig buffer. Slots past this are cleared, not skipped.


DebugVertexLiftOptions ​

What DebugVertexLift needs at construction.

Properties ​

centroid? ​
ts
optional centroid?: readonly [number, number, number];

Object-space centre for normal shading. Defaults to the origin.

device ​
ts
device: GPUDevice;

The renderer's own device — the one the rig backend built its buffers on.

label? ​
ts
optional label?: string;

Label prefix for the GPU objects, so a capture is readable.

opacity? ​
ts
optional opacity?: number;

Alpha of every gaussian. Defaults to 1.

range? ​
ts
optional range?: SlotRange;

A range to write into. Omit and the lift allocates vertexCount slots from the sink and frees them on dispose.

shading? ​
ts
optional shading?: DebugShading;

Colour source. Defaults to tint.

sigma? ​
ts
optional sigma?: number;

Gaussian radius in object space, metres. Defaults to 2 mm.

sink ​
ts
sink: SplatSink;

Where the gaussians go.

tint? ​
ts
optional tint?: Uint32Array<ArrayBufferLike>;

One packed RGBA per vertex, from vertexTint.ts. Defaults to a flat neutral.

transform? ​
ts
optional transform?: readonly number[];

Rig -> object affine, row-major 3x4. Defaults to the identity.

vertexCount ​
ts
vertexCount: number;

How many vertices that buffer holds.

vertsBuffer ​
ts
vertsBuffer: GPUBuffer;

The rig's posed vertices, V * 3 f32 in the rig's own units.


ExpressionSpace ​

The expression_space block.

Properties ​

arkitMap? ​
ts
optional arkitMap?: number[];

For a gnm space driven from ARKit: slot index per ARKit channel, or -1. Absent until the ARKit -> GNM map is trained; a caller sending ARKit to a GNM bundle without it gets a loud refusal rather than a face that moves wrongly.

dim ​
ts
dim: number;

Vector width the animation layer sends.

kind ​
ts
kind: ExpressionKind;
names ​
ts
names: string[];

One name per slot. Empty when the space is implied by kind alone.

segments ​
ts
segments: ExpressionSegment[];

Named runs — ARKit has none; GNM has left_eye / right_eye / lower_face / tongue / pupils / gaze.


FittedArkitMap ​

The mapper a fitted table gives, in the shape createArkitToGnmMap returns.

Properties ​

dim ​
ts
dim: number;

Floats map writes: the pack's head_ext.

dropped ​
ts
dropped: string[];

The table's channels this reader has no ARKit channel for (left out).

map ​
ts
map: (arkit, out) => void;

ARKit weights (ARKIT_NAMES order, 52) -> head_ext. Allocates nothing.

Parameters ​
ParameterType
arkitFloat32Array
outFloat32Array
Returns ​

void

terms ​
ts
terms: number;

Rows of the table in use: channels plus correctives.


HeadExtLayout ​

The head_ext parameter block.

Properties ​

dim ​
ts
dim: number;

387 = expression + gaze.

exprDim ​
ts
exprDim: number;
gazeDim ​
ts
gazeDim: number;

4: [pitch_L, yaw_L, pitch_R, yaw_R] radians.

reduced ​
ts
reduced: Record<string, number>;

The reduced ML view's per-region counts; they sum to 64, plus gaze = 68.

regions ​
ts
regions: [string, number][];

[["left_eye",100], ["right_eye",100], ["lower_face",150], ["tongue",32], ["pupils",1]].


JointOverride ​

A per-joint override the animation layer applies on top of the solved rig.

Properties ​

joint ​
ts
joint: string;

Joint name in the backend's own namespace.

rotation ​
ts
rotation: readonly [number, number, number, number];

Parent-relative rotation as a quaternion (w, x, y, z).


LoadCharacterBundleOptions ​

Options for loadCharacterBundle.

Extends ​

  • BundleFetchOptions

Properties ​

fetch? ​
ts
optional fetch?: {
  (input, init?): Promise<Response>;
  (input, init?): Promise<Response>;
};

Overrides the global fetch; ignored when resolver is given.

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns ​

Promise<Response>

Call Signature ​
ts
(input, init?): Promise<Response>;

MDN Reference

Parameters ​
ParameterType
inputstring | URL | Request
init?RequestInit
Returns ​

Promise<Response>

Inherited from ​
ts
BundleFetchOptions.fetch
preferFp16? ​
ts
optional preferFp16?: boolean;

Skip the fp16 decoders even where the bundle ships them.

resolver? ​
ts
optional resolver?: AssetResolver;

Resolve a bundle-relative name to bytes yourself (CDN, asset manager, tests).

Inherited from ​
ts
BundleFetchOptions.resolver
signal? ​
ts
optional signal?: AbortSignal;
Inherited from ​
ts
BundleFetchOptions.signal

MemoryReport ​

Where a character's memory went.

Properties ​

branches ​
ts
branches: object[];

Per branch: name, slots, GPU bytes.

gpuBytes ​
ts
gpuBytes: number;
name ​
ts
name: string;
slots ​
ts
slots: number;
bundleBytes ​
ts
bundleBytes: number;

Resident bundle bytes (decoders, meshes, rig pack).

gpuBytes ​
ts
gpuBytes: number;

Approximate GPU bytes across every branch's lift and rig.

slots ​
ts
slots: number;

Splat slots this character owns.


MouthHidden ​

The listed splats, parsed.

Properties ​

band ​
ts
band: number;
count ​
ts
count: number;
forward ​
ts
forward: [number, number, number];
kind ​
ts
kind: Uint8Array;
lower ​
ts
lower: Uint32Array;
plugs ​
ts
plugs: number;

How many are the lip line's plugs (kind 1), and how many the lips' band (kind 3); the rest are inside points (kind 2).

row ​
ts
row: Uint32Array;

Per listed splat: its row in character.ply, its kind, its upper and lower lip vertex.

up ​
ts
up: [number, number, number];

The head's up and forward, unit, in the character's frame.

upper ​
ts
upper: Uint32Array;

OrtDeviceAttachment ​

What attachOrtDevice managed to do.

Properties ​

gpuBufferIo ​
ts
readonly gpuBufferIo: boolean;

When false, every decoder session must be built with downloadOutputs so its outputs come back as CPU arrays. A cross-device GPUBuffer is a validation error, not a slow path.

reason ​
ts
readonly reason: string | null;

Why not, when shared is false. Null on success.

shared ​
ts
readonly shared: boolean;

True when ORT accepted the renderer's device.


RendererLike ​

The slice of WebGPURenderer this package reads.

Properties ​

backend? ​
ts
optional backend?: object;

RigBackend ​

One rig -> vertices implementation.

Lifecycle: init -> (setControls -> encode)* -> dispose. setControls is pure CPU work (the solve); encode records the per-vertex half into a caller-supplied encoder so a whole character is one submission.

Properties ​

controlNames ​
ts
readonly controlNames: readonly string[];

The control space this backend drives, in setControls order.

kind ​
ts
readonly kind: RigKind;

Which implementation this is.

vertexCount ​
ts
readonly vertexCount: number;

Vertices produced per pose.

vertsAABB ​
ts
readonly vertsAABB: VertsAABB;

Bounds of the neutral pose, for the sink's bounding sphere.

vertsBuffer ​
ts
readonly vertsBuffer: GPUBuffer;

The posed vertices, in the rig's own frame and units. Valid after the encoded pass has executed; the lift's copyBufferToBuffer is what reads it.

Methods ​

bytes() ​
ts
bytes(): number;

Approximate GPU + wasm bytes this backend holds, for memoryReport().

Returns ​

number

dispose() ​
ts
dispose(): void;

Release the wasm heap and every GPU buffer. Idempotent.

Returns ​

void

encode() ​
ts
encode(encoder): void;

Record the per-vertex pass into encoder. No submit, no fence, no readback.

Parameters ​
ParameterType
encoderGPUCommandEncoder
Returns ​

void

init() ​
ts
init(options): Promise<void>;

Build the wasm/GPU resources. Idempotent; throws loudly on a bad asset.

Parameters ​
ParameterType
optionsRigBackendInit
Returns ​

Promise<void>

runCpu() ​
ts
runCpu(controls): Promise<Float32Array<ArrayBufferLike>>;

Solve and deform ON THE CPU, for calibration only.

The rig->bundle similarity and the per-vertex corr are fitted from this, and corr is then added to every GPU-produced frame — so the two implementations must agree. Never call it per frame.

Parameters ​
ParameterType
controlsFloat32Array
Returns ​

Promise<Float32Array<ArrayBufferLike>>

setControls() ​
ts
setControls(controls): void;

Solve at controls. CPU-only and synchronous — there is no inference call to await — so a caller may call it several times a frame and only encode once.

Parameters ​
ParameterType
controlsFloat32Array
Returns ​

void

setJointOverrides()? ​
ts
optional setJointOverrides(overrides): boolean;

Apply per-joint rotation overrides on top of the solved pose (procedural head aim, gaze). Optional: a backend with no addressable joints simply omits it.

Returns whether anything actually MOVED. A converged head aim resends the same quaternions every frame and a backend with no addressable joints ignores them outright, so a caller that re-ran a full decode on every call was decoding an idle character sixty times a second.

Parameters ​
ParameterType
overridesreadonly JointOverride[]
Returns ​

boolean


RigBackendInit ​

Everything a backend needs before it can pose anything.

Properties ​

controlNames ​
ts
controlNames: string[];

The bundle's control-name list, in the order setControls takes values.

device ​
ts
device: GPUDevice;

The device the lift owns. A backend must build every buffer on THIS device.

expectVerts? ​
ts
optional expectVerts?: number;

Expected vertex count; a mismatch throws rather than posing garbage.

fetchBytes? ​
ts
optional fetchBytes?: (name) => Promise<Uint8Array<ArrayBufferLike>>;

Lazily fetch a bundle file the eager pass skipped.

Parameters ​
ParameterType
namestring
Returns ​

Promise<Uint8Array<ArrayBufferLike>>

getBytes ​
ts
getBytes: (name) => Uint8Array<ArrayBufferLike> | undefined;

Bundle-relative file bytes, already resident.

Parameters ​
ParameterType
namestring
Returns ​

Uint8Array<ArrayBufferLike> | undefined


RigManifest ​

The rig block.

Properties ​

backend ​
ts
backend: RigBackendKind;

orl (OpenRigLogic from the character's own DNA) or gnm (a baked .aosrig).

controlNames ​
ts
controlNames: string[];

The control-name list, in the order setRig takes values.

pack ​
ts
pack: string | null;

Bundle-relative pack filename: orl_pack.bin or <name>.aosrig.

vertexCount ​
ts
vertexCount: number;

Expected vertex count of the rig's output; 0 when the bundle does not state one.


RigPreview ​

A live rig preview.

Properties ​

backend ​
ts
readonly backend: RigBackend;

The backend, initialised.

lift ​
ts
readonly lift: DebugVertexLift;

The debug lift, for setSigma / setShading / setTransform.

range ​
ts
readonly range: SlotRange;

The slots the preview owns.

scale ​
ts
readonly scale: number;

Uniform scale the placement fit chose, for an overlay to report.

Methods ​

dispose() ​
ts
dispose(): void;

Release the lift's buffers, the backend's resources and the slot range.

Returns ​

void

encode() ​
ts
encode(encoder): void;

Record the rig pass and the debug lift into one encoder, in that order.

Use this when the caller owns the submission — a frame that also wants timestamp queries around the two passes, for instance.

Parameters ​
ParameterTypeDescription
encoderGPUCommandEncoderThe frame's command encoder.
Returns ​

void

render() ​
ts
render(): void;

Encode, submit and ask the sink to re-sort. The one-call-per-frame path.

Returns ​

void

setControls() ​
ts
setControls(controls): void;

Solve the rig at controls — the backend's own control space, so head_ext for GNM.

Parameters ​
ParameterTypeDescription
controlsFloat32ArrayThe control vector.
Returns ​

void


RigSurface ​

The rig mesh at rest, in the character's space.

Properties ​

normals ​
ts
readonly normals: ArrayLike<number>;

Unit vertex normals, three per vertex.

positions ​
ts
readonly positions: ArrayLike<number>;

Vertex positions, three per vertex.


SlotRange ​

A contiguous run of splat slots owned by one branch for its whole life.

Properties ​

count ​
ts
readonly count: number;

Slot count. Constant: culled texels write opacity 0 rather than compacting.

offset ​
ts
readonly offset: number;

First slot index.


SomaClip ​

One clip of a soma-clips file.

Properties ​

bones ​
ts
readonly bones: ReadonlyMap<string, Float32Array<ArrayBufferLike>>;

Per joint name, x y z w per frame.

fps ​
ts
readonly fps: number;
frames ​
ts
readonly frames: number;
locomotion ​
ts
readonly locomotion: boolean;

Whether the clip belongs in the walk and run blend.

loop ​
ts
readonly loop: boolean;
name ​
ts
readonly name: string;
root ​
ts
readonly root: Float32Array;

The Hips' path, x y z per frame (the neutral body's, over its floor).

speed ​
ts
readonly speed: number;

Ground speed in metres per second, when the clip says (a locomotion clip).


SomaJoint ​

One joint of the skeleton, as skeleton.json states it.

Properties ​

baseLocalRotation ​
ts
readonly baseLocalRotation: Quat | null;

A twist helper's local rotation before its twist (x y z w); null for every other joint.

inverseBind ​
ts
readonly inverseBind: Matrix4;

Inverse bind matrix (three's column-major order): where the splats were bound.

localPosition ​
ts
readonly localPosition: Vec3;

Rest offset in the parent's frame, metres.

localRotation ​
ts
readonly localRotation: Quat;

Rest rotation in the parent's frame, x y z w: the character's stance.

name ​
ts
readonly name: string;

The joint's name (Hips, LeftForeArmTwist2 ...).

parent ​
ts
readonly parent: number;

Its parent's index, or -1 for Root. Parents come before their children.


SomaPoser ​

Poses a rig built by createSomaRig and writes its skin matrices.

Properties ​

worlds ​
ts
readonly worlds: Float64Array;

Each joint's rig-space world, from the last pose (16 per joint, column-major).

Methods ​

pose() ​
ts
pose(out): void;

Turn the twist helpers from the pose on the bones (the mixer's and the head aim's work), write their rotations back onto their bones, and write every joint's skin matrix (rig-space world . inverse bind, column-major) into out.

Parameters ​
ParameterTypeDescription
outFloat32Array16 floats per joint, at least.
Returns ​

void


SomaSkeleton ​

A package's skeleton, validated.

Properties ​

floorY ​
ts
readonly floorY: number;

The floor's height in the package frame (0 in every v2 package).

groundOffset ​
ts
readonly groundOffset: number;

Added to the Hips' height for a clip's root track.

helpers ​
ts
readonly helpers: readonly SomaHelper[];

The twist helpers, in the file's twist order.

hips ​
ts
readonly hips: number;

Index of Hips, where a clip's root track goes.

joints ​
ts
readonly joints: readonly SomaJoint[];

Every joint, in the file's order (character.json's jointNames).

names ​
ts
readonly names: readonly string[];

The joint names, same order.

segments ​
ts
readonly segments: readonly SomaSegment[];

The twist rule's segments.


SplatSink ​

The splat object a character writes its gaussians into.

Implemented by gameable/splat's AnimatedGaussianSplat. Slot ranges are allocated once per branch at load and never move: three's CountingSort keeps an index -> splat map across frames, so compacting on the GPU would tear the sort during camera motion.

Properties ​

buffers ​
ts
readonly buffers: SplatSinkBuffers;
capacity ​
ts
readonly capacity: number;

Total slots. A character must fit inside it or allocate throws.

nodes? ​
ts
readonly optional nodes?: object;

The four buffers as writable TSL storage nodes (gameable/splat's SplatStorageNodes), on WebGPU and the WebGL2 fallback. Absent on a sink that only speaks GPUBuffers.

center ​
ts
readonly center: StorageBufferNode<"vec4">;
color ​
ts
readonly color: StorageBufferNode<"uint">;
covarianceA ​
ts
readonly covarianceA: StorageBufferNode<"vec4">;
covarianceB ​
ts
readonly covarianceB: StorageBufferNode<"vec4">;
object3D ​
ts
readonly object3D: Object3D<Object3DEventMap>;

Scene node the splats hang under.

storageCapacity? ​
ts
readonly optional storageCapacity?: number;

Elements per buffer, capacity padded to the WebGL PBO shape; set with nodes.

Methods ​

allocate() ​
ts
allocate(count): SlotRange;

Reserve count contiguous slots. Throws when the sink is full.

Parameters ​
ParameterType
countnumber
Returns ​

SlotRange

free() ​
ts
free(range): void;

Return a range to the free list.

Parameters ​
ParameterType
rangeSlotRange
Returns ​

void

markGaussiansChanged() ​
ts
markGaussiansChanged(): void;

Force a re-sort on the next render, even when the camera has not moved.

Returns ​

void

setBoundingSphere() ​
ts
setBoundingSphere(center, radius): void;

Owner-supplied bounds; the splat object never recomputes them from the GPU.

Parameters ​
ParameterType
center[number, number, number]
radiusnumber
Returns ​

void

setFragmentAlpha()? ​
ts
optional setFragmentAlpha(builder): void;

Scale each fragment's opacity by a node built from the splat's view-space centre, or null to draw as before. Optional: a sink without it gets no mouth interior.

Parameters ​
ParameterType
builder((view) => Node<unknown>) | null
Returns ​

void


SplatSinkBuffers ​

The GPU buffers a lift writes into. All four are indexed by absolute slot.

Properties ​

center ​
ts
readonly center: GPUBuffer;

array<vec4<f32>>: xyz = centre in scene metres, w unused.

color ​
ts
readonly color: GPUBuffer;

array<u32>: pack4x8unorm(vec4f(r, g, b, opacity)).

covarianceA ​
ts
readonly covarianceA: GPUBuffer;

array<vec4<f32>>: (c00, c01, c02, c11).

covarianceB ​
ts
readonly covarianceB: GPUBuffer;

array<vec4<f32>>: (c12, c22, 0, 0).


VertsAABB ​

Axis-aligned bounds of the posed vertices, in the rig's own units.

Properties ​

max ​
ts
max: readonly [number, number, number];
min ​
ts
min: readonly [number, number, number];

Type Aliases ​

AosrigHostSink ​

ts
type AosrigHostSink = SplatSink & object;

A sink the host made, which the host also frees.

Type Declaration ​

dispose() ​
ts
dispose(): void;
Returns ​

void


ArkitGnmTable ​

ts
type ArkitGnmTable = Readonly<Record<string, readonly ArkitGnmTerm[]>>;

The table: ARKit-52 channel name -> the coefficients it drives.


DebugShading ​

ts
type DebugShading = "tint" | "normal" | "flat";

How the shader colours a vertex.


ExpressionKind ​

ts
type ExpressionKind = "arkit52" | "gnm" | "gnm68";

How a caller addresses this character's face.


RigBackendKind ​

ts
type RigBackendKind = "orl" | "gnm" | "none";

Which rig backend drives this bundle's branches.


RigKind ​

ts
type RigKind = "orl" | "gnm";

Which implementation is behind a backend, for logs and the manifest.


TintKind ​

ts
type TintKind = "joint" | "uv" | "height" | "flat";

Which per-vertex colouring a preview asks for.

Variables ​

ARKIT_NAMES ​

ts
const ARKIT_NAMES: string[];

ARKit blendshape names in the exact order the server sends them (indices 0-51). Source: AvatarosWhisper/Private/AnimationFrameLiveLinkSource.cpp

Lives in its own module so pure-logic consumers can read the canonical channel order without pulling in anything else.

Ported from aos-threejs-poc/src/lib/arkitNames.js @ cdd63b10


ARKIT_TO_GNM_DEFAULT ​

ts
const ARKIT_TO_GNM_DEFAULT: ArkitGnmTable;

The default ARKit-52 -> GNM table.

Covers the channels a face clip actually moves in a shipped idle: blink, squint, wide, the brows, the jaw, the smile/frown pair, the lateral mouth shift and the tongue. Channels with no entry contribute nothing rather than being approximated by a neighbouring mode, because a wrong mode is worse than a still one.

Example ​

ts
import { ARKIT_TO_GNM_DEFAULT } from 'gameable/character';

console.log(ARKIT_TO_GNM_DEFAULT.JawOpen[0].region); // 'lower_face'

BAND_CAP_M ​

ts
const BAND_CAP_M: 0.0025 = 0.0025;

A band splat's sizes are capped at this as the lips part there (metres).


DEBUG_LIFT_PARAMS_BYTES ​

ts
const DEBUG_LIFT_PARAMS_BYTES: 96 = 96;

Bytes of the params uniform: four u32, three vec4 rows, four f32, one vec4.


DEBUG_LIFT_WORKGROUP ​

ts
const DEBUG_LIFT_WORKGROUP: 64 = 64;

Threads per workgroup in debug_vertex_lift.wgsl. Mirrors the shader's literal.


GAZE_FULL_SCALE ​

ts
const GAZE_FULL_SCALE: number;

Radians of gaze per unit ARKit EyeLook* weight — 20 degrees at full deflection.

Example ​

ts
import { GAZE_FULL_SCALE } from 'gameable/character';

console.log((GAZE_FULL_SCALE * 180) / Math.PI); // 20

GNM_PACK_FILE ​

ts
const GNM_PACK_FILE: "gnm_head.aosrig" = 'gnm_head.aosrig';

Default pack name when a manifest declares none.


IDENTITY_3X4 ​

ts
const IDENTITY_3X4: readonly number[];

The identity 3x4, row-major: a rig whose vertices are already in object space.


MAX_CORRECTIONS_PLAYED ​

ts
const MAX_CORRECTIONS_PLAYED: 2 = 2;

How many corrections the GPU plays: two weight slots per correction (its left and right), four slots in all. A package listing more is played from the first ones, with a warning.


MH_LEN ​

ts
const MH_LEN: 188 = 188;

The MetaHuman control-vector width arkitToMh writes into.


MH_RIG_NAMES ​

ts
const MH_RIG_NAMES: string[];

The 188 MetaHuman board controls, in arkitToMh index order.


MOUTH_RENDER_ORDER ​

ts
const MOUTH_RENDER_ORDER: object;

Draw order of the inside laid over the window; the character's own splat draws at 1000.

Type Declaration ​

inside ​
ts
readonly inside: 997 = 997;

ORL_HEAD_VERTS ​

ts
const ORL_HEAD_VERTS: 24049 = 24049;

MetaHuman head_lod0 vertex count — what an ORL pack bakes. A branch mesh of any other size is not a head_lod0 and cannot be skinned by the DNA.


PACKAGE ​

ts
const PACKAGE: "@gameable/character";

Package identity marker for gameable/character.

Example ​

ts
import { PACKAGE } from 'gameable/character';

console.log(PACKAGE); // 'gameable/character'

REQUIRED_STORAGE_BUFFERS ​

ts
const REQUIRED_STORAGE_BUFFERS: 8 = 8;

WebGPU's DEFAULT maxStorageBuffersPerShaderStage, which every adapter grants. The lift's heaviest pass (lift_pass1.wgsl) binds exactly 8, so no device is refused the lift over a limit. It bound 9 until valid was folded into triim (inference/liftTriim.ts), and the integrated GPUs that report exactly 8 were silently landing on a seconds-per-frame CPU path.

Functions ​

arkitToMh() ​

ts
function arkitToMh(arkit, out): Float32Array;

Map 52 ARKit blendshape weights onto the 188-element MetaHuman control array.

Parameters ​

ParameterTypeDescription
arkitArrayLike<number>Live ARKit weights, length >= 52.
outFloat32ArrayDestination, length MH_LEN. ZEROED first — many indices are never written below and would otherwise carry over from the previous frame.

Returns ​

Float32Array

out, filled with the 188 MetaHuman control weights.


arkitToRig() ​

ts
function arkitToRig(
   arkit52, 
   gather, 
   outN, 
   scratch188?, 
   rest?, 
   range?
): Float32Array;

Map a live 52-element ARKit frame to a character's N-element rig vector.

ABOUT rest. ARKit-52 can only express a SUBSET of a MetaHuman board, and arkitToMh ZEROES every control it does not write — so without a rest vector the output is "the few controls ARKit reached, and 0 everywhere else". That is only a neutral face if the control space is centred on 0. For one declaring rig_range [0,1] it is an extreme corner the decoders never saw: measured on eyeline V10-C, ARKit reaches 53 of 168 controls (115 are permanently 0) against a trained mean with 133 non-zero, and the resulting vector sits FURTHER from the training distribution (L2 3.32) than a real trained pose does (2.96). The decoders then extrapolate, which is what a washed-out, detail-free face is.

ARKit weights are deltas from neutral BY DEFINITION (0 = neutral, 1 = full expression), and arkitToMh maps them into an MH space with the same convention — arkitToMh(zeros) is all-zeros, verified. So ARKit's contribution is ADDED to rest rather than replacing it: an ARKit-neutral frame reproduces rest exactly, and the 115 controls ARKit cannot reach hold their rest value instead of collapsing to 0.

Passing no rest reproduces the previous behaviour exactly, which is correct for a bundle whose controls do rest at zero.

Parameters ​

ParameterTypeDefault valueDescription
arkit52ArrayLike<number>undefinedLive ARKit weights (length >= 52).
gatherreadonly number[]undefinedN indices into the 188 MH controls (gatherFromRigNames).
outNFloat32ArrayundefinedDestination, length N === gather.length (overwritten).
scratch188?Float32Array<ArrayBufferLike>undefinedOptional reusable 188-length buffer, to avoid a per-call allocation on the hot path.
rest?ArrayLike<number> | nullnullThe bundle's rest configuration, length N. Ignored when its length does not match the gather.
range?RigRange | nullnullEither the bundle's declared rig_range as one [lo,hi] pair, or PER-CONTROL trained-domain limits, one [lo,hi] per gathered control. Prefer the latter — see below.

Returns ​

Float32Array

outN, holding the N gathered controls — rest plus the ARKit delta, clamped to range, when a matching rest was supplied.

Example ​

ts
import { arkitToRig, ARKIT_NAMES, gatherFromRigNames } from 'gameable/character';

const gather = gatherFromRigNames(bundle.manifest.rig.controlNames);
const controls = new Float32Array(gather.length);
const weights = new Float32Array(ARKIT_NAMES.length);

weights[ARKIT_NAMES.indexOf('jawOpen')] = 0.4;
arkitToRig(weights, gather, controls);

armAngleDeg() ​

ts
function armAngleDeg(
   dx, 
   dy, 
   dz, 
   frame
): number;

The arm's elevation from hanging straight down, read in the frame joint's rest orientation: d' = R_rest · R_now⁻¹ · d, so a lean of the whole torso does not count. frame is the joint's turn since rest (its skin matrix in the character's frame, R_now · R_rest⁻¹), so d' is its transpose applied to d.

Parameters ​

ParameterTypeDescription
dxnumberThe arm's direction: the joint after the upper arm less the upper arm, x.
dynumberIts y (the character's up is +y).
dznumberIts z.
frameArrayLike<number>The frame joint's skin matrix, column-major 4x4.

Returns ​

number

Degrees: 0 hanging, 90 straight out (in any direction), 180 straight up.


attachOrtDevice() ​

ts
function attachOrtDevice(device): OrtDeviceAttachment;

Hand the renderer's GPUDevice to onnxruntime-web.

The property is ort.env.webgpu.device, and in onnxruntime-web 1.29 it is a genuine accessor pair: the SETTER takes a GPUDevice and the GETTER returns a Promise<GPUDevice> (it will create one on demand if nothing was set). Setting it only has effect BEFORE the first WebGPU inference session is created — which is why this must run immediately after prepareLiftDevice and before any decoder is built.

env.webgpu.adapter also exists and is settable, but it is deprecated in 1.29 and it is the wrong lever anyway: an adapter would let ORT build its own device off the same hardware, which is still a DIFFERENT device and still refuses a cross-device buffer. If a future ORT drops the device setter, the fallback is the one below — gpuBufferIo: false, every decoder output downloaded to a CPU Float32Array and re-uploaded by the lift. That costs one round trip per decode and is correct; it is the POC's own demote path (DecoderSession.setDownloadOutputs).

Never throws: a character that cannot share the device still renders, slower.

Parameters ​

ParameterTypeDescription
deviceGPUDeviceThe renderer's device, from prepareLiftDevice.

Returns ​

OrtDeviceAttachment

Whether ORT took the device, why it did not, and whether decoder outputs may therefore stay in GPUBuffers instead of being downloaded.


blendWeight() ​

ts
function blendWeight(thetaDeg, drive): number;

A drive's weight for an arm elevation: 0 up to fromDeg, a smoothstep to toDeg, 1 from there on.

Parameters ​

ParameterTypeDescription
thetaDegnumberThe arm's elevation from hanging, degrees.
drivePick<CorrectiveDrive, "fromDeg" | "toDeg">The drive.

Returns ​

number

The weight, 0..1.


buildAosrigCharacter() ​

ts
function buildAosrigCharacter<S>(o): Promise<AosrigCharacterBuild<S>>;

Build an exported character's splats and its runtime.

On failure every sink made here is freed and the error is thrown on.

Type Parameters ​

Type Parameter
S extends AosrigHostSink

Parameters ​

ParameterTypeDescription
o{ bundle: AosrigSplatBundle; makeSink: (request) => Promise<S>; mouth: boolean; onMouthError?: (error) => void; pause?: () => Promise<void>; releaseBuffer?: (attribute) => void; renderer: WebGPURenderer; rig: Object3D; webgl?: boolean; }The package, its rig, the renderer and how to make a sink.
o.bundleAosrigSplatBundleThe loaded package.
o.makeSink(request) => Promise<S>Makes a sink (createAnimatedSplat), with whatever the host adds (a draw order).
o.mouthbooleanDraw the inside of the mouth when the package carries the teeth's points.
o.onMouthError?(error) => voidTold when the mouth cannot be built; the character draws without it.
o.pause?() => Promise<void>Awaited between blocks of the build, so frames keep drawing.
o.releaseBuffer?(attribute) => voidFrees a storage attribute's GPU buffers (releaseStorageAttribute).
o.rendererWebGPURendererThe renderer that owns the sinks.
o.rigObject3DThe rig the bindings follow.
o.webgl?booleanThe renderer is on the WebGL2 fallback.

Returns ​

Promise<AosrigCharacterBuild<S>>

The runtime and the sinks.

Example ​

ts
const { runtime, sink } = await buildAosrigCharacter({
  bundle, rig, renderer, mouth: true,
  makeSink: (request) => createAnimatedSplat(renderer, request),
});
group.add(sink.object3D);

capturedLightSamples() ​

ts
function capturedLightSamples(
   bundle, 
   rig, 
   options?
): CapturedLightSamples;

Sample a character's package for the light it was captured under. Call it while the package's bytes are still held (before the loader drops them); it reads the PLY and the bindings, keeps nothing, and costs a few tens of milliseconds once.

Parameters ​

ParameterTypeDescription
bundleAosrigSplatBundleThe loaded package (its character.ply and bindings.bin).
rigRigSurfaceThe rig mesh at rest, in the character's space.
optionsCapturedLightOptionsHow many gaussians to visit and which to skip.

Returns ​

CapturedLightSamples

The samples.

Throws ​

When the package's files are gone or malformed.


correctivePoints() ​

ts
function correctivePoints(corrections): number;

The new splats the GPU is given room for: those of the corrections it plays.

Parameters ​

ParameterTypeDescription
correctionsreadonly object[] | undefinedThe package's corrections, in the index's order (or none).

Returns ​

number

Their point counts summed, over the first MAX_CORRECTIONS_PLAYED.


createAosrigSplat() ​

ts
function createAosrigSplat(options): Promise<AosrigSplatRuntime>;

Bind an animated skeleton to the exported Gaussian cloud: version 2's aosrig-v2 skeleton (createSomaRig of the bundle's) or version 1's aosrig_v0 glTF rig.

The face and the splats deform in two TSL compute passes (tslDeform.ts) that the renderer runs once a frame, on WebGPU or, as transform feedback, on the WebGL2 fallback.

Parameters ​

ParameterTypeDescription
options{ bundle: AosrigSplatBundle; mouth?: AosrigMouthOptions; pause?: () => Promise<void>; releaseBuffer?: (attribute) => void; renderer: WebGPURenderer; rig: Object3D; sink: SplatSink; webgl?: boolean; }Verified export, its rig, the renderer and the splat sink.
options.bundleAosrigSplatBundleThe loaded package.
options.mouth?AosrigMouthOptionsDraw the inside of the mouth when the package carries it: a second sink for the teeth's points. Omit to draw the character without it.
options.pause?() => Promise<void>Awaited between slices of the build's main-thread work, so frames keep drawing while a character is built behind one already on screen. Omit to build in one go.
options.releaseBuffer?(attribute) => voidFree a storage attribute's GPU buffers (@gameable/splat's releaseStorageAttribute).
options.rendererWebGPURendererThe renderer that owns the sink; runs the passes.
options.rigObject3DThe body rig the bindings follow.
options.sinkSplatSinkThe sink. On WebGL2 it holds this character alone (its every slot is written each frame).
options.webgl?booleanThe renderer is on the WebGL2 fallback: transform-feedback compute, PBO reads.

Returns ​

Promise<AosrigSplatRuntime>

A deformation runtime; call render after the body animator updates.

Example ​

ts
const character = await createAosrigSplat({ bundle, rig, sink, renderer });
character.render(expression, [0, 0, 0, 0], camera);

createArkitToGnmMap() ​

ts
function createArkitToGnmMap(layout, options?): object;

Build an ExpressionSpace.map for a pack's layout.

The returned function is what gameable/animation's createAnimator calls every frame, so it ALLOCATES NOTHING: the table is resolved to absolute slots here, once, and the per-frame path is a loop over a flat array.

Terms that fall outside the layout — a coefficient a truncated pack dropped, a region that pack does not declare — are discarded at build time and reported, rather than being clamped into a neighbouring coefficient.

Parameters ​

ParameterTypeDescription
layoutHeadExtLayoutThe pack's header.headExt.
optionsArkitToGnmOptionsSee ArkitToGnmOptions.

Returns ​

object

The mapper plus what it resolved: dim to size the output with, the terms it kept, and the entries it dropped with the reason.

dim ​
ts
dim: number;
dropped ​
ts
dropped: string[];
map ​
ts
map: (arkit, out) => void;
Parameters ​
ParameterType
arkitFloat32Array
outFloat32Array
Returns ​

void

terms ​
ts
terms: number;

Example ​

ts
import { createArkitToGnmMap, parseAosRig } from 'gameable/character';

const pack = parseAosRig(bytes);
const { dim, map } = createArkitToGnmMap(pack.header.headExt);
const animator = createAnimator({ root, expressionSpace: { kind: 'gnm', dim, map } });

createCharacter() ​

ts
function createCharacter(bundle, init): Promise<Character>;

Bring a loaded bundle to life against a splat sink.

Parameters ​

ParameterTypeDescription
bundleCharacterBundleThe already-loaded bundle, from loadCharacterBundle.
initCreateCharacterOptionsThe renderer whose device everything borrows, the scene to parent the splat object under, the sink whose slots the branches are lifted into, and the optional tuning in init.options.

Returns ​

Promise<Character>

The live character: setRig / setExpression / setBodyPose / setLookAt to drive it, update once a frame, and dispose to give back every GPU buffer, ORT session and slot range.

Throws ​

(from ./errors.ts, via prepareLiftDevice) when the renderer is not on the WebGPU backend.

Example ​

ts
const bundle = await loadCharacterBundle('/assets/characters/myra');
const character = await createCharacter(bundle, { renderer, scene, sink });
character.setExpression(arkitWeights);
character.update(dt, camera);

createFittedArkitMap() ​

ts
function createFittedArkitMap(table, layout): FittedArkitMap;

Build the ExpressionSpace.map for a package's fitted face table.

head_ext's expression block is the head's coefficients in order, so the table's columns land on it one to one; its gaze block (pitchL, yawL, pitchR, yawR, pitch + looking down, yaw + toward the character's left) takes the eyeLook* channels' turns, the table's pitch (+ up) negated.

Parameters ​

ParameterTypeDescription
table{ bytes: Uint8Array; info: ArkitFaceTableInfo; }The descriptor's face.arkit and the file's bytes (hash already checked).
table.bytesUint8Array-
table.infoArkitFaceTableInfo-
layoutHeadExtLayoutThe pack's header.headExt.

Returns ​

FittedArkitMap

The mapper.

Throws ​

When the table does not fit this head (another coefficient count, a file of another size), so the caller can fall back to the stopgap.

Example ​

ts
import { createFittedArkitMap } from 'gameable/character';

const face = createFittedArkitMap(bundle.faceArkit, pack.header.headExt);
const animator = createAnimator({ root, expressionSpace: { kind: 'gnm', dim: face.dim, map: face.map } });

createRigPreview() ​

ts
function createRigPreview(options): Promise<RigPreview>;

Put a rig backend on screen without any decoders.

Parameters ​

ParameterTypeDescription
optionsCreateRigPreviewOptionsSee CreateRigPreviewOptions.

Returns ​

Promise<RigPreview>

The live preview.

Throws ​

When the renderer is on the WebGL fallback.

Example ​

ts
import { createRigPreview, GnmRigBackend, jointTint } from 'gameable/character';

const preview = await createRigPreview({
  renderer,
  sink: splat,
  backend: new GnmRigBackend({ packFile: 'myra_head.aosrig' }),
  getBytes: (name) => files.get(name),
});
preview.setControls(headExt);
preview.render();

createSomaPoser() ​

ts
function createSomaPoser(skeleton, bones): SomaPoser;

The per-frame poser for a skeleton's bones. Allocation-free after construction.

Parameters ​

ParameterTypeDescription
skeletonSomaSkeletonThe skeleton.
bonesreadonly Object3D<Object3DEventMap>[]Its bones, in its order (createSomaRig's).

Returns ​

SomaPoser

The poser.


createSomaRig() ​

ts
function createSomaRig(skeleton): object;

The skeleton as three bones at rest (the stance), under one group: what the engine's animator and mixer drive, named as the file names them.

Parameters ​

ParameterTypeDescription
skeletonSomaSkeletonThe skeleton.

Returns ​

object

The group and the bones in the skeleton's order.

bones ​
ts
bones: Bone<Object3DEventMap>[];
root ​
ts
root: Group;

debugLiftDispatch() ​

ts
function debugLiftDispatch(slotCount): number;

Dispatch size for a slot range.

Parameters ​

ParameterTypeDescription
slotCountnumberSlots the preview owns.

Returns ​

number

Workgroups to dispatch — every slot is visited, including the ones past the vertex count, because those have to be cleared rather than left stale.

Example ​

ts
import { debugLiftDispatch } from 'gameable/character';

console.log(debugLiftDispatch(17_821)); // 279

fitVertsTransform() ​

ts
function fitVertsTransform(aabb, options?): object;

A rig -> object transform that centres the vertices and scales them to a target size.

The two rigs disagree about units (GNM metres, ORL centimetres) and neither puts a head anywhere near the origin, so a preview that did not normalise would render off screen at the wrong size and look like a failure. This is a VIEWING convenience and nothing downstream depends on it.

Parameters ​

ParameterTypeDescription
aabbVertsAABBThe backend's neutral-pose bounds, in its own units.
options{ height?: number; offset?: readonly [number, number, number]; scale?: number; }Placement overrides.
options.height?numberObject-space height the bounds are scaled to. Defaults to 0.35 m, a head at conversational distance.
options.offset?readonly [number, number, number]Where the fitted centre lands. Defaults to the origin.
options.scale?numberA uniform scale, overriding height outright.

Returns ​

object

A row-major 3x4 affine, ready for DebugLiftParams.transform, plus the scale it chose and the object-space centroid the normal shading needs.

centroid ​
ts
centroid: [number, number, number];
scale ​
ts
scale: number;
transform ​
ts
transform: number[];

flatTint() ​

ts
function flatTint(vertexCount, colour?): Uint32Array;

One flat colour for every vertex.

Parameters ​

ParameterTypeDescription
vertexCountnumberVertices to colour.
colourreadonly [number, number, number]RGB in [0, 1]. Defaults to a light neutral.

Returns ​

Uint32Array

One packed RGBA per vertex, opaque.


gatherFromRigNames() ​

ts
function gatherFromRigNames(rigNames): number[];

Parameters ​

ParameterTypeDescription
rigNamesstring[]The bundle's rig_names.json (N control names).

Returns ​

number[]

N indices into the 188-control arkitToMh output.

Throws ​

When rigNames is not a non-empty string array, or any name is missing from the canonical 188 ordering.


gazeToEyeRotations() ​

ts
function gazeToEyeRotations(gaze): [EyeQuaternion, EyeQuaternion];

Gaze [pitch_L, yaw_L, pitch_R, yaw_R] (radians) -> a rotation per eye.

The readable form, for tests and for the CPU reference. gazeToEyeRotationsInto is the same maths without the two tuples, and is what the per-frame path calls.

Parameters ​

ParameterTypeDescription
gazeArrayLike<number>Four radians: [pitch_L, yaw_L, pitch_R, yaw_R].

Returns ​

[EyeQuaternion, EyeQuaternion]

The left and right eye rotations, each (w, x, y, z).


gnmForward() ​

ts
function gnmForward(pack, headExt): GnmForwardResult;

head_ext -> head-local vertices, BEFORE the seam stitch and before skinning.

Reproduces BakedHead.forward: the linear expression model, then GNM's own eye rotation blended by each eye joint's skinning weight.

Parameters ​

ParameterTypeDescription
packAosRigPackThe parsed .aosrig, supplying the neutral, the basis and the eyes.
headExtArrayLike<number>One frame's packed rig vector — expression coefficients plus the four gaze angles, split by unpackHeadExt.

Returns ​

GnmForwardResult

The head-local vertices as (V,3) in METRES, before the seam stitch and before skinning.


gnmPose() ​

ts
function gnmPose(
   pack, 
   headExt, 
   jointWorld?
): Float32Array;

The full rig -> vertices path on the CPU: gnmForward, the baked neck seam, then linear-blend skinning against jointWorld.

jointWorld is J*16 row-major world matrices in the pack's compact joint order, defaulting to the pack's own rest (which makes the skinning the identity).

Parameters ​

ParameterTypeDefault valueDescription
packAosRigPackundefinedThe parsed .aosrig.
headExtArrayLike<number>undefinedOne frame's packed rig vector, as gnmForward takes it.
jointWorldFloat32Arraypack.restWorldJ*16 row-major world matrices in the pack's compact joint order. Defaults to pack.restWorld.

Returns ​

Float32Array

The posed vertices as (V,3) in METRES, in the body's bind space — bindTransform is folded into the skin matrices rather than applied here.


headExtNames() ​

ts
function headExtNames(layout): string[];

Every control name the layout declares, in order — the manifest's control_names.

Parameters ​

ParameterTypeDescription
layoutHeadExtLayoutThe pack's header.headExt.

Returns ​

string[]

layout.dim names: <region>_000-style per-coefficient names, then the four gaze angles.


headJointOf() ​

ts
function headJointOf(d): string;

The joint the head hangs from: the one the mouth follows.

Parameters ​

ParameterTypeDescription
dPick<AosrigSplatDescriptor, "version">A validated descriptor.

Returns ​

string

Head for version 2, c_head for version 1.


heightTint() ​

ts
function heightTint(verts, vertexCount): Uint32Array;

A colour per vertex from its height inside the vertex bounds — a plain ramp.

The backend-agnostic fallback: it needs nothing but the vertices, so it works for a rig whose pack this package cannot read at all.

Parameters ​

ParameterTypeDescription
vertsFloat32Array(V,3) vertices in the rig's own units.
vertexCountnumberVertices to colour.

Returns ​

Uint32Array

One packed RGBA per vertex, opaque. A zero-height bound yields a flat ramp value rather than a division by zero.


hiddenAlpha() ​

ts
function hiddenAlpha(gapM): number;

The listed splat's opacity factor for a lips' gap.

Parameters ​

ParameterTypeDescription
gapMnumberThe gap at the splat, metres (the two lip vertices' parting past rest, along up).

Returns ​

number

1 while the lips touch, 0 from 2 mm apart, a smoothstep between.


jointTint() ​

ts
function jointTint(pack): Uint32Array;

A colour per vertex from the vertex's dominant skinning joint, with the eyes called out.

This is the colouring that makes a rig bug obvious: a vertex weighted to the wrong joint is a wrongly-coloured patch on an otherwise clean head, and a pack whose eye weights did not survive the bake has no cyan in it at all.

Parameters ​

ParameterTypeDescription
packAosRigPackThe parsed .aosrig, for skinIndex, skinWeight and eyeWeights.

Returns ​

Uint32Array

One packed RGBA per vertex, opaque.


loadAosrigSplatBundle() ​

ts
function loadAosrigSplatBundle(
   url, 
   signal?, 
   options?
): Promise<AosrigSplatBundle>;

Load an extracted character archive, validating all content hashes.

Parameters ​

ParameterTypeDescription
urlstringURL of character.json.
signal?AbortSignalOptional cancellation signal.
options?AosrigSplatLoadOptionsWhich optional files to fetch, progress, a custom fetch (see AosrigSplatLoadOptions).

Returns ​

Promise<AosrigSplatBundle>

Verified descriptor and files.

Example ​

ts
const bundle = await loadAosrigSplatBundle('/characters/hero/character.json');
console.log(bundle.descriptor.splatCount);

loadCharacterBundle() ​

ts
function loadCharacterBundle(url, options?): Promise<CharacterBundle>;

Fetch and parse a character bundle.

Parameters ​

ParameterTypeDescription
urlstringThe bundle directory, or any string the supplied resolver understands.
optionsLoadCharacterBundleOptionsfetch / signal / resolver and the fp16 preference.

Returns ​

Promise<CharacterBundle>

Everything createCharacter needs: the parsed manifest with the engine's rig and expression blocks, per-branch mesh topology in manifest order, every eagerly fetched file kept resident, a resolver for the lazy ones, whether the fp16 decoders are to be used, and the resident byte total.

Example ​

ts
import { loadCharacterBundle } from 'gameable/character';

const bundle = await loadCharacterBundle('/assets/characters/myra/');
console.log(bundle.manifest.rig.backend, bundle.manifest.expressionSpace.dim);

orlDriveMode() ​

ts
function orlDriveMode(vertexCount): OrlDriveMode;

Decide the drive mode from the branch mesh alone.

Parameters ​

ParameterTypeDescription
vertexCountnumberThe branch mesh's vertex count.

Returns ​

OrlDriveMode

skin at exactly ORL_HEAD_VERTS, shell at or below ORL_SHELL_MAX_VERTS, and none for anything bigger — a real body or hair branch, which renders at its neutral pose.


packAosRig() ​

ts
function packAosRig(header, blobs): Uint8Array;

Build a pack.

Blobs are written in the given order and the header's key order is fixed, so packing the same bake twice is BYTE-IDENTICAL — which is what makes a re-bake diff meaningful.

Parameters ​

ParameterTypeDescription
headerOmit<AosRigHeader, "buffers">Everything but buffers, which is derived from the laid-out blobs.
blobsAosRigBlob[]The blobs, in the order they are written. Each is copied verbatim; the declared shape and dtype are what parseAosRig checks its length against.

Returns ​

Uint8Array

The whole container: magic, header JSON, then the 8-byte-aligned blobs.

Throws ​

When the header's encoded length does not converge in four passes.


packRgba() ​

ts
function packRgba(
   r, 
   g, 
   b, 
   a?
): number;

Pack a colour the way pack4x8unorm does.

Parameters ​

ParameterTypeDefault valueDescription
rnumberundefinedRed in [0, 1]; out-of-range values are clamped.
gnumberundefinedGreen in [0, 1].
bnumberundefinedBlue in [0, 1].
anumber1Alpha in [0, 1]. Defaults to 1.

Returns ​

number

The 32 bits a WGSL unpack4x8unorm reads back as that colour.

Example ​

ts
import { packRgba } from 'gameable/character';

console.log(packRgba(1, 0, 0, 1).toString(16)); // 'ff0000ff'

parseAosRig() ​

ts
function parseAosRig(bytes): AosRigPack;

Parse a .aosrig pack.

Throws on anything it cannot trust. A GNM head that silently loads half its expression basis renders a face that moves a little and looks nearly right, which is the failure mode with no symptom.

Parameters ​

ParameterTypeDescription
bytesArrayBuffer | Uint8Array<ArrayBufferLike>The downloaded pack, as bytes or a whole buffer.

Returns ​

AosRigPack

The parsed pack: the header plus typed views over the blobs. Only skinWeight is materialised (widened from its f16 lanes); everything else is a view into bytes. bindTransform falls back to the identity when the bake declared none.

Throws ​

On a bad magic, a length that disagrees with the header, a version or model this build does not read, a missing required blob, or any array whose length contradicts V, E, the joint list or maxInfluence.

Example ​

ts
import { parseAosRig } from 'gameable/character';

const pack = parseAosRig(await (await fetch('/assets/myra.aosrig')).arrayBuffer());
console.log(pack.vertexCount, pack.header.headExt.dim); // 17821 387

parseCorrectiveIndex() ​

ts
function parseCorrectiveIndex(value, descriptor): CorrectiveInfo[];

Validate corrective/index.json against the package it rides with.

Parameters ​

ParameterTypeDescription
valueunknownParsed JSON.
descriptor{ jointNames: readonly string[]; splatCount: number; }The package's splat count and joint names.
descriptor.jointNamesreadonly string[]-
descriptor.splatCountnumber-

Returns ​

CorrectiveInfo[]

The corrections, in the index's order.

Throws ​

When the format is not this one, a correction was made for another character (splat count), a drive names a joint the rig lacks, its angles are not in order, or a path is not a plain path under corrective/.


parseMouthHidden() ​

ts
function parseMouthHidden(
   bytes, 
   splatCount, 
   headVertexCount
): MouthHidden;

Parse mouth_hidden.bin.

Parameters ​

ParameterTypeDescription
bytesUint8ArrayThe file.
splatCountnumberThe character's splat count (rows must be under it).
headVertexCountnumberThe head model's vertex count (the lip vertices must be under it).

Returns ​

MouthHidden

The list.

Throws ​

On a bad magic or size, a row or vertex out of range, a kind that is not 1, 2 or 3, a row listed twice, or an up or forward that is not a direction.


parseSomaClips() ​

ts
function parseSomaClips(value, skeleton): object;

Validate a soma-clips file (a package's clips.json, or a shared clip file of the same format) against a skeleton. A clip that breaks a rule is left out and said, never the whole file.

Parameters ​

ParameterTypeDescription
valueunknownThe parsed JSON.
skeletonSomaSkeletonThe skeleton the clips play on.

Returns ​

object

The clips, and each refused clip with its reason.

clips ​
ts
clips: SomaClip[];
refused ​
ts
refused: object[];

Throws ​

When the file is not a soma-clips file at all.


parseSomaSkeleton() ​

ts
function parseSomaSkeleton(value, jointNames): SomaSkeleton;

Validate skeleton.json against the descriptor's joint names.

Parameters ​

ParameterTypeDescription
valueunknownThe parsed JSON.
jointNamesreadonly string[]character.json's jointNames.

Returns ​

SomaSkeleton

The skeleton.

Throws ​

When the file is not an aosrig-v2 skeleton of those joints.


plugPush() ​

ts
function plugPush(gapM): number;

How far back along the head's forward a plug is pushed for a lips' gap.

Parameters ​

ParameterTypeDescription
gapMnumberThe gap, metres.

Returns ​

number

Metres.


prepareLiftDevice() ​

ts
function prepareLiftDevice(renderer): GPUDevice;

The GPUDevice a character's compute runs on: the renderer's own.

Throws CharacterUnsupportedError when the renderer is on the WebGL fallback backend or has not been initialised, because there is then no device to borrow and no CPU path to demote to.

Parameters ​

ParameterTypeDescription
rendererRendererLikeThe engine's WebGPURenderer, already init()ed; only backend.device and backend.isWebGPUBackend are read.

Returns ​

GPUDevice

The renderer's own device, confirmed to grant at least REQUIRED_STORAGE_BUFFERS storage buffers per shader stage.

Example ​

ts
import { attachOrtDevice, prepareLiftDevice } from 'gameable/character';

await renderer.init();
const device = prepareLiftDevice(renderer);
attachOrtDevice(device); // before the first ONNX session

regionSlices() ​

ts
function regionSlices(layout): Record<string, {
  end: number;
  start: number;
}>;

Where each expression region sits inside head_ext.

Parameters ​

ParameterTypeDescription
layoutHeadExtLayoutThe pack's header.headExt.

Returns ​

Record<string, { end: number; start: number; }>

A { start, end } half-open COEFFICIENT range per region name, tiled in declaration order from 0. Gaze sits past the last region and is not included.


releaseSharedClips() ​

ts
function releaseSharedClips(): void;

Forget the page's shared clip packs (their bytes and the clips read from them), so their memory goes once the characters that play them are gone. A host calls it when its last character is disposed; a character loaded afterwards fetches the pack again.

Returns ​

void

Nothing.

Example ​

ts
character.dispose();
releaseSharedClips(); // the last one: the shared pack can go

somaAnimationClip() ​

ts
function somaAnimationClip(clip, skeleton): AnimationClip;

A clip as three's mixer plays it: a quaternion track per joint it names and the Hips' position from its root track, in Root's frame. userData.aos carries its loop flag and speed, as the engine's clip index reads them.

Parameters ​

ParameterTypeDescription
clipSomaClipA parsed clip.
skeletonSomaSkeletonThe skeleton it plays on.

Returns ​

AnimationClip

The clip, named as the package names it.


unpackHeadExt() ​

ts
function unpackHeadExt(
   headExt, 
   layout, 
   out?
): object;

head_ext -> the two halves the model consumes.

The split is a slice, not a parse, and it is the one place the 383/4 boundary is written down on this side. Gaze is [pitch_L, yaw_L, pitch_R, yaw_R] in radians, head-local: pitch > 0 looks DOWN (about +X), yaw > 0 looks toward character-LEFT (about +Y). Neck and head rotation are NOT here — they live in the body joints neck_01 / neck_02 / head.

Parameters ​

ParameterTypeDescription
headExtArrayLike<number>One frame's rig vector, at least layout.dim long. A longer one is accepted and its tail ignored.
layoutHeadExtLayoutThe pack's header.headExt, which owns the 383/4 boundary.
out?{ expr: Float32Array; gaze: Float32Array; }Optional destination buffers, reused across frames so the per-frame caller (GnmRigBackend.encode) allocates nothing. Either buffer that is too short is replaced by a fresh one of the right width.
out.expr?Float32ArrayDestination for the exprDim expression coefficients.
out.gaze?Float32ArrayDestination for the gazeDim gaze angles.

Returns ​

object

The exprDim expression coefficients and the gazeDim gaze angles — out's buffers when they were supplied and wide enough, else fresh arrays.

expr ​
ts
expr: Float32Array;
gaze ​
ts
gaze: Float32Array;

Throws ​

When headExt is shorter than the layout declares.

Example ​

ts
import { parseAosRig, unpackHeadExt } from 'gameable/character';

const pack = parseAosRig(bytes);
const { expr, gaze } = unpackHeadExt(new Float32Array(pack.header.headExt.dim), pack.header.headExt);
console.log(expr.length, gaze.length);

uvTint() ​

ts
function uvTint(uv, vertexCount): Uint32Array;

A colour per vertex from the mesh's UVs: red = u, green = v.

The UV view is how a texture-space problem shows itself — a seam in the wrong place, a flipped island, a bake that dropped uv entirely (every vertex then comes out the same colour, which is itself the finding).

Parameters ​

ParameterTypeDescription
uvFloat32Array(V,2) texture coordinates.
vertexCountnumberVertices to colour; uv must hold at least 2 * vertexCount.

Returns ​

Uint32Array

One packed RGBA per vertex, opaque.


verifyOrtDevice() ​

ts
function verifyOrtDevice(device): Promise<boolean>;

Confirm ORT is really on device once the first session exists.

The getter resolves to whatever ORT actually built, so this is the only honest check — a successful set is an intention, not an outcome. A mismatch DEMOTES loudly rather than handing the lift a foreign buffer.

Parameters ​

ParameterTypeDescription
deviceGPUDeviceThe device ORT was asked to adopt, from prepareLiftDevice.

Returns ​

Promise<boolean>

True when ORT's device is that same object; false — with a warning — when ORT built its own or exposes no env.webgpu, meaning outputs must be downloaded.


writeDebugLiftParams() ​

ts
function writeDebugLiftParams(params, into?): ArrayBuffer;

Pack the params uniform.

Separate from the class so the byte layout can be asserted in a node test — a mis-packed uniform is a head at the wrong scale or in the wrong place, with no error anywhere.

Parameters ​

ParameterTypeDescription
paramsDebugLiftParamsThe values to write.
into?ArrayBufferAn existing 96-byte buffer to fill. A fresh one is allocated when omitted, which the per-frame path never does.

Returns ​

ArrayBuffer

The buffer, filled.

Throws ​

When transform is not 12 numbers, or into is the wrong size.


yieldingPause() ​

ts
function yieldingPause(ms?): () => Promise<void>;

A pause for a character's load and build (the loader's and createAosrigSplat's pause): once ms of work has gone by, it gives the page back for a moment (a message-channel task, so a frame that is due draws, and a hidden tab, whose frames and timers are held back, still loads at full speed). The app's own scene keeps drawing while a character is unpacked and built.

Parameters ​

ParameterTypeDefault valueDescription
msnumber8Work allowed between yields.

Returns ​

The pause.

() => Promise<void>

Example ​

ts
const pause = yieldingPause();
const bundle = await loadAosrigSplatBundle(url, signal, { pause });