@gameable/character
Classes
BonePoseWriter
Writes a BonePose onto a skeleton's bones and takes it off again. One per character; the bones are found by name once per names array.
Constructors
Constructor
ts
new BonePoseWriter(root, warn?): BonePoseWriter;Parameters
| Parameter | Type | Description |
|---|---|---|
root | Object3D | The skeleton's root (the bones are found under it by name). |
warn | (message) => void | Where to say the names the skeleton has no bone for. |
Returns
Properties
missing
ts
readonly missing: string[] = [];The names given that the skeleton has no bone for (said once, when they are bound).
Accessors
driven
Get Signature
ts
get driven(): boolean;Whether the game has the body now (a pose has been written and not let go).
Returns
boolean
Methods
release()
ts
release(): void;Hand the body back to the clips: the bones as they were when the game took them.
Returns
void
write()
ts
write(pose, scale): void;Lay the pose on the bones. Each bone keeps what the clips had on it when first written.
Parameters
| Parameter | Type | Description |
|---|---|---|
pose | BonePose | The pose. |
scale | number | How much larger the character is drawn than its rig (the hips are given in drawn metres). |
Returns
void
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
| Parameter | Type |
|---|---|
reason | string |
Returns
Overrides
ts
Error.constructorProperties
name
ts
readonly name: "CharacterUnsupportedError" = 'CharacterUnsupportedError';Overrides
ts
Error.namereason
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
| Parameter | Type | Description |
|---|---|---|
options | DebugVertexLiftOptions | See DebugVertexLiftOptions. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
encoder | GPUCommandEncoder | The frame's encoder. |
Returns
void
setShading()
ts
setShading(shading): void;Change the colour source.
Parameters
| Parameter | Type | Description |
|---|---|---|
shading | DebugShading | tint, normal or flat. |
Returns
void
setSigma()
ts
setSigma(sigma): void;Change the gaussian radius.
Parameters
| Parameter | Type | Description |
|---|---|---|
sigma | number | Radius in object space, metres. |
Returns
void
setTransform()
ts
setTransform(transform, centroid?): void;Change the rig -> object placement.
Parameters
| Parameter | Type | Description |
|---|---|---|
transform | readonly number[] | Row-major 3x4 affine, 12 numbers. |
centroid? | readonly [number, number, number] | Object-space centre for normal shading. Left alone when omitted. |
Returns
void
FightLayer
Constructors
Constructor
ts
new FightLayer(
skeleton,
set,
stature,
height,
options,
name?
): FightLayer;Parameters
| Parameter | Type | Default value |
|---|---|---|
skeleton | FightSkeletonLike | undefined |
set | FightSet | undefined |
stature | number | undefined |
height | number | undefined |
options | { bounds?: FightBounds; random?: () => number; } | undefined |
options.bounds? | FightBounds | undefined |
options.random? | () => number | undefined |
name | string | 'package' |
Returns
Properties
body
ts
readonly body: FightBody;driver
ts
readonly driver: FightDriver;handed
ts
readonly handed: BonePose & object;the pose handed to the bone pose door: the body's names bound once, the driver's rotations, the hips copied
Type Declaration
hips
ts
readonly hips: object;hips.joint
ts
readonly joint: string;hips.x
ts
x: number;hips.y
ts
y: number;hips.z
ts
z: number;request
ts
request: FightRequest | null = null;the request for this step, or none (the pose stays as it was)
target
ts
target: FightLayer | null = null;the other fighter this one fights (the request's target, resolved by the bridge before the step), or none
turn
ts
turn: number = 0;the line's turn off the floor's, as the last step had it
Accessors
leads
Get Signature
ts
get leads(): boolean;the pose is the driver's own place (thrown, lying, rising): the entity follows it
Returns
boolean
Methods
contact()
ts
contact(def, out): boolean;Whether this fighter's strike meets the other's body this step, as Fight Lab tests it: the strike's areas (the striking part and the path its tip swept) against the other's hurt areas, in order, the first pair that touch; out the part struck, the point (world; its depth the strike's tip's) and the struck body's nearer leg.
Parameters
| Parameter | Type |
|---|---|
def | FightLayer |
out | FightContact |
Returns
boolean
fall()
ts
fall(
v,
spin,
turn,
kick?
): void;Parameters
| Parameter | Type | Default value |
|---|---|---|
v | { x: number; y: number; z: number; } | undefined |
v.x | number | undefined |
v.y | number | undefined |
v.z | number | undefined |
spin | number | undefined |
turn | number | undefined |
kick | boolean | false |
Returns
void
getUp()
ts
getUp(quick): FightRise | null;Parameters
| Parameter | Type |
|---|---|
quick | boolean |
Returns
FightRise | null
hit()
ts
hit(h): void;Parameters
| Parameter | Type |
|---|---|
h | FightHit |
Returns
void
state()
ts
state(facing): FightState;Parameters
| Parameter | Type |
|---|---|
facing | number |
Returns
step()
ts
step(): void;one fixed step: the request on the driver
Returns
void
GnmRigBackend
GNM as a RigBackend.
Implements
Constructors
Constructor
ts
new GnmRigBackend(options?): GnmRigBackend;Parameters
| Parameter | Type |
|---|---|
options | GnmRigBackendOptions |
Returns
Properties
kind
ts
readonly kind: "gnm";Which implementation this is.
Implementation of
Accessors
assets
Get Signature
ts
get assets(): AosRigPack;The parsed pack — the topology and UVs a caller may want for a preview mesh. After releaseBlobs it is the light pack: the big blobs are empty.
Returns
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
vertexCount
Get Signature
ts
get vertexCount(): number;Vertices produced per pose.
Returns
number
Vertices produced per pose.
Implementation of
vertsAABB
Get Signature
ts
get vertsAABB(): VertsAABB;Bounds of the neutral pose, for the sink's bounding sphere.
Returns
Bounds of the neutral pose, for the sink's bounding sphere.
Implementation of
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
Methods
bytes()
ts
bytes(): number;Approximate GPU + wasm bytes this backend holds, for memoryReport().
Returns
number
Implementation of
dispose()
ts
dispose(): void;Release the wasm heap and every GPU buffer. Idempotent.
Returns
void
Implementation of
encode()
ts
encode(encoder): void;Record the per-vertex pass into encoder. No submit, no fence, no readback.
Parameters
| Parameter | Type |
|---|---|
encoder | GPUCommandEncoder |
Returns
void
Implementation of
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
| Parameter | Type |
|---|---|
options | RigBackendInit |
Returns
Promise<void>
Implementation of
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
| Parameter | Type | Description |
|---|---|---|
options | Omit<RigBackendInit, "device"> | As init, without the device. |
Returns
Promise<void>
Nothing.
releaseBlobs()
ts
releaseBlobs(): number;Let the pack's file go once the head's inputs are made: on a phone four riders held their head file a second time this way (42 MB a head, 5 Oct 2026). Keeps what the frame reads (the header, the counts, the eye positions, the rest joints and their parents, the basis scale, the bind transform, the mouth's small lists, whether the bake had a neck seam) as small copies of their own; the big blobs (neutral, basis, skin, eye weights, faces, quads, uv, the seam's values) become empty arrays of their type. After it assets is that light pack and runCpu throws; the frame (setControls, frameValues, encode, the joints) runs exactly as before. Call it after the last build step that reads the pack (the head's inputs, the rest vertices, the mouth). Safe to call twice.
Returns
number
The bytes of the file no longer held (0 on a second call).
Example
ts
const head = createGnmNode(backend.assets, false);
const freed = backend.releaseBlobs(); // 42 MB for a studio headrunCpu()
ts
runCpu(controls): Promise<Float32Array<ArrayBufferLike>>;The CPU reference, for calibration. Never per frame — see gnmReference.ts.
Parameters
| Parameter | Type | Description |
|---|---|---|
controls | Float32Array | A 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
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
| Parameter | Type | Description |
|---|---|---|
controls | Float32Array | Up to head_ext.dim floats: the expression coefficients, then the four gaze angles in radians. A longer vector throws. |
Returns
void
Implementation of
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
| Parameter | Type | Description |
|---|---|---|
overrides | readonly 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
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
| Parameter | Type | Description |
|---|---|---|
worlds | Float32Array | J*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
| Parameter | Type |
|---|---|
options | OrlRigBackendOptions |
Returns
Properties
kind
ts
readonly kind: "orl";Which implementation this is.
Implementation of
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
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
vertsAABB
Get Signature
ts
get vertsAABB(): VertsAABB;Bounds of the neutral pose, for the sink's bounding sphere.
Returns
Bounds of the neutral pose, for the sink's bounding sphere.
Implementation of
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
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
| Parameter | Type | Description |
|---|---|---|
lin | Float32Array | The 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. |
offset | Float32Array | The 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
dispose()
ts
dispose(): void;Release the wasm heap and every GPU buffer. Idempotent.
Returns
void
Implementation of
encode()
ts
encode(encoder): void;Record the per-vertex pass into encoder. No submit, no fence, no readback.
Parameters
| Parameter | Type |
|---|---|
encoder | GPUCommandEncoder |
Returns
void
Implementation of
init()
ts
init(options): Promise<void>;Build the wasm/GPU resources. Idempotent; throws loudly on a bad asset.
Parameters
| Parameter | Type |
|---|---|
options | RigBackendInit |
Returns
Promise<void>
Implementation of
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
| Parameter | Type | Description |
|---|---|---|
controls | Float32Array | One 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
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
| Parameter | Type |
|---|---|
controls | Float32Array |
Returns
void
Implementation of
setJointOverrides()
ts
setJointOverrides(overrides): boolean;No addressable joint namespace yet — head aim rides the body rig, not the DNA.
Parameters
| Parameter | Type | Description |
|---|---|---|
overrides | readonly 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
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
| Parameter | Type |
|---|---|
capacity | number |
Returns
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
| Parameter | Type | Description |
|---|---|---|
count | number | How many splat slots the branch needs; a positive integer. |
Returns
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
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
| Parameter | Type | Description |
|---|---|---|
range | SlotRange | The 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 from the teeth's points; 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.
AosrigHairState
The hair on a character, as a host reads it (runtime.hair).
Extended by
Properties
frames
ts
readonly frames: number;The frames simulated so far.
ms
ts
readonly ms: number;Her sim's wall time last frame, ms (0 before the first frame).
rows
ts
readonly rows: number;The rows riding the strands.
slots
ts
readonly slots: number;The bone slots after the rig's joints (her proxies and every strand point).
strands
ts
readonly strands: number;The guide strands.
Methods
reset()
ts
reset(): void;After a teleport or a pose jump: the strands snap to their rest targets on the next frame.
Returns
void
setParams()
ts
setParams(params): void;Live tuning (HAIR_DEFAULTS are Sara's chosen settings); bodyCollide stays 0 until the clean body lands.
Parameters
| Parameter | Type | Description |
|---|---|---|
params | Partial<HairParams> | The settings to change. |
Returns
void
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;lit
ts
lit: boolean;A frozen mouth lit softly from the camera (true, the default) or unlit.
mode
ts
mode: AosrigMouthMode | null;A frozen mouth's drawing (AosrigMouthMode); 'textured' without the picture draws plain. Null for a mouth of the teeth's points.
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;Last parting in metres: polygon area / width for 'studio', the sampled lip points for the other windows.
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;setBack
ts
readonly setBack: number;The frozen mouth's set-back along the face's forward, metres (0 for the teeth's points).
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 | null;The teeth's splat sink (drawn into the inside's own small render); null for a frozen mouth.
thresholds
ts
thresholds: object;How far apart the lips' points must be for the inside to show, metres: nothing below start, all from full. The drawn window's defaults are DRAWN_OPEN_START_M and DRAWN_OPEN_FULL_M (1 and 2 mm, the studio's rule), the edge window's OPEN_START_M and OPEN_FULL_M; a page may tune them.
full
ts
full: number;start
ts
start: number;window
ts
window: "studio" | "drawn" | "edges";What the window's edges follow: 'studio' (the default), the expressed head rim minus its neutral opening, inset 1.5 mm as pcMouth draws it; or 'drawn': the lips a person sees, strip by strip across the whole mouth (their seen points' median move), and the inside shows once they are DRAWN_OPEN_START_M apart on average across the mouth; or 'edges', the lips' edge points picked at rest (the window before). Switching also switches thresholds to that window's own.
Methods
compile()
ts
compile(): Promise<void>;TSL: build the two passes' pipelines and the mask's and the inside's programs ahead of the first open frame (three builds them synchronously inside that frame otherwise). Once; a failure is warned and left to that frame. Nothing on the WGSL path (its pipelines were built through the device's async call already).
Returns
Promise<void>
Resolves once everything is built.
dispatch()
ts
dispatch(): void;TSL: run the two passes (teeth, mesh) with renderer.compute, after the head pass. Nothing on the WGSL path.
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
encode()
ts
encode(pass): void;WGSL: record the two dispatches (teeth, mesh) into the skin pass, after the head's blend. Nothing on the TSL path.
Parameters
| Parameter | Type |
|---|---|
pass | GPUComputePassEncoder |
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;passes()
ts
passes(): ComputeNode[];TSL: the passes to run this frame (teeth, mesh), each pointed at this character's buffers, for a caller that runs them in its own renderer.compute call after the head pass (one call a frame for the whole character). Empty while the mouth is shut and on the WGSL path.
Returns
ComputeNode[]
The compute nodes, in order.
update()
ts
update(
controls,
skin,
camera
): boolean;Follow this frame's face, head joint and camera. Allocation-free.
Parameters
| Parameter | Type | Description |
|---|---|---|
controls | ArrayLike<number> | The head's control vector (expression first). |
skin | ArrayLike<number> | The head joint's skin matrix, column-major 4x4, into the splat's frame. |
camera | Camera | The 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;bandCap
ts
bandCap: boolean;Cap the deformed narrow/wide-band shapes as the lips part. Off, their sizes are unchanged.
count
ts
readonly count: number;Mouth-neighbourhood splats, and the file's counts of plugs and narrow-band points.
enabled
ts
enabled: boolean;Apply the gap fades, opening hide and dark sheet when no interior is drawn. Off, these rules do not act.
plugs
ts
readonly plugs: number;AosrigMouthMeshFiles
The files the package's frozen mouth comes as.
Properties
glb
ts
glb: Uint8Array;info
ts
info: MouthMeshInfo;texture?
ts
optional texture?: Uint8Array<ArrayBufferLike>;The picture's bytes (PNG), when it was fetched (the textured mode only).
AosrigMouthOptions
What the host supplies for a character's mouth.
Properties
acquireBuffer?
ts
optional acquireBuffer?: (attribute) => GPUBuffer;On the WGSL path: the GPUBuffer behind a storage attribute (@gameable/splat's acquireStorageGPUBuffer).
Parameters
| Parameter | Type |
|---|---|
attribute | object |
Returns
GPUBuffer
look?
ts
optional look?: "engine" | "studio" | "parts";How a frozen mouth drawn without its picture looks: 'studio' (the default) or its alias 'parts', the teeth, gums and tongue in their part colours lit as the studio lights them (a key from the camera's side and a small fill, both darkened toward the back of the mouth); or 'engine', the lighter, evenly lit colours drawn before 2026-09-28. The picture is not changed.
mode?
ts
optional mode?: AosrigMouthMode;The frozen mouth's first mode (AosrigMouth.mode), when the package carries it. The picture is fetched and uploaded only for 'textured'. Default 'off'.
releaseBuffer?
ts
optional releaseBuffer?: (attribute) => void;Gives a storage attribute's buffers back (@gameable/splat's releaseStorageAttribute).
Parameters
| Parameter | Type |
|---|---|
attribute | object |
Returns
void
renderer?
ts
optional renderer?: WebGPURenderer;The renderer, when createAosrigSplat is not given one; it is the same renderer. Kept for hosts written before 2 Oct 2026.
sink?
ts
optional sink?: SplatSink;A second splat sink for the teeth's points, with room for teeth.json's count (the teeth's points only: a package's frozen mouth has none).
textureSize?
ts
optional textureSize?: number;The picture's side on the GPU, pixels (a phone may ask for 1024). Default: the file's.
window?
ts
optional window?: "studio" | "drawn" | "edges";The window's first rule (AosrigMouth.window). Default 'studio': the head rim's opening past rest.
Methods
onError()?
ts
optional onError(error): void;Told when the mouth cannot be built; the character then draws without it.
Parameters
| Parameter | Type |
|---|---|
error | unknown |
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;header
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: "linear" | "srgb";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>>;hair?
ts
optional hair?: object;Sara's strand hair (hair/hair.json and hair/hair.bind.bin, her builder's output on the package's rows), when the package lists them and the host asked (hair, default true); hash-checked. Absent otherwise.
bind
ts
bind: Uint8Array;json
ts
json: Uint8Array;level
ts
level: CharacterLevel;What was loaded: the package folder's name, its level name (_ogs512, _ogs256, _ogs128; null for a build) and its points, as said in the loader's first line.
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.
mouthMesh?
ts
optional mouthMesh?: AosrigMouthMeshFiles;The package's frozen mouth (mouth.json naming mouth.glb and mouth_texture.png, the picture fetched for the textured mode only), when asked for and there; the teeth's points are then not fetched. Absent otherwise.
shared?
ts
optional shared?: Map<string, Uint8Array<ArrayBufferLike>>;With keepShared: file parts, head and skeleton files, by SHA-256, for another 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
animation?
ts
optional animation?: unknown;What the creator meant by the package's animations ({ intent: 'included' | 'style' | 'none', clips?, style?, life? }). Read by gameable/animation's readAnimationIntent; absent, the package says nothing.
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?: "linear" | "srgb";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;
parts?: object[];
sha256: string;
src?: string;
}>;format
ts
format: "aosrig-splat";headVertexCount
ts
headVertexCount: number;holds?
ts
optional holds?: unknown;Parts that stay as captured under every clip ({ arms, at: 'rest', joints }, both collar bones and everything below for a character whose arms are held). Read by gameable/animation's readHolds; absent, nothing is held.
jointNames
ts
jointNames: string[];life?
ts
optional life?: unknown;The character's Life setting, as the studio keeps it (how alive it is between the lines: level, "Looks at you", blinks). Read by gameable/animation's readLifeSetting; absent, the defaults.
look?
ts
optional look?: CharacterLook;How the package asks to be drawn, when it says (the studio's exporter may write it): the point shape ('engine', three's own; 'studio', the studio page's; 'size-aware'; 'full-opacity') and where the points' colours add up ('stored', in the file's own space as the studio adds them, or 'linear', in linear light). Absent: DEFAULT_CHARACTER_LOOK, the colours as stored with the size-aware point (Isaac's choice on the looks sheet of 30 Sep 2026). The game's manifest entry (rig.look) and the bridge's extras override it.
motion?
ts
optional motion?: unknown;The package's own motion set ({ src, format: 'soma-motion', takes? }: whole takes of a performer on this skeleton, text or the compact binary, beside character.json). The character bridge moves the character by it, laid over the engine's shared set; absent, the character moves by the shared set alone.
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.
poseAdjust?
ts
optional poseAdjust?: unknown;The character's own pose adjustment ({ version: 1, space: 'parent', adjust: { joint: [x, y, z, w] }, move? }): a person's straightening of the character that holds under every animation. The package's skeleton at rest and its own clips carry it already; the character bridge lays it on the takes of a shared motion set, which do not. Absent, the takes are drawn as stored.
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.
clipFilesWin?
ts
optional clipFilesWin?: boolean;Version 2 only: a clip of a clipFiles file takes the place of the package's own clip of the same name, and of the shared pack's (the core's standard locomotion set over a package's copies of the pack's walk and run). Default false: the package's own clips win, then the pack's, then the files', in that order.
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
| Parameter | Type |
|---|---|
input | URL | RequestInfo |
init? | RequestInit |
Returns
Promise<Response>
firstPoints?
ts
optional firstPoints?:
| {
count: number;
sha256: string;
src: string;
}
| null;The first part of the points, with onPoints: a packed points file of its own that draws the character by itself (the studio's published copy names one, first.psp: every point without its highlights, a third of the bytes). Fetched, hash-checked and handed to onFirstPoints before the whole points, so a host draws the character still from it while they come. src is relative to character.json. One that does not come or does not match is said and skipped: never the reason a character does not load.
hair?
ts
optional hair?: boolean;The strand hair's files (hair/), when the package carries them. Default true.
keepShared?
ts
optional keepShared?: boolean;Keep file parts and the head and skeleton files in the bundle's shared, by SHA-256, for another character or a fuller 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.
mouthMesh?
ts
optional mouthMesh?: "off" | "plain" | "textured";The package's frozen mouth (mouth.glb) instead of the teeth's points: 'plain' fetches mouth.json and mouth.glb, 'textured' the picture too. Default 'off' (nothing of it fetched). Tala Me This draws Tala with it.
name?
ts
optional name?: string;What the page calls this character (its manifest id), for the loader's lines: the level it loads, said first, and a refusal in words. Default: the package folder's name.
onFirstPoints?
ts
optional onFirstPoints?: (ply, descriptor) => void;Handed the first part's points (unpacked), with firstPoints.
Parameters
| Parameter | Type |
|---|---|
ply | Uint8Array |
descriptor | AosrigSplatDescriptor |
Returns
void
onPoints?
ts
optional onPoints?: (ply, descriptor) => void;The points first: when given, the package's points (character.ply, unpacked when they come packed) are fetched alone before any other file starts, and handed here the moment they are in, so a host can draw the character still while the rest comes (the studio's order, webapp/src/studio/characterParts.ts: nothing waits for the head or the clips before the first picture). The bundle the load resolves with is the same either way.
Parameters
| Parameter | Type |
|---|---|
ply | Uint8Array |
descriptor | AosrigSplatDescriptor |
Returns
void
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
| Parameter | Type |
|---|---|
loadedBytes | number |
totalBytes | number |
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 and parts already downloaded and checked, by SHA-256 (another character sharing a head part, or a fuller copy sharing its head and skeleton): used instead of fetching 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.
Extended by
Properties
corrective
ts
readonly corrective: AosrigCorrectiveState | null;The pose corrections, when the package carries corrective/; else null.
hair
ts
readonly hair: AosrigHairState | null;Sara's strand hair, when the package carries hair/ and it fits this splat (hair/hairRuntime.ts): its guide strands are bone slots after the rig's joints, ticked each frame; 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
| Parameter | Type |
|---|---|
arkit | Float32Array |
out | Float32Array |
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 or the package's frozen meshes), when the package carries them, the head pack its mouth and the host asked for it; else null.
Methods
compile()
ts
compile(): Promise<void>;The GPU programs built ahead of the first frame (the deformation's passes, the mouth's); already awaited by createAosrigSplat, so a host that awaits it again (a swap, before it marks the copy compiled) waits for nothing more.
Returns
Promise<void>
Resolves once everything is built.
dispose()
ts
dispose(): void;Returns
void
render()
ts
render(
expression,
gaze,
camera,
dt?
): void;Pose face, body and splats after AnimationMixer and procedural aim have run.
Parameters
| Parameter | Type | Description |
|---|---|---|
expression | Float32Array | The face's controls. |
gaze | ArrayLike<number> | The eyes' turn. |
camera | Camera | The camera the sink is drawn for. |
dt? | number | The frame's time step, s, for the strand hair (the clock's own frame when the host gives none). |
Returns
void
setEyeShear()
ts
setEyeShear(frame): void;Copy calibrated rest-space eye volumes and shear vectors, or clear with null.
Parameters
| Parameter | Type |
|---|---|
frame | EyeShear | null |
Returns
void
setJointAffines()
ts
setJointAffines(names, affines): void;Copy per-joint row-major [A-I | offset] surface deltas, or clear with null.
Parameters
| Parameter | Type |
|---|---|
names | readonly string[] |
affines | Float32Array<ArrayBufferLike> | null |
Returns
void
setPointShare()?
ts
optional setPointShare(share): void;A test lever on the fallback skin: draw this share of the points (0 to 1), an evenly spread subset. Absent on the WebGPU runtime.
Parameters
| Parameter | Type |
|---|---|
share | number |
Returns
void
setSkinOptions()?
ts
optional setSkinOptions(options): unknown;A test lever on the fallback skin: how it spends the chip's time (WebglSkinOptions in webglSkin.ts: the skin in the vertex stage or in a pass of its own, the lazy fetches, the colour bands). Absent on the WebGPU runtime.
Parameters
| Parameter | Type |
|---|---|
options | { boneMask?: "all" | readonly string[] | "arms and hands" | "upper body"; discBelowPx?: number; drawScale?: number; drawShape?: "auto" | "full" | "disc"; extentSigma?: number; lazyFetch?: boolean; leanOrderUpload?: boolean; maskMode?: "compact" | "live"; maxPointPx?: number; minContribution?: number; nearCutM?: number; poseHz?: number; shBands?: 0 | 2 | 3 | 1; skin?: "pass" | "vertex" | "stream"; sortMaxPerSecond?: number; streamLean?: boolean; } |
options.boneMask? | "all" | readonly string[] | "arms and hands" | "upper body" |
options.discBelowPx? | number |
options.drawScale? | number |
options.drawShape? | "auto" | "full" | "disc" |
options.extentSigma? | number |
options.lazyFetch? | boolean |
options.leanOrderUpload? | boolean |
options.maskMode? | "compact" | "live" |
options.maxPointPx? | number |
options.minContribution? | number |
options.nearCutM? | number |
options.poseHz? | number |
options.shBands? | 0 | 2 | 3 | 1 |
options.skin? | "pass" | "vertex" | "stream" |
options.sortMaxPerSecond? | number |
options.streamLean? | boolean |
Returns
unknown
setSortMode()?
ts
optional setSortMode(mode): void;A test lever on the fallback skin: when the draw order is remade. Absent on the WebGPU runtime.
Parameters
| Parameter | Type |
|---|---|
mode | "frozen" | "pose" | "turn" | "slow" |
Returns
void
swapLevel()?
ts
optional swapLevel(bundle, pause?): Promise<void>;The fallback skin only: take another level of the same character into this runtime without a new material (WebglAosrigSplat.swapLevel); the sink's capacity bounds the levels it can take.
Parameters
| Parameter | Type |
|---|---|
bundle | AosrigSplatBundle |
pause? | () => Promise<void> |
Returns
Promise<void>
warmSkinVariants()?
ts
optional warmSkinVariants(modes, nextFrame): Promise<void>;The fallback skin only: build and compile the skin variants a page will switch between, once, behind its loading screen.
Parameters
| Parameter | Type |
|---|---|
modes | readonly ("pass" | "vertex" | "stream")[] |
nextFrame | () => Promise<void> |
Returns
Promise<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
blinks?
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.
AutoDetail
The governor.
Methods
add()
ts
add(character): void;Parameters
| Parameter | Type |
|---|---|
character | AutoDetailCharacter |
Returns
void
budget()
ts
budget(): object;The scene budget in force, points (0 = none), and the points the levels shown add up to.
Returns
object
points
ts
points: number;shown
ts
shown: number;dispose()
ts
dispose(): void;No more changes.
Returns
void
frame()
ts
frame(fps, dt): void;Once a frame: the frames a second now and the frame's length in seconds.
Parameters
| Parameter | Type |
|---|---|
fps | number |
dt | number |
Returns
void
remove()
ts
remove(id): void;Parameters
| Parameter | Type |
|---|---|
id | string |
Returns
void
setBudgetPoints()
ts
setBudgetPoints(points): void;Set the scene budget, points (0 = none).
Parameters
| Parameter | Type |
|---|---|
points | number |
Returns
void
setTargetFps()
ts
setTargetFps(fps): void;Parameters
| Parameter | Type |
|---|---|
fps | number |
Returns
void
state()
ts
state(): object[];What it knows: per character the level, the wanted-by-size level, whether a swap is in flight.
Returns
object[]
AutoDetailCharacter
A character the governor looks after.
Properties
id
ts
readonly id: string;Its name in the log.
levels
ts
readonly levels: readonly string[];Its levels, lightest first (['128', '256', '512']); the ones it has.
Methods
current()
ts
current(): string;The level shown now.
Returns
string
points()
ts
points(level): number;Points at a level (the package's splatCount), to pick the heaviest character to step down.
Parameters
| Parameter | Type |
|---|---|
level | string |
Returns
number
rank()?
ts
optional rank(): number;The game's own order under the scene budget, 0 first (a fight: the player, then the opponent, then the rest), before any size or view: a lower rank gets the heavier level the budget leaves. Optional: without it the looked-at or nearest character comes first.
Returns
number
screenHeightPx()
ts
screenHeightPx(): number;Its height on screen this frame, pixels (0 when out of view).
Returns
number
swap()
ts
swap(level): Promise<void>;Show this level; resolves when the new level draws (the old one is removed by the page after).
Parameters
| Parameter | Type |
|---|---|
level | string |
Returns
Promise<void>
viewAngleDeg()?
ts
optional viewAngleDeg(): number;The angle between the view's direction and the character, degrees (0 = looked straight at). Optional.
Returns
number
AutoDetailEvent
What the governor reports.
Properties
at
ts
readonly at: number;fps
ts
readonly fps: number;Why: the frames a second (budget steps) or the height on screen (size steps).
from
ts
readonly from: string;id
ts
readonly id: string;kind
ts
readonly kind: "size" | "failed" | "step-up" | "step-down" | "budget-down" | "budget-up";screenHeightPx
ts
readonly screenHeightPx: number;to
ts
readonly to: string;AutoDetailOptions
The governor's numbers.
Properties
adaptBudget?
ts
readonly optional adaptBudget?: boolean;Let the budget move with the frames (needs budgetPoints > 0). Default true when targetFps is given.
budgetPoints?
ts
readonly optional budgetPoints?: number;The scene budget, points over every character; 0 (the default) means none (the rules above alone). The page's one knob: a fixed budgetPoints, or budgetPoints as a start with targetFps, and it moves with the frames.
deriveBudget?
ts
readonly optional deriveBudget?: boolean;Derive the scene budget from the characters' own level sizes whenever one is added: every character at its lightest level plus one step up for the largest step among them, and 2 % room (four heroes at 21.4k / 63k give about 130k); budgetPoints is then the fallback until a character is known. The scene defaults set it.
dwellSeconds?
ts
readonly optional dwellSeconds?: number;Under the scene budget, how long (seconds) the plan must ask the same of a character before it changes: a gaze sweeping across the heroes asks a different thing every few hundred milliseconds and moves nothing; a gaze that rests moves the heavier level after this long. Default 1.5.
headroomSeconds?
ts
readonly optional headroomSeconds?: number;headroomShare?
ts
readonly optional headroomShare?: number;At or over this share of the target for headroomSeconds: step the nearest character up. Default 0.97 for 5 s.
holdDownSeconds?
ts
readonly optional holdDownSeconds?: number;After a step down for the budget, no step up of that character for this long, seconds. Default 12.
holderMargin?
ts
readonly optional holderMargin?: number;A holder keeps its heavier level unless a challenger's rank beats it by this share. Default 0.2.
log?
ts
readonly optional log?: (event) => void;Where every change goes (and every refusal, for the log).
Parameters
| Parameter | Type |
|---|---|
event | AutoDetailEvent |
Returns
void
maxBudgetPoints?
ts
readonly optional maxBudgetPoints?: number;The budget's ceiling when it adapts. Default 4 x the start.
minSecondsBetween?
ts
readonly optional minSecondsBetween?: number;The least time between two changes, seconds. Default 4.
now?
ts
readonly optional now?: () => number;The clock, seconds. Default performance.now() / 1000.
Returns
number
quality?
ts
readonly optional quality?: Quality;Engine rendering quality, scoped to this governor.
settleSeconds?
ts
readonly optional settleSeconds?: number;How long (seconds) after one of the governor's own swaps the frames are not counted as slow or roomy: a level's load stalls the frames, and counting the stall as "slow" made the budget undo its own lift. The frames are not counted while the swap is in flight either. Default 1.
sizeBand?
ts
readonly optional sizeBand?: number;The band either side of a size threshold, a share of it. Default 0.15.
sizeThresholdsPx?
ts
readonly optional sizeThresholdsPx?: Readonly<Record<string, number>>;A level is worth showing when the figure is at least this tall on screen, pixels. Default 256 at 350 px, 512 at 800 px.
slowSeconds?
ts
readonly optional slowSeconds?: number;slowShare?
ts
readonly optional slowShare?: number;Under this share of the target for slowSeconds: step the heaviest character down. Default 0.9 for 2 s.
targetFps?
ts
readonly optional targetFps?: number;Frames a second to hold (the headset's rate). Default 72.
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.
BoneMaskEdge
The soft edge's numbers.
Properties
fullAbove
ts
readonly fullAbove: number;A point whose share is at or over this keeps its whole opacity; between, a smooth ramp. Default 0.6.
keepBelow
ts
readonly keepBelow: number;A point whose share is under this is left out. Default 0.15.
BonePose
A body pose a game hands a character: read by reference every frame, so change the numbers in place.
Properties
hips?
ts
readonly optional hips?: Readonly<{
joint: string;
x: number;
y: number;
z: number;
}>;The hips' place on the joint named, in metres as the character is drawn (at its height), or none to leave them.
names
ts
readonly names: readonly string[];The joints, by the skeleton's own names (a package's bone names), '' for one the game has and the skeleton does not (a game's own root). Keep the same array: it is bound to the bones once, and a new array binds again.
rotations
ts
readonly rotations: readonly Readonly<{
w: number;
x: number;
y: number;
z: number;
}>[];Each joint's rotation in its parent's frame, one per name (x, y, z, w; three's own quaternions do).
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
setBodyPose()
ts
setBodyPose(pose): void;Pose the body: per-joint rotations for the rig, plus a root translation.
Parameters
| Parameter | Type |
|---|---|
pose | BodyPose | 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
| Parameter | Type |
|---|---|
weights | Float32Array |
Returns
void
setLookAt()
ts
setLookAt(target): void;Look at a world point, or null to return to rest.
Parameters
| Parameter | Type |
|---|---|
target | readonly [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
| Parameter | Type |
|---|---|
controls | Float32Array |
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
| Parameter | Type |
|---|---|
dt | number |
camera | PerspectiveCamera |
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.
CharacterLevel
What a loaded package is: its folder's name, its level name (null for a build), its points.
Properties
name
ts
readonly name: "_ogs512" | "_ogs256" | "_ogs128" | "_phone" | null;The level name the folder ends in, or null: a build, not a level.
package
ts
readonly package: string;The package folder's name as addressed (F22Elio_ogs256; a hash for a published character).
points
ts
readonly points: number;The package's points (character.json's splatCount).
CharacterLook
A character's look, as its package, a game's manifest or a host asks for it; every field optional.
Properties
blend?
ts
optional blend?: CharacterBlend;pointShape?
ts
optional pointShape?: CharacterPointShape;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
| Parameter | Type |
|---|---|
message | string |
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
| Parameter | Type |
|---|---|
name | string |
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
| Parameter | Type |
|---|---|
name | string |
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.
EyeShear
The studio's two eye volumes and reach fade; null at the setter switches them off.
Example
ts
character.setEyeShear({
left: { centre: [0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] },
right: { centre: [-0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] },
fade: [1, 1.35],
});Properties
fade?
ts
readonly optional fade?: readonly [number, number];Reach fade in eye radii, increasing. Studio default [1, 1.35].
left
ts
readonly left: EyeShearVolume;right
ts
readonly right: EyeShearVolume;EyeShearVolume
One calibrated eye volume in the package rest frame, as pcGazeFrame supplies it.
Example
ts
const eye: EyeShearVolume = { centre: [0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] };Properties
centre
ts
readonly centre: readonly [number, number, number];Rest centre in metres, including the package coordinate conversion.
radius
ts
readonly radius: number;Effective volume radius (studio rho * eye.radius), metres, positive.
shear
ts
readonly shear: readonly [number, number, number];Studio shearVector output; no easing, clamping or camera math in the engine.
FightSet
the fight set, read: the pack, the joints its takes move, and the pack's fight block as the pack carries it (JSON text)
Properties
joints
ts
readonly joints: readonly string[];json
ts
readonly json: string;name
ts
readonly name: string;pack
ts
readonly pack: FightPack;FightState
what the camera and the game read of a fighter
Properties
airborne
ts
readonly airborne: boolean;depth
ts
readonly depth: number;down
ts
readonly down: boolean;faceUp
ts
readonly faceUp: boolean;facing
ts
readonly facing: number;headY
ts
readonly headY: number;lift
ts
readonly lift: number;pose
ts
readonly pose: object;the pose this step (a recording's, a test's): every joint's turn in its parent, and the hips' place
hips
ts
readonly hips: object;hips.x
ts
readonly x: number;hips.y
ts
readonly y: number;hips.z
ts
readonly z: number;local
ts
readonly local: readonly object[];ragdolled
ts
readonly ragdolled: boolean;thrown or lying; lying at rest; getting up; thrown and still in the air; lying face up
rising
ts
readonly rising: boolean;scale
ts
readonly scale: number;the body's scale (its drawn height over the clips' own), the head's height, how high it is
x
ts
readonly x: number;where the driver has it: along, up, across; the way it faces
y
ts
readonly y: number;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
| Parameter | Type |
|---|---|
arkit | Float32Array |
out | Float32Array |
Returns
void
terms
ts
terms: number;Rows of the table in use: channels plus correctives.
HairData
Properties
bodyCount
ts
bodyCount: number;character
ts
character: string;check
ts
check: number[][];idx
ts
idx: Int32Array;The body-splat gaussians on the strands, their 4 slots and weights.
joints
ts
joints: Uint16Array;mask
ts
mask: Uint8Array<ArrayBufferLike> | null;Per body gaussian (when the file has it): 0 not hair, 1 hair on its own skinning, 2 on the strands.
proxies
ts
proxies: number[];strands
ts
strands: HairStrand[];weights
ts
weights: Float32Array;HairParams
Properties
bodyCollide
ts
bodyCollide: number;1: the strands keep off the body's skin; 0: they pass through it (garments still collide). Sara (Marnie, whose package body has shoulders far wider than her splat): "hard to gauge the hair simulation when the body collision is wrong"; the body is someone else's to fix. OFF by default (2026-10-23, Sara: the bodies are being refitted and are not good yet): off, the sim never searches the skin at all.
calmFrom
ts
calmFrom: number;... and the INERTIA SCALE BY SPEED (local-frame simulation, as games blend it): from calmFrom to calmTo m/s of its rest target's speed (smoothed over a quarter second) the share of its target's acceleration a point still feels eases from 1 to calmInertia; the rest moves it with its target. No pull towards the rest shape: the hair swings freely, only less (Sara, 2026-10-23, on a pull that rose with speed: Marnie's run looked "like it's trying to stay in base position the whole time instead of just limiting the movement"). The walk's targets never reach calmFrom (Jamila's: 0.38 m/s at most in the walk, 1.2 median and 2.6 at most in the run): the walk is exactly as before. calmInertia 1: off.
calmInertia
ts
calmInertia: number;calmTo
ts
calmTo: number;clothMargin
ts
clothMargin: number;cohesion
ts
cohesion: number;cohesionReach
ts
cohesionReach: number;cohesionShape
ts
cohesionShape: number;collide
ts
collide: number;Collision correction per substep (0..1): soft, so a point on the jiggling dress does not jitter every frame.
damping
ts
damping: number;Velocity damping, 1/s.
ftl
ts
ftl: number;Follow-the-leader velocity correction (0..1, Müller 2012: ~0.9; without it the chain gains energy and whips).
globalRange
ts
globalRange: number;globalRoot
ts
globalRoot: number;Global shape pull per substep near the scalp and far from it (eased by the point's s, its distance from the scalp, over globalRange).
globalTip
ts
globalTip: number;gravity
ts
gravity: number;m/s^2
inertiaMax
ts
inertiaMax: number;CALM IN THE RUN (Sara, 2026-10-23: "the hair movement for running was crazy too big ... ok during walk"): the most acceleration of its rest target a strand point feels as inertia, m/s^2. Each frame the target's acceleration past it is handed to the point too (it moves with its target for that part, as hair simulated partly in the body's frame): a walk's head and chest never reach it (Jamila's strand targets: 9.5 m/s^2 at most in the walk, 12 median and 53 at most in the run), so the walk is exactly as before. 0: no limit.
local
ts
local: number;Local shape pull per substep.
lockCohesion
ts
lockCohesion: number;The share of the cohesion a FREE LOCK of its own keeps (a curl hooking below the hairline, a mustache's curve). Not short locks: Dorian's fringe, on its own, tore 28 mm from the hair beside it (16 with full cohesion).
skinMargin
ts
skinMargin: number;Clearance kept off the skin / the cloth (at most the rest one: the hair layer under a strand keeps its thickness), m.
stiffBlend
ts
stiffBlend: number;stiffLayer
ts
stiffLayer: number;stiffLocal
ts
stiffLocal: number;stiffPull
ts
stiffPull: number;STIFF NEAR THE ROOT (Sara: "stiff near the roots so we don't lose hair volume ... some movement shortly after"): a point's stiffness w is 1 within stiffLayer of the scalp skin (its rest distance, the strand file's s), easing to 0 over stiffBlend further (by DISTANCE, alike on long and short strands); it is pulled to its rest shape at least w x stiffPull a substep and bent back at least w x stiffLocal. All live (the page's Hair simulation sliders).
substep
ts
substep: number;Longest substep, s.
tailInertia
ts
tailInertia: number;The share of the body's acceleration a TIED TAIL feels at any speed (Sara, 2026-10-23: Marnie's pigtails "need 25 percent less movement ... they bounce too much" in the walk): the rest moves it with its tie, as the inertia scale above. 1: as loose hair.
HairRuntime
The runtime's side of the hair: fed each frame, read by the host as AosrigHairState.
Extends
Properties
frames
ts
readonly frames: number;The frames simulated so far.
Inherited from
ms
ts
readonly ms: number;Her sim's wall time last frame, ms (0 before the first frame).
Inherited from
rows
ts
readonly rows: number;The rows riding the strands.
Inherited from
slots
ts
readonly slots: number;The bone slots after the rig's joints (her proxies and every strand point).
Inherited from
strands
ts
readonly strands: number;The guide strands.
Inherited from
Methods
dispose()
ts
dispose(): void;Let the sim go.
Returns
void
reset()
ts
reset(): void;After a teleport or a pose jump: the strands snap to their rest targets on the next frame.
Returns
void
Inherited from
setParams()
ts
setParams(params): void;Live tuning (HAIR_DEFAULTS are Sara's chosen settings); bodyCollide stays 0 until the clean body lands.
Parameters
| Parameter | Type | Description |
|---|---|---|
params | Partial<HairParams> | The settings to change. |
Returns
void
Inherited from
update()
ts
update(dt, matrices): void;One frame: her sim advanced by dt against the rig's fresh skin matrices, its slots written into the bone table after the rig's joints. The first call records the rest against these matrices (the rig at its bind pose, as her page does at load).
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | The frame's time step, s. |
matrices | Float32Array | The bone table: the rig's jointBase skin matrices written, the slots to write. |
Returns
void
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).
LevelPlanCharacter
What planLevels reads of a character: the part of AutoDetailCharacter the plan needs.
Properties
id
ts
readonly id: string;levels
ts
readonly levels: readonly string[];Its levels, lightest first.
Methods
current()?
ts
optional current(): string;The level shown now (a holder keeps its heavier level unless beaten by the margin); optional.
Returns
string
points()
ts
points(level): number;Parameters
| Parameter | Type |
|---|---|
level | string |
Returns
number
rank()?
ts
optional rank(): number;The game's own order, 0 first; optional.
Returns
number
screenHeightPx()?
ts
optional screenHeightPx(): number;Its size on screen and its angle off the view, for the order within a rank; optional (then all equal).
Returns
number
viewAngleDeg()?
ts
optional viewAngleDeg(): number;Returns
number
LevelVerdict
What the loader does with a package on this device: a warning or a refusal, each with its line.
Properties
line
ts
readonly line: string;The reason in words, the same on every device.
refuse
ts
readonly refuse: boolean;True on a phone or a headset: the package is not loaded. A computer loads it and says the line.
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>;Parameters
| Parameter | Type |
|---|---|
input | URL | RequestInfo |
init? | RequestInit |
Returns
Promise<Response>
Call Signature
ts
(input, init?): Promise<Response>;Parameters
| Parameter | Type |
|---|---|
input | string | URL | Request |
init? | RequestInit |
Returns
Promise<Response>
Inherited from
ts
BundleFetchOptions.fetchpreferFp16?
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.resolversignal?
ts
optional signal?: AbortSignal;Inherited from
ts
BundleFetchOptions.signalMemoryReport
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.
MouthGlbPart
One part of mouth.glb, expanded to its triangles' corners.
Properties
name
ts
name: string;The node's name: mouth_teeth, mouth_gums, mouth_tongue, mouth_bag.
positions
ts
positions: Float32Array;x y z per corner, three corners per triangle, the package's frame.
uvs
ts
uvs: Float32Array;u v per corner (glTF's: v runs down the picture).
MouthHidden
The listed splats, parsed.
Properties
band
ts
band: number;capLower?
ts
optional capLower?: Uint32Array<ArrayBufferLike>;capUpper?
ts
optional capUpper?: Uint32Array<ArrayBufferLike>;Cap pairs from the actual lip rim; fade pairs remain upper/lower.
count
ts
count: number;fill?
ts
optional fill?: Float32Array<ArrayBufferLike>;Plug sheet metadata, populated at load; two floats per listed row (column, fraction).
fillCount?
ts
optional fillCount?: Uint32Array<ArrayBufferLike>;Number of plugs sharing a fill column; packed beside its column and fraction.
forward
ts
forward: [number, number, number];kind
ts
kind: Uint8Array;lower
ts
lower: Uint32Array;motionVertices?
ts
optional motionVertices?: Uint32Array<ArrayBufferLike>;Studio-style same-side inverse-distance motion for mouth-neighbourhood rows; weights include the 8–20 mm fade.
motionWeights?
ts
optional motionWeights?: Float32Array<ArrayBufferLike>;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;wide?
ts
optional wide?: Uint8Array<ArrayBufferLike>;Load-derived wide-band membership, independent of the file's fade kind.
MouthMeshInfo
mouth.json, as far as the engine reads it.
Properties
glb
ts
glb: string;The meshes' file, mouth.glb.
setBackM?
ts
optional setBackM?: number;How far the studio sets the mouth back along the face's forward so it sits behind the splat's lips (metres), when the package says; the engine measures it otherwise.
texture
ts
texture: string;The picture, mouth_texture.png.
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.
PoseAsk
What a character tells the scheduler each frame.
Properties
distanceM
ts
readonly distanceM: number;Its distance from the eye, metres (0 when unknown: treated as near).
hz
ts
readonly hz: number;Its own rate, poses a second; 0 = every frame the budget allows.
id
ts
readonly id: string;The character's key.
nowS
ts
readonly nowS: number;The frame's time, seconds.
points
ts
readonly points: number;Points it would pose.
PoseScheduler
The scheduler.
Properties
budgetPoints
ts
readonly budgetPoints: number;Methods
forget()
ts
forget(id): void;A character left the scene.
Parameters
| Parameter | Type |
|---|---|
id | string |
Returns
void
grant()
ts
grant(ask): boolean;Ask to pose this frame; true grants it (the caller then poses).
Parameters
| Parameter | Type |
|---|---|
ask | PoseAsk |
Returns
boolean
setBudgetPoints()
ts
setBudgetPoints(points): void;Set the scene budget, points re-posed a frame; 0 = no limit (every character every frame, its own rate aside).
Parameters
| Parameter | Type |
|---|---|
points | number |
Returns
void
state()
ts
state(): object;The scheduler's own view, for the log.
Returns
object
budgetPoints
ts
budgetPoints: number;characters
ts
characters: object[];frames
ts
frames: number;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
| Parameter | Type |
|---|---|
encoder | GPUCommandEncoder |
Returns
void
init()
ts
init(options): Promise<void>;Build the wasm/GPU resources. Idempotent; throws loudly on a bad asset.
Parameters
| Parameter | Type |
|---|---|
options | RigBackendInit |
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
| Parameter | Type |
|---|---|
controls | Float32Array |
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
| Parameter | Type |
|---|---|
controls | Float32Array |
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
| Parameter | Type |
|---|---|
overrides | readonly 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
| Parameter | Type |
|---|---|
name | string |
Returns
Promise<Uint8Array<ArrayBufferLike>>
getBytes
ts
getBytes: (name) => Uint8Array<ArrayBufferLike> | undefined;Bundle-relative file bytes, already resident.
Parameters
| Parameter | Type |
|---|---|
name | string |
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
| Parameter | Type | Description |
|---|---|---|
encoder | GPUCommandEncoder | The 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
| Parameter | Type | Description |
|---|---|---|
controls | Float32Array | The 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.
SkinRules
The rules' numbers.
Properties
bandCapM
ts
readonly bandCapM: number;The lips' band points' sizes are capped toward this as the lips part.
hideGapM
ts
readonly hideGapM: readonly [number, number];The lips' gap (metres) over which a hidden point fades: 1 at the first, 0 from the second.
maxBones
ts
readonly maxBones: number;Bone matrices a character may have.
minTriangleArea
ts
readonly minTriangleArea: number;A face triangle smaller than this (twice its area) binds nothing.
plugPush
ts
readonly plugPush: object;A plug's push back along the head's forward: a step of stepM over rampM of gap, then slope of the gap up to capM.
capM
ts
readonly capM: number;rampM
ts
readonly rampM: number;slope
ts
readonly slope: number;stepM
ts
readonly stepM: number;sortBins
ts
readonly sortBins: number;Depth bins of the draw order (the fork's counting sort).
sortGate
ts
readonly sortGate: object;The pose gate for a re-sort (the studio's rule, on matrices): a rotation entry or a translation moved this far.
positionM
ts
readonly positionM: number;rotation
ts
readonly rotation: number;sortTurnCos
ts
readonly sortTurnCos: number;The sort's own re-sort threshold: the cosine of the view direction's turn (the fork's 0.9995, about 1.81 degrees).
stretchMax
ts
readonly stretchMax: number;stretchMin
ts
readonly stretchMin: number;A face point's stretch with its triangle is kept within this of its rest size, each axis.
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.
contacts
ts
readonly contacts: SomaClipContacts | null;When each foot is on the ground, as the file states it (contacts: { left, right }, one 0 or 1 per frame: a reading of the performance the clip was made from); null when the file does not say. somaAnimationClip then reads them off the feet's height.
floor
ts
readonly floor: boolean;Whether the clip's planted feet are to stand on the character's own floor when the host asks for that (somaAnimationClip's floor): said by the file (floor: true), else true for a clip that travels (speed > 0) or names a gait. A jump's air time or a swim must not be lowered to the floor.
fps
ts
readonly fps: number;frames
ts
readonly frames: number;gait
ts
readonly gait: Gait | null;The clip's gait, when it is one of the forward gaits the walk blend orders by speed (walk, jog, run, sprint); null for every other clip (a turn, a side step, a crouch: never in the blend, whatever locomotion says).
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).
SomaClipContacts
A clip's feet on the ground, frame by frame: 1 while the foot is down.
Properties
left
ts
readonly left: Uint8Array;right
ts
readonly right: Uint8Array;SomaClipFeet
What a clip's feet do on a skeleton.
Properties
low
ts
readonly low: number;The lowest the feet reach over the clip (the middle of the ankle and the ball, rig frame, with the Hips where the clip's root puts them), metres.
planted
ts
readonly planted: SomaClipContacts;The feet's contacts as their height says: per frame, 1 while the foot (left, then right) is within 3 cm of the lowest the feet reach. What stands in for a clip's own contacts when its file states none.
travel
ts
readonly travel: number;The planted feet's speed over the ground, metres a second: how fast the legs really carry this skeleton (the median over the planted frames of both feet, the root's own travel taken out); 0 when no foot stays planted.
SomaClipOptions
How a clip is placed on the character.
Properties
floor?
ts
optional floor?: boolean;Stand the clip's planted feet on the character's own floor: the Hips' track is raised (or lowered) by one amount for the whole clip so the lowest the feet reach is where this skeleton's feet stand at rest (somaRestFootY), instead of where the neutral body's root put them. Taken only for a clip whose feet touch the ground (SomaClip.floor).
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
| Parameter | Type | Description |
|---|---|---|
out | Float32Array | 16 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
| Parameter | Type |
|---|---|
count | number |
Returns
free()
ts
free(range): void;Return a range to the free list.
Parameters
| Parameter | Type |
|---|---|
range | SlotRange |
Returns
void
markGaussiansChanged()
ts
markGaussiansChanged(): void;Force a re-sort on the next render, even when the camera has not moved.
Returns
void
restPoints()?
ts
optional restPoints(): GPUBuffer;The rest points (array<vec4<f32>>, one per slot: xyz = the point's rest centre, w = its group as a whole number), made on first call, for a game's per-point hook. Optional: a sink without it gets no rest points, and a hook then reads each point's current centre and group 0.
Returns
GPUBuffer
setBoundingSphere()
ts
setBoundingSphere(center, radius): void;Owner-supplied bounds; the splat object never recomputes them from the GPU.
Parameters
| Parameter | Type |
|---|---|
center | [number, number, number] |
radius | number |
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
| Parameter | Type |
|---|---|
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];WebglAosrigSplat
A WebGL character, as a game draws it: the runtime, its stats and the sink it draws through.
Extends
Properties
corrective
ts
readonly corrective: AosrigCorrectiveState | null;The pose corrections, when the package carries corrective/; else null.
Inherited from
hair
ts
readonly hair: AosrigHairState | null;Sara's strand hair, when the package carries hair/ and it fits this splat (hair/hairRuntime.ts): its guide strands are bone slots after the rig's joints, ticked each frame; else null.
Inherited from
hiddenPoints
ts
readonly hiddenPoints: AosrigMouthHiddenState | null;The closed mouth's inside points, when the package carries mouth_hidden.bin; else null.
Inherited from
AosrigSplatRuntime.hiddenPoints
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
| Parameter | Type |
|---|---|
arkit | Float32Array |
out | Float32Array |
Returns
void
terms
ts
terms: number;Inherited from
mouth
ts
readonly mouth: AosrigMouth | null;The inside of the mouth (teeth, gums, tongue, and the teeth's own points or the package's frozen meshes), when the package carries them, the head pack its mouth and the host asked for it; else null.
Inherited from
stats
ts
readonly stats: WebglSkinStats;Methods
compile()
ts
compile(): Promise<void>;The GPU programs built ahead of the first frame (the deformation's passes, the mouth's); already awaited by createAosrigSplat, so a host that awaits it again (a swap, before it marks the copy compiled) waits for nothing more.
Returns
Promise<void>
Resolves once everything is built.
Inherited from
dispose()
ts
dispose(): void;Returns
void
Inherited from
referenceCenters()
ts
referenceCenters(): Promise<Float32Array<ArrayBufferLike>>;Every point's posed centre through the CPU reference (skinPoint), for the last pose rendered, xyz a point: the measurement's side of the parity check against the compute pass.
Returns
Promise<Float32Array<ArrayBufferLike>>
The centres, from the worker.
render()
ts
render(
expression,
gaze,
camera,
dt?
): void;Pose face, body and splats after AnimationMixer and procedural aim have run.
Parameters
| Parameter | Type | Description |
|---|---|---|
expression | Float32Array | The face's controls. |
gaze | ArrayLike<number> | The eyes' turn. |
camera | Camera | The camera the sink is drawn for. |
dt? | number | The frame's time step, s, for the strand hair (the clock's own frame when the host gives none). |
Returns
void
Inherited from
setEyeShear()
ts
setEyeShear(frame): void;Copy calibrated rest-space eye volumes and shear vectors, or clear with null.
Parameters
| Parameter | Type |
|---|---|
frame | EyeShear | null |
Returns
void
Inherited from
AosrigSplatRuntime.setEyeShear
setJointAffines()
ts
setJointAffines(names, affines): void;Copy per-joint row-major [A-I | offset] surface deltas, or clear with null.
Parameters
| Parameter | Type |
|---|---|
names | readonly string[] |
affines | Float32Array<ArrayBufferLike> | null |
Returns
void
Inherited from
AosrigSplatRuntime.setJointAffines
setPointShare()?
ts
optional setPointShare(share): void;A test lever on the fallback skin: draw this share of the points (0 to 1), an evenly spread subset. Absent on the WebGPU runtime.
Parameters
| Parameter | Type |
|---|---|
share | number |
Returns
void
Inherited from
AosrigSplatRuntime.setPointShare
setSkinOptions()
ts
setSkinOptions(options): WebglSkinOptionsInForce;Change how the skin spends the chip's time, live: the material is rebuilt for the vertex stage or the pass.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | WebglSkinOptions | The fields to change; the rest stay. |
Returns
The options now in force.
Overrides
AosrigSplatRuntime.setSkinOptions
setSortMode()?
ts
optional setSortMode(mode): void;A test lever on the fallback skin: when the draw order is remade. Absent on the WebGPU runtime.
Parameters
| Parameter | Type |
|---|---|
mode | "frozen" | "pose" | "turn" | "slow" |
Returns
void
Inherited from
AosrigSplatRuntime.setSortMode
sortIfViewMoved()
ts
sortIfViewMoved(camera): void;A request for a new order when the view moved past the sort's threshold.
Parameters
| Parameter | Type |
|---|---|
camera | Camera |
Returns
void
swapLevel()
ts
swapLevel(bundle, pause?): Promise<void>;Take another level of the same character (its package: another point count, the same rig, the same head) into this runtime: the records and harmonics parsed and packed in slices (pause between them), the worker given the new set, and, when its first order is in, the textures refilled and the counts switched in one frame. The material and its programs stay (no node build, no compile: the stall of a fresh runtime); the old points keep drawing with their last order until then. The sink's capacity bounds the levels this runtime can take: make it for the heaviest level a page will show. stats.lastSwap says what the swap cost.
Parameters
| Parameter | Type | Description |
|---|---|---|
bundle | AosrigSplatBundle | The level's package. |
pause? | () => Promise<void> | Awaited between slices of the main-thread work. |
Returns
Promise<void>
Resolves when the new level is drawn; rejects when the level does not fit the sink or the head differs.
Overrides
warmSkinVariants()
ts
warmSkinVariants(modes, nextFrame): Promise<void>;Warm the skin variants a page will switch between (skin: 'vertex' | 'pass' | 'stream') once, behind its loading screen, so the first switch of a Speed-up lever does not stall: each variant is taken in turn and drawn by the page's own frames (the headset's own cameras: the only compile that counts), two frames each, and kept (one material object a variant on the fork, one pass a variant here); a later setSkinOptions to a warmed variant reassigns what was built and compiled. The options in force are restored at the end.
Parameters
| Parameter | Type | Description |
|---|---|---|
modes | readonly ("pass" | "vertex" | "stream")[] | The variants to warm (the current one is skipped). |
nextFrame | () => Promise<void> | Resolves after the page's next drawn frame (e.g. an engine:frame listener). |
Returns
Promise<void>
Overrides
AosrigSplatRuntime.warmSkinVariants
WebglSkinOptions
How the fallback skin spends a headset chip's time. Every field is a switch the xr levers and the device speed test can flip at run time (WebglAosrigSplat.setSkinOptions); the defaults draw today's picture today's way.
Properties
boneMask?
ts
readonly optional boneMask?: BoneMaskChoice;boneMask: draw only the points bound mainly to a set of bones, decided at load as a compacted set (the rest never uploaded, sorted or fetched): 'all' (the default), 'arms and hands' (a first-person hero's arms, the shoulder a soft edge), 'upper body' (the waist a soft edge), or a list of the rig's joint names. Taken when the character is made (setWebglSkinDefaults or the skin option before the load); a later setSkinOptions with another mask is noted in stats.mask.wanted and takes the next load.
discBelowPx?
ts
readonly optional discBelowPx?: number;discBelowPx: the height on screen under which 'auto' draws the disc. Default 220.
drawScale?
ts
readonly optional drawScale?: number;drawScale: the character drawn into a buffer this share of each eye's size and scaled up with the linear filter (attachLowResDraw): 1 (the default) draws as before; 0.7 draws half the fragments, 0.5 a quarter. The page stays sharp; the character's depth test against the scene is lost (drawn alone). Not with both eyes in one pass (the helper steps aside while multiview is in force).
drawShape?
ts
readonly optional drawShape?: "auto" | "full" | "disc";drawShape: the vertex stage that draws the points: 'full' (today's: the covariance projected, the eigen split), 'disc' (the cheap round point for a far or small character, fork edit 35), or 'auto' (the disc once the character stands under discBelowPx on screen, the full stage back above it, a 15 % band between). Default 'full'.
extentSigma?
ts
readonly optional extentSigma?: number;The fill switches (the second cost on a headset chip: 16 M blended fragments a frame at 512). Each is a uniform or a helper, no rebuild; each changes the picture, so the defaults keep it.
extentSigma: how far each point's square reaches, in sigma of the engine's point (2 = the whole shape, the default; 1.7 draws 72 % of the fragments, 1.5 56 %). The fall-off stays the shape's, its tail pulled to zero at the new edge.
lazyFetch?
ts
readonly optional lazyFetch?: boolean;The binding texels fetched only where needed (see WebglSkinTuning). Default true; no change to the picture.
leanOrderUpload?
ts
readonly optional leanOrderUpload?: boolean;leanOrderUpload: a new draw order goes up as its texture alone (the fallback's vertex stage reads the texture, never the buffer): half the bytes a re-sort (0.58 MB instead of 1.2 at 146k points). Default false; the same picture.
maskMode?
ts
readonly optional maskMode?: "compact" | "live";maskMode: how the bone mask is applied. 'compact' (default, today's way): the points left out are dropped at load into a compact set, the lightest draw, and another mask needs another load. 'live': the runtime keeps every point and the mask is a weight a point (a texture) plus the order's keep array: boneMask then switches in place (the arms and the whole body are one runtime; a swap costs one texture update and one order, no load), at the cost of the pass posing every point and a copy of the records kept for the weights.
maxPointPx?
ts
readonly optional maxPointPx?: number;maxPointPx: cap a point's square on screen at this many pixels from its centre along either axis (the big blurry points near the eye cost the most fill); 0 = no cap. Default 0.
minContribution?
ts
readonly optional minContribution?: number;minContribution: a point whose whole contribution on screen (opacity x 2 pi x the root of its footprint's determinant, pixels) is under this is not drawn: the faintest and the sub-pixel ones at a distance go first. 0 (the default) draws every point; SuperSplat's viewer uses 1.
nearCutM?
ts
readonly optional nearCutM?: number;nearCutM: a point nearer the eye than this many metres is not drawn (nothing balloons in the face when the hero is seen through his own eyes). 0 draws every point; the arms preset takes 0.2 unless told otherwise.
poseHz?
ts
readonly optional poseHz?: number;poseHz: this character's own posing rate, poses a second; 0 (the default) poses every frame the scene budget allows (setPoseBudgetPoints). A frame without a pose draws the last posed buffers: the bones, the head and the pass stand still for it, the picture does not.
shBands?
ts
readonly optional shBands?: 0 | 2 | 3 | 1;Bands of the view-dependent colour evaluated, 0 to 3. Default 3 (the package's look); fewer flattens the sheen.
skin?
ts
readonly optional skin?: "pass" | "vertex" | "stream";Where the skin runs. 'vertex' (the default): in the draw's vertex stage, once per corner of every point's square and once per eye (eight times a point a frame in a headset, more where the chip bins its tiles first). 'pass': once a point a frame, in a compute pass over the sink's four buffers (three's WebGL backend runs it as transform feedback and copies the result into the textures the draw reads), so the draw's vertex stage reads five texels (the order and the posed point) instead of about twenty-eight and does no skinning, no face binding and no harmonics. The same rules, the same picture; the frame order is one frame later on the first frame after a switch (the copy lands a frame after the pass). 'stream': the same pass, but each slot of the pass takes the point the draw order puts there, so its output is already in draw order and goes to the draw as four per-instance attributes (no texture copy, no texture fetch in the draw's vertex stage: the chip streams the posed points as it streams any mesh). The order is read by the pass, so a new order shows a frame after it arrives.
sortMaxPerSecond?
ts
readonly optional sortMaxPerSecond?: number;sortMaxPerSecond: at most this many re-sorts (and order uploads) a second; 0 (the default) is as many as the gates ask. A walking character's pose gate can ask every frame; a mobile chip stalls on an upload onto a texture in use.
streamLean?
ts
readonly optional streamLean?: boolean;streamLean (the 'stream' skin only): the draw reads no texture at all: the fork's four stand-in reads and its order read become constants, and the pass reads the order from the skin's own texture, so the fork's order uploads (buffer and texture) stop too. Default true; false is the earlier stream for an A/B.
swapBands?
ts
readonly optional swapBands?: boolean;swapBands: make the records and harmonics textures with room for two levels (two regions, each the sink's capacity), so swapLevel copies the new level into the other region in bands over frames and flips a base uniform in its apply frame: no whole-texture upload in any frame. Costs the second region's memory (about 270 B a point of capacity on the GPU). Default false: a swap refills the textures whole (one frame's upload).
WebglSkinStats
What the WebGL runtime measures, for the lab pages.
Properties
basisEntries
ts
basisEntries: number;Sparse basis entries the worker digested.
build
ts
build: object;firstDrawMs
ts
firstDrawMs: number;The first draw of the sink, from the fork's before-render hook to its after-render hook: the material's node build and the program's compile land here when the page did not warm them (renderer.compileAsync), and the textures' upload always does. On the desk about 450 to 600 ms a material variant.
firstFrameMs
ts
firstFrameMs: number;firstPassMs
ts
firstPassMs: number;longestStepMs
ts
longestStepMs: number;maskMs
ts
maskMs: number;packMs
ts
packMs: number;parseMs
ts
parseMs: number;texturesMs
ts
texturesMs: number;totalMs
ts
totalMs: number;workerReadyMs
ts
workerReadyMs: number;counts
ts
counts: DrawCounts;What this character costs the chip a frame, counted from the sizes: vertex-shader runs and texel fetches per eye, transform-feedback points per pose, blended fragments per eye (the worker's area sample times the disc's share). The eye's size in pixels is eyePx (set by the page; else the drawing buffer, halved across for two eyes). Summed over the characters, these are the numbers a cost model fits.
drawShape
ts
drawShape: "full" | "disc";The vertex stage drawing the points now ('full' or 'disc'; 'auto' resolves here).
eyePx
ts
eyePx: [number, number];The eye's size in pixels the fragment estimate uses, [width, height]; [0, 0] = the drawing buffer's.
framesPerSecond
ts
framesPerSecond: number;heads
ts
heads: number;Head blends asked of the worker.
inView
ts
inView: boolean;False while the character is wholly behind the eye or outside the view (its sphere, padded for the other eye, against the eyes' frustum): nothing is posed, sorted, passed, uploaded or drawn for it then, and lastSortBins and lastSortRange are the last order's, not this frame's. The first frame back in view sorts at once.
kept
ts
kept: number;Points drawn after the share lever (setPointShare): the kept count of the last order.
lastGate
ts
lastGate: object;What asked for the last re-sort: the largest turn entry and place move (metres) since the order before.
camZ?
ts
optional camZ?: number[];invZ?
ts
optional invZ?: number[];The view test's inputs (the headset log): the camera's inverse z row, its world z column, the splat's x axis, the last order's direction.
joint
ts
joint: number;lastDir?
ts
optional lastDir?: number[] | null;placeM
ts
placeM: number;reachM
ts
reachM: number;rotation
ts
rotation: number;splatX?
ts
optional splatX?: number[];turnReachM
ts
turnReachM: number;view
ts
view: boolean;lastHeadMs
ts
lastHeadMs: number;lastMainMs
ts
lastMainMs: number;The main thread's own time in render, milliseconds, last frame.
lastMaskSwapMs
ts
lastMaskSwapMs: number;The last live mask change's main-thread time, milliseconds (the weights, the texture's data, the message).
lastPassMs
ts
lastPassMs: number;lastPoseMs
ts
lastPoseMs: number;lastSortBins
ts
lastSortBins: number;The last order's depth bins used (one = every depth outside the range: the index order, a see-through jumble).
lastSortMs
ts
lastSortMs: number;The last sort's time in the worker (posing every centre and binning), milliseconds.
lastSortRange
ts
lastSortRange: object;The last order's depth range, measured over the posed points in the view the order was made for.
far
ts
far: number;near
ts
near: number;lastSwap
ts
lastSwap:
| {
applyMs: number;
bandMs: number;
bands: number;
longestStepMs: number;
maskMs: number;
packMs: number;
parseMs: number;
points: number;
texturesMs: number;
totalMs: number;
workerReadyMs: number;
}
| null;The last swapLevel: its slices' milliseconds (parse, pack, mask, the textures' arrays, the worker's digest off the main thread, the apply frame's own work), the total from the call to the new level drawn, the longest single main-thread slice, and the points taken. Null before any.
Union Members
Type Literal
ts
{
applyMs: number;
bandMs: number;
bands: number;
longestStepMs: number;
maskMs: number;
packMs: number;
parseMs: number;
points: number;
texturesMs: number;
totalMs: number;
workerReadyMs: number;
}applyMs
ts
applyMs: number;bandMs
ts
bandMs: number;bands
ts
bands: number;The banded swap: bands copied and the longest one's milliseconds (0 without bands).
longestStepMs
ts
longestStepMs: number;maskMs
ts
maskMs: number;packMs
ts
packMs: number;parseMs
ts
parseMs: number;points
ts
points: number;texturesMs
ts
texturesMs: number;totalMs
ts
totalMs: number;workerReadyMs
ts
workerReadyMs: number;null
lastWarm
ts
lastWarm: object[] | null;The last warmSkinVariants: per variant the compile's milliseconds and whether its pass was built and run.
lowRes
ts
lowRes:
| {
height: number;
width: number;
}
| null;The small buffer the character is drawn into (drawScale under 1), pixels; null while drawn at full size.
mask
ts
mask: object;The bone mask: what was loaded (applied), what is wanted now, and the points kept of the package's.
applied
ts
applied: string;kept
ts
kept: number;of
ts
of: number;wanted
ts
wanted: string;passes
ts
passes: number;Skin passes run (the 'pass' skin), and how long the main thread spent asking for the last one.
passGpuMs
ts
passGpuMs: number | null;The pass's time on the card, milliseconds, where the renderer keeps a timer (trackTimestamp); else null.
posesPerSecond
ts
posesPerSecond: number;Poses granted in the last second (the frames this character's pose moved), and frames drawn in it.
ready
ts
ready: boolean;Whether the worker has digested the head and answered.
screenHeightPx
ts
screenHeightPx: number;The character's height on screen, pixels, estimated from its sphere each frame (what 'auto' decides by).
share
ts
share: number;The share of points asked for, 0 to 1.
skinOptions
ts
skinOptions: WebglSkinOptionsInForce;The skin options in force (setSkinOptions).
sortMode
ts
sortMode: "frozen" | "pose" | "turn" | "slow";When the order is remade (setSortMode): 'pose' when the pose moves past the gate or the view turns (the rule); 'turn' only when the view turns; 'slow' when the view turns or every two seconds.
sorts
ts
sorts: number;Re-sorts asked of the worker, and how many were skipped because one was running.
sortsCoalesced
ts
sortsCoalesced: number;sortsPerSecond
ts
sortsPerSecond: number;Re-sorts asked of the worker in the last second, and the bytes uploaded for them and the head in that second.
uploadBytesPerSecond
ts
uploadBytesPerSecond: number;WebglSkinTuning
How the source spends the chip's time; the picture is the same unless shBands drops a band.
Properties
lazyFetch?
ts
readonly optional lazyFetch?: boolean;Fetch the record's binding texels (the rest frame's inverse, the origin, the hidden lanes: texels 7 to 10) only in the branches that read them, so a body point (no face triangle, not hidden) fetches 7 record texels instead of 11. Default true; the same values either way.
shBands?
ts
readonly optional shBands?: 0 | 2 | 3 | 1;How many bands of the view-dependent colour to evaluate: 3 (all 45 harmonics, 12 texels a point), 2 (24, 8 texels), 1 (9, 5 texels) or 0 (the stored colour alone, no harmonics texel). Default 3, the package's own look.
Type Aliases
AosrigDeform
ts
type AosrigDeform = "tsl" | "wgsl" | "webgl-skin";How an exported character deforms:
'tsl'(the default): the head pass and the splat deformation as TSL compute nodes (tslDeform.ts), on WebGPU and, as transform feedback, on the WebGL2 fallback.'wgsl':bound_splat.wgsland the mouth's WGSL passes as compute pipelines on the renderer's own device (wgslDeform.ts); WebGPU only. The path measured on Tala Me This through 1 Oct 2026.'webgl-skin': the vertex-stage skin for a phone without WebGPU (webglSkin.ts): the records in textures, the draw order from a worker; takes asplatinstead of asink.
AosrigHostSink
ts
type AosrigHostSink = SplatSink & object;A sink the host made, which the host also frees.
Type Declaration
dispose()
ts
dispose(): void;Returns
void
AosrigMouthMode
ts
type AosrigMouthMode = "off" | "plain" | "textured";What a mouth built from the package's frozen meshes draws: nothing, the meshes in plain colours, or the picture on the teeth, gums and tongue (the bag always plain).
AosrigSplatOptions
ts
type AosrigSplatOptions = AosrigSplatCommonOptions &
| {
acquireBuffer?: (attribute) => GPUBuffer;
deform?: "tsl" | "wgsl";
device?: GPUDevice;
frame?: Object3D;
mouth?: AosrigMouthOptions;
mouthMotion?: "frame" | "studio";
releaseBuffer?: (attribute) => void;
releaseCopy?: (attribute) => void;
sink: SplatSink;
webgl?: boolean;
}
| {
deform: "webgl-skin";
splat: WebglSkinTarget;
};The options of createAosrigSplat: the compute paths over a sink, or the vertex-stage skin over a splat.
ArkitGnmTable
ts
type ArkitGnmTable = Readonly<Record<string, readonly ArkitGnmTerm[]>>;The table: ARKit-52 channel name -> the coefficients it drives.
BoneMaskChoice
ts
type BoneMaskChoice = "all" | "arms and hands" | "upper body" | readonly string[];The presets, and a list of joint names (full weight) for a page's own choice.
CharacterBlend
ts
type CharacterBlend = "stored" | "linear";Where a character's colours add up: in the file's own space, as the studio adds them ('stored'), or in linear light.
CharacterPointShape
ts
type CharacterPointShape = "engine" | "studio" | "full-opacity" | "size-aware";How a character's points are drawn: three's own ('engine': 2 sigma, the small-point fade of the dilation's opacity compensation), the studio page's ('studio': a small point keeps its whole opacity, 2.83 sigma with the tail pulled to zero, points under 2 pixels across not drawn), the small-point rule alone ('full-opacity'), or 'size-aware' (the engine's soft point while a point is small on screen, the studio's once it is large, blended across a band of sizes). The fork's 'trained' (the training renderer's 3-sigma box) is the align lab's measuring switch only: Isaac rejected it for characters on 30 Sep 2026, so no package, manifest or host may ask for it.
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.
FightRequest
ts
type FightRequest = Omit<FightFrame, "x" | "y" | "depth" | "turn" | "target"> & object;what the game says of a fighter each step: where it stands (world) and the yaw its object is turned by, the other fighter it fights (an entity; 0 none), and the rest
Type Declaration
target
ts
readonly target: number;x
ts
readonly x: number;y
ts
readonly y: number;yaw
ts
readonly yaw: number;z
ts
readonly z: number;Gait
ts
type Gait = "walk" | "jog" | "run" | "sprint";The forward gaits the walk blend reads, slowest first.
LevelName
ts
type LevelName = typeof LEVEL_NAMES[number];One of the ladder's level names.
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.
WebglFillPreset
ts
type WebglFillPreset = keyof typeof FILL_PRESETS;A fill preset's name.
WebglSkinOptionsInForce
ts
type WebglSkinOptionsInForce = Readonly<Required<WebglSkinOptions>>;The options as they stand after the defaults.
WebglSkinPreset
ts
type WebglSkinPreset = keyof typeof SKIN_PRESETS;A preset's name.
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).
BONE_MASK_EDGE
ts
const BONE_MASK_EDGE: BoneMaskEdge;BONE_MASK_EDGE_HARD
ts
const BONE_MASK_EDGE_HARD: BoneMaskEdge;The arms preset's edge: hard, no ramp. From the hero's own head the shoulder is 15 to 25 cm from the eyes, where a half-transparent band reads as smoke and costs fill for nothing (3 Oct 2026, the first-person shots): a point is in or out.
CHARACTER_BLENDS
ts
const CHARACTER_BLENDS: readonly CharacterBlend[];The blends a look may name.
CHARACTER_POINT_SHAPES
ts
const CHARACTER_POINT_SHAPES: readonly CharacterPointShape[];The point shapes a look may name.
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.
DEFAULT_CHARACTER_LOOK
ts
const DEFAULT_CHARACTER_LOOK: Readonly<Required<CharacterLook>>;The look a character from a package gets when nothing asks for one by name: the colours added up as stored on the engine's splat layer and the size-aware point (the engine's soft point while a point is small on screen, the studio's once large), chosen by Isaac on the looks sheet of 30 Sep 2026 by eye ("the colour fix with the size-aware point is the clear winner on Ari; Logan Paul has almost no difference"). A game, a manifest entry or a package asks for another by name.
Example
ts
import { DEFAULT_CHARACTER_LOOK } from 'gameable/character';
console.log(DEFAULT_CHARACTER_LOOK.pointShape, DEFAULT_CHARACTER_LOOK.blend); // 'size-aware' 'stored'FILL_PRESET_NAMES
ts
const FILL_PRESET_NAMES: readonly (
| "off"
| "tighter 1.7"
| "tighter 1.5"
| "faint dropped"
| "fill for 72"
| "tighter 1.7, faint dropped"
| "smaller buffer 0.7"
| "smaller buffer 0.5"
| "smaller buffer 0.7, tighter 1.7")[];The fill presets' names, in the levers' order.
FILL_PRESETS
ts
const FILL_PRESETS: object;The fill's named sets, a second lever beside the skin's (the two are independent: a fill preset leaves the skin's fields as they are, and the other way round); 'off' is today's picture.
Type Declaration
faint dropped
ts
readonly faint dropped: object;faint dropped.drawScale
ts
readonly drawScale: 1 = 1;faint dropped.extentSigma
ts
readonly extentSigma: 2 = 2;faint dropped.maxPointPx
ts
readonly maxPointPx: 0 = 0;faint dropped.minContribution
ts
readonly minContribution: 1 = 1;fill for 72
ts
readonly fill for 72: object;fill for 72.drawScale
ts
readonly drawScale: 0.7 = 0.7;fill for 72.extentSigma
ts
readonly extentSigma: 1.5 = 1.5;fill for 72.maxPointPx
ts
readonly maxPointPx: 48 = 48;fill for 72.minContribution
ts
readonly minContribution: 0 = 0;off
ts
readonly off: object;off.drawScale
ts
readonly drawScale: 1 = 1;off.extentSigma
ts
readonly extentSigma: 2 = 2;off.maxPointPx
ts
readonly maxPointPx: 0 = 0;off.minContribution
ts
readonly minContribution: 0 = 0;smaller buffer 0.5
ts
readonly smaller buffer 0.5: object;smaller buffer 0.5.drawScale
ts
readonly drawScale: 0.5 = 0.5;smaller buffer 0.5.extentSigma
ts
readonly extentSigma: 2 = 2;smaller buffer 0.5.maxPointPx
ts
readonly maxPointPx: 0 = 0;smaller buffer 0.5.minContribution
ts
readonly minContribution: 0 = 0;smaller buffer 0.7
ts
readonly smaller buffer 0.7: object;smaller buffer 0.7.drawScale
ts
readonly drawScale: 0.7 = 0.7;smaller buffer 0.7.extentSigma
ts
readonly extentSigma: 2 = 2;smaller buffer 0.7.maxPointPx
ts
readonly maxPointPx: 0 = 0;smaller buffer 0.7.minContribution
ts
readonly minContribution: 0 = 0;smaller buffer 0.7, tighter 1.7
ts
readonly smaller buffer 0.7, tighter 1.7: object;smaller buffer 0.7, tighter 1.7.drawScale
ts
readonly drawScale: 0.7 = 0.7;smaller buffer 0.7, tighter 1.7.extentSigma
ts
readonly extentSigma: 1.7 = 1.7;smaller buffer 0.7, tighter 1.7.maxPointPx
ts
readonly maxPointPx: 0 = 0;smaller buffer 0.7, tighter 1.7.minContribution
ts
readonly minContribution: 0 = 0;tighter 1.5
ts
readonly tighter 1.5: object;tighter 1.5.drawScale
ts
readonly drawScale: 1 = 1;tighter 1.5.extentSigma
ts
readonly extentSigma: 1.5 = 1.5;tighter 1.5.maxPointPx
ts
readonly maxPointPx: 0 = 0;tighter 1.5.minContribution
ts
readonly minContribution: 0 = 0;tighter 1.7
ts
readonly tighter 1.7: object;tighter 1.7.drawScale
ts
readonly drawScale: 1 = 1;tighter 1.7.extentSigma
ts
readonly extentSigma: 1.7 = 1.7;tighter 1.7.maxPointPx
ts
readonly maxPointPx: 0 = 0;tighter 1.7.minContribution
ts
readonly minContribution: 0 = 0;tighter 1.7, faint dropped
ts
readonly tighter 1.7, faint dropped: object;tighter 1.7, faint dropped.drawScale
ts
readonly drawScale: 1 = 1;tighter 1.7, faint dropped.extentSigma
ts
readonly extentSigma: 1.7 = 1.7;tighter 1.7, faint dropped.maxPointPx
ts
readonly maxPointPx: 0 = 0;tighter 1.7, faint dropped.minContribution
ts
readonly minContribution: 1 = 1;GAITS
ts
const GAITS: readonly Gait[];The gait names, in order.
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); // 20GNM_PACK_FILE
ts
const GNM_PACK_FILE: "gnm_head.aosrig" = 'gnm_head.aosrig';Default pack name when a manifest declares none.
HANDHELD_POINTS_CAP
ts
const HANDHELD_POINTS_CAP: 262144 = 262_144;The most points a phone or a headset takes in one character's package: four characters at the 256 level.
IDENTITY_3X4
ts
const IDENTITY_3X4: readonly number[];The identity 3x4, row-major: a rig whose vertices are already in object space.
LEVEL_NAMES
ts
const LEVEL_NAMES: readonly ["_ogs512", "_ogs256", "_ogs128", "_phone"];The level names a character's ladder has, heaviest first; _phone is the kit's phone copy (the 512 level packed for a phone: Hollow Reign's ari_phone, 130,993 points), which the kit's pages load by that name.
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'REC
ts
const REC: object;Where each value sits in a record, as the compute pass's Gaussian struct lays it out.
Type Declaration
band
ts
readonly band: 43 = HIDDEN_LANE.band;origin.w: a band point's 1-based place in the band shapes (u32 bits), 0 for none.
blend
ts
readonly blend: 27 = 27;face.w: the face binding's blend, 0 for a body point.
center
ts
readonly center: 0 = 0;xyz: the rest centre, the character's space.
color
ts
readonly color: 12 = 12;r g b a: the stored colour (0.5 + C0 f_dc) and the opacity.
covA
ts
readonly covA: 4 = 4;c00 c01 c02 c11 of the rest covariance.
covB
ts
readonly covB: 8 = 8;c12 c22.
face
ts
readonly face: 24 = 24;Three head vertex indices of the face triangle (u32 bits).
fade
ts
readonly fade: 3 = LANE.fade;center.w: the opacity factor a pose correction fades this point to at full blend (1: none).
hiddenKind
ts
readonly hiddenKind: 31 = HIDDEN_LANE.kind;inv0.w: the closed mouth's hidden kind (u32 bits): 0 none, 1 plug, 2 hidden, 3 the lips' band.
inv0
ts
readonly inv0: 28 = 28;The rest frame's inverse, three rows of three (xyz of inv0, inv1, inv2).
inv1
ts
readonly inv1: 32 = 32;inv2
ts
readonly inv2: 36 = 36;joints
ts
readonly joints: 16 = 16;Four joint indices (u32 bits).
kind
ts
readonly kind: 11 = LANE.kind;covB.w: 1 for a correction's own new point (shown by its slot's weight), 0 for the character's.
lower
ts
readonly lower: 39 = HIDDEN_LANE.lower;inv2.w: the lower lip vertex (u32 bits).
origin
ts
readonly origin: 40 = 40;xyz: the binding's origin on the rest triangle.
slot
ts
readonly slot: 10 = LANE.slot;covB.z: the correction weight slot (0 to 3).
upper
ts
readonly upper: 35 = HIDDEN_LANE.upper;inv1.w: the upper lip vertex (u32 bits).
weights
ts
readonly weights: 20 = 20;Four weights.
RECORD_FLOATS
ts
const RECORD_FLOATS: 44 = 44;Floats a packed record holds (packGaussianChunk).
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.
SCENE_DETAIL_DEFAULTS
ts
const SCENE_DETAIL_DEFAULTS: object;The scene budget's defaults for a page with several characters (createSceneDetail).
Type Declaration
budgetPoints
ts
readonly budgetPoints: 130000 = 130_000;The fallback until the characters are known: deriveBudget then takes every character at its lightest level plus the largest single step up, with 2 % room (four heroes at 21.4k / 63k: about 130k; 125k could never lift one of them).
deriveBudget
ts
readonly deriveBudget: true = true;dwellSeconds
ts
readonly dwellSeconds: 1.5 = 1.5;holderMargin
ts
readonly holderMargin: 0.2 = 0.2;maxBudgetPoints
ts
readonly maxBudgetPoints: 260000 = 260_000;minSecondsBetween
ts
readonly minSecondsBetween: 2 = 2;settleSeconds
ts
readonly settleSeconds: 1 = 1;targetFps
ts
readonly targetFps: 72 = 72;SH_TERMS
ts
const SH_TERMS: readonly object[];The real spherical harmonics of bands 1 to 3, as the compute pass evaluates them (shValue): term k of 15 is factor times a sum of monomials weight x^a y^b z^c. One table for the CPU (shBasis) and the vertex stage (webglSkinNodes.ts); the test holds the shader's text to it.
SKIN_PRESET_NAMES
ts
const SKIN_PRESET_NAMES: (
| "as is"
| "one pass"
| "sorted stream"
| "sorted stream, colour 1 band"
| "one pass, colour 1 band"
| "as is, colour 1 band"
| "as is, eager")[];The presets a lever offers a person, in order: the colour-band cuts are out (Isaac on the Quest 2, 3 Oct 2026: "the flat lighting" compromised the look for no frames), the eager fetch is the speed test's control only.
SKIN_PRESET_NAMES_ALL
ts
const SKIN_PRESET_NAMES_ALL: readonly (
| "as is"
| "one pass"
| "sorted stream"
| "sorted stream, colour 1 band"
| "one pass, colour 1 band"
| "as is, colour 1 band"
| "as is, eager")[];Every preset's name, in order (the speed test's list).
SKIN_PRESETS
ts
const SKIN_PRESETS: object;The named sets the levers and the speed test flip between (the skin's work); 'as is' is today's way.
Type Declaration
as is
ts
readonly as is: object;as is.lazyFetch
ts
readonly lazyFetch: true = true;as is.shBands
ts
readonly shBands: 3 = 3;as is.skin
ts
readonly skin: "vertex" = 'vertex';as is, colour 1 band
ts
readonly as is, colour 1 band: object;as is, colour 1 band.lazyFetch
ts
readonly lazyFetch: true = true;as is, colour 1 band.shBands
ts
readonly shBands: 1 = 1;as is, colour 1 band.skin
ts
readonly skin: "vertex" = 'vertex';as is, eager
ts
readonly as is, eager: object;as is, eager.lazyFetch
ts
readonly lazyFetch: false = false;as is, eager.shBands
ts
readonly shBands: 3 = 3;as is, eager.skin
ts
readonly skin: "vertex" = 'vertex';one pass
ts
readonly one pass: object;one pass.lazyFetch
ts
readonly lazyFetch: true = true;one pass.shBands
ts
readonly shBands: 3 = 3;one pass.skin
ts
readonly skin: "pass" = 'pass';one pass, colour 1 band
ts
readonly one pass, colour 1 band: object;one pass, colour 1 band.lazyFetch
ts
readonly lazyFetch: true = true;one pass, colour 1 band.shBands
ts
readonly shBands: 1 = 1;one pass, colour 1 band.skin
ts
readonly skin: "pass" = 'pass';sorted stream
ts
readonly sorted stream: object;sorted stream.lazyFetch
ts
readonly lazyFetch: true = true;sorted stream.shBands
ts
readonly shBands: 3 = 3;sorted stream.skin
ts
readonly skin: "stream" = 'stream';sorted stream, colour 1 band
ts
readonly sorted stream, colour 1 band: object;sorted stream, colour 1 band.lazyFetch
ts
readonly lazyFetch: true = true;sorted stream, colour 1 band.shBands
ts
readonly shBands: 1 = 1;sorted stream, colour 1 band.skin
ts
readonly skin: "stream" = 'stream';SKIN_RULES
ts
const SKIN_RULES: SkinRules;The rules' numbers, as the compute pass has them.
Functions
arkitToMh()
ts
function arkitToMh(arkit, out): Float32Array;Map 52 ARKit blendshape weights onto the 188-element MetaHuman control array.
Parameters
| Parameter | Type | Description |
|---|---|---|
arkit | ArrayLike<number> | Live ARKit weights, length >= 52. |
out | Float32Array | Destination, 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
| Parameter | Type | Default value | Description |
|---|---|---|---|
arkit52 | ArrayLike<number> | undefined | Live ARKit weights (length >= 52). |
gather | readonly number[] | undefined | N indices into the 188 MH controls (gatherFromRigNames). |
outN | Float32Array | undefined | Destination, length N === gather.length (overwritten). |
scratch188? | Float32Array<ArrayBufferLike> | undefined | Optional reusable 188-length buffer, to avoid a per-call allocation on the hot path. |
rest? | ArrayLike<number> | null | null | The bundle's rest configuration, length N. Ignored when its length does not match the gather. |
range? | RigRange | null | null | Either 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
| Parameter | Type | Description |
|---|---|---|
dx | number | The arm's direction: the joint after the upper arm less the upper arm, x. |
dy | number | Its y (the character's up is +y). |
dz | number | Its z. |
frame | ArrayLike<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
| Parameter | Type | Description |
|---|---|---|
device | GPUDevice | The renderer's device, from prepareLiftDevice. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
thetaDeg | number | The arm's elevation from hanging, degrees. |
drive | Pick<CorrectiveDrive, "fromDeg" | "toDeg"> | The drive. |
Returns
number
The weight, 0..1.
boneMaskDefault()
ts
function boneMaskDefault(): BoneMaskChoice;The mask in force for new characters.
Returns
The choice.
boneMaskEdgeFor()
ts
function boneMaskEdgeFor(choice): BoneMaskEdge;The edge a choice uses: hard for the arms, soft for the upper body and a list.
Parameters
| Parameter | Type | Description |
|---|---|---|
choice | BoneMaskChoice | The preset or the list. |
Returns
The edge.
boneMaskName()
ts
function boneMaskName(choice): string;A mask's name for a log or a card.
Parameters
| Parameter | Type | Description |
|---|---|---|
choice | BoneMaskChoice | The preset or the list. |
Returns
string
The preset's name, or the list's length.
boneMaskWeight()
ts
function boneMaskWeight(name, choice): number;A joint's mask weight under a choice.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The joint's name. |
choice | BoneMaskChoice | The preset or the list. |
Returns
number
1 kept, 0 left out, a fraction at a soft edge.
boneMaskWeights()
ts
function boneMaskWeights(jointNames, choice): Float32Array;The mask weights for every joint of a rig.
Parameters
| Parameter | Type | Description |
|---|---|---|
jointNames | readonly string[] | The rig's joint names, in the skin's joint order. |
choice | BoneMaskChoice | The preset or the list. |
Returns
Float32Array
One weight a joint.
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
| Parameter | Type | Description |
|---|---|---|
o | { acquireBuffer?: (attribute) => GPUBuffer; bundle: AosrigSplatBundle; deform?: "tsl" | "wgsl"; frame?: Object3D<Object3DEventMap>; makeSink: (request) => Promise<S>; mouth: boolean; mouthMotion?: "studio" | "frame"; mouthSettings?: AosrigMouthSettings; onMouthError?: (error) => void; pause?: () => Promise<void>; releaseBuffer?: (attribute) => void; releaseCopy?: (attribute) => void; renderer: WebGPURenderer; restPoints?: boolean; rig: Object3D; surfaceNormals?: boolean; webgl?: boolean; } | The package, its rig, the renderer and how to make a sink. |
o.acquireBuffer? | (attribute) => GPUBuffer | The WGSL mouth's buffer behind a storage attribute (acquireStorageGPUBuffer). |
o.bundle | AosrigSplatBundle | The loaded package. |
o.deform? | "tsl" | "wgsl" | The deformation: the TSL passes (default) or the WGSL pipeline (createAosrigSplat's deform). |
o.frame? | Object3D<Object3DEventMap> | The sink is shared with other characters and draws in this object's frame (createAosrigSplat's frame): it is not hidden while the character is built (its unwritten slots draw nothing), and the mouth's inside is not built. |
o.makeSink | (request) => Promise<S> | Makes a sink (createAnimatedSplat), with whatever the host adds (a draw order). |
o.mouth | boolean | Draw the inside of the mouth when the package carries the teeth's points or its frozen mouth. |
o.mouthMotion? | "studio" | "frame" | Mouth expression motion: frame (default) or studio translation only. |
o.mouthSettings? | AosrigMouthSettings | The frozen mouth's mode, look and picture size, and the window's rule. |
o.onMouthError? | (error) => void | Told 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) => void | Frees a storage attribute's GPU buffers (releaseStorageAttribute). |
o.releaseCopy? | (attribute) => void | Drop the copy here of an input the card holds (createAosrigSplat's releaseCopy); with acquireBuffer. |
o.renderer | WebGPURenderer | The renderer that owns the sinks. |
o.restPoints? | boolean | Keep every point's rest centre and group for a game's point hook. |
o.rig | Object3D | The rig the bindings follow. |
o.surfaceNormals? | boolean | Give every point a surface direction at load, for the splat's lighting (createAosrigSplat's surfaceNormals). |
o.webgl? | boolean | The 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
| Parameter | Type | Description |
|---|---|---|
bundle | AosrigSplatBundle | The loaded package (its character.ply and bindings.bin). |
rig | RigSurface | The rig mesh at rest, in the character's space. |
options | CapturedLightOptions | How many gaussians to visit and which to skip. |
Returns
The samples.
Throws
When the package's files are gone or malformed.
compactByBoneMask()
ts
function compactByBoneMask(
records,
harmonics,
shCount,
count,
weights,
edge?,
cuts?,
places?
): CompactedRecords;Keep the points the mask keeps, their opacity scaled at the soft edge, in a new compact set.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
records | Float32Array | undefined | The packed records, RECORD_FLOATS a point (the whole-number lanes as u32 bits). |
harmonics | Float32Array | undefined | shCount floats a point (any length when shCount is 0). |
shCount | number | undefined | Harmonics a point. |
count | number | undefined | Points. |
weights | Float32Array | undefined | One mask weight a joint (boneMaskWeights). |
edge | BoneMaskEdge | BONE_MASK_EDGE | The soft edge's numbers. |
cuts | readonly BoneSegmentCut[] | [] | - |
places | Float32Array | ... | - |
Returns
CompactedRecords
The compacted set (the inputs themselves when every point is kept whole).
Example
ts
const weights = boneMaskWeights(d.jointNames, 'arms and hands');
const { records, harmonics, count } = compactByBoneMask(all, sh, 45, n, weights);correctivePoints()
ts
function correctivePoints(corrections): number;The new splats the GPU is given room for: those of the corrections it plays.
Parameters
| Parameter | Type | Description |
|---|---|---|
corrections | readonly object[] | undefined | The 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.
By default 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. deform: 'wgsl' keeps the WGSL compute pipeline (bound_splat.wgsl) on the renderer's own device, and deform: 'webgl-skin' the vertex-stage skin for a phone without WebGPU (AosrigDeform). Every path plays the same package the same way: the pose corrections, the closed mouth's inside points and their band cap, and (on the compute paths) the mouth.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | AosrigSplatOptions | Verified export, its rig, the renderer and the splat sink (or splat). |
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
| Parameter | Type | Description |
|---|---|---|
layout | HeadExtLayout | The pack's header.headExt. |
options | ArkitToGnmOptions | See 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
| Parameter | Type |
|---|---|
arkit | Float32Array |
out | Float32Array |
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 } });createAutoDetail()
ts
function createAutoDetail(options?): AutoDetail;Make the governor.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | AutoDetailOptions | The numbers. |
Returns
The governor.
Example
ts
const detail = createAutoDetail({ targetFps: 72, budgetPoints: 125_000, log: (e) => console.log(e) });
detail.add({ id: 'tala', levels: ['128', '256', '512'], current: () => level, points, screenHeightPx, swap });
engine.events.on('engine:frame', ({ dt }) => detail.frame(fps, dt));createCharacter()
ts
function createCharacter(bundle, init): Promise<Character>;Bring a loaded bundle to life against a splat sink.
Parameters
| Parameter | Type | Description |
|---|---|---|
bundle | CharacterBundle | The already-loaded bundle, from loadCharacterBundle. |
init | CreateCharacterOptions | The 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
| Parameter | Type | Description |
|---|---|---|
table | { bytes: Uint8Array; info: ArkitFaceTableInfo; } | The descriptor's face.arkit and the file's bytes (hash already checked). |
table.bytes | Uint8Array | - |
table.info | ArkitFaceTableInfo | - |
layout | HeadExtLayout | The pack's header.headExt. |
Returns
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 } });createHairRuntime()
ts
function createHairRuntime(
data,
jointBase,
params?
): HairRuntime;The hair runtime for one character.
Parameters
| Parameter | Type | Description |
|---|---|---|
data | HairData | The hair, checked against the splat already. |
jointBase | number | The rig's joint count: the slots start there. |
params | Partial<HairParams> | Her settings to change from HAIR_DEFAULTS (none: hers). |
Returns
The runtime.
Example
ts
const hair = createHairRuntime(data, descriptor.jointNames.length);
// each frame, after the rig's skin matrices are written and before the bone table is uploaded:
hair.update(dt, matrices);createPoseScheduler()
ts
function createPoseScheduler(): PoseScheduler;Make a scheduler.
Returns
The scheduler.
Example
ts
const schedule = createPoseScheduler();
schedule.setBudgetPoints(120_000);
if (schedule.grant({ id: 'tala', points: 62_193, distanceM: 1.8, hz: 0, nowS: performance.now() / 1000 })) pose();createRigPreview()
ts
function createRigPreview(options): Promise<RigPreview>;Put a rig backend on screen without any decoders.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | CreateRigPreviewOptions | See 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();createSceneDetail()
ts
function createSceneDetail(options?): AutoDetail;The detail governor for a page with several characters, the scene budget as its default path: the looked-at or nearest hero heavier, the others lighter, within a budget in points that moves with the frames; the heavier level follows a gaze that rests (the dwell) and never a gaze that sweeps. The options override the defaults.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | AutoDetailOptions | What to change from SCENE_DETAIL_DEFAULTS (budgetPoints first). |
Returns
The governor.
Example
ts
const detail = createSceneDetail({ budgetPoints: 125_000 });
for (const hero of heroes) detail.add(hero);createSomaPoser()
ts
function createSomaPoser(skeleton, bones): SomaPoser;The per-frame poser for a skeleton's bones. Allocation-free after construction.
Parameters
| Parameter | Type | Description |
|---|---|---|
skeleton | SomaSkeleton | The skeleton. |
bones | readonly Object3D<Object3DEventMap>[] | Its bones, in its order (createSomaRig's). |
Returns
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
| Parameter | Type | Description |
|---|---|---|
skeleton | SomaSkeleton | The skeleton. |
Returns
object
The group and the bones in the skeleton's order.
bones
ts
bones: Bone<Object3DEventMap>[];root
ts
root: Group;createWebglAosrigSplat()
ts
function createWebglAosrigSplat(options): Promise<WebglAosrigSplat>;Bind a package to its rig on the WebGL fallback: the records in textures, the skin in the vertex stage, the order from a worker. The same interface as createAosrigSplat.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | { bundle: AosrigSplatBundle; pause?: () => Promise<void>; renderer: WebGPURenderer; rig: Object3D; skin?: WebglSkinOptions; splat: WebglSkinTarget; } | The package, its rig, the renderer and the splat to draw through. |
options.bundle | AosrigSplatBundle | - |
options.pause? | () => Promise<void> | Awaited between slices of the build's main-thread work. |
options.renderer | WebGPURenderer | - |
options.rig | Object3D | - |
options.skin? | WebglSkinOptions | How the skin spends the chip's time; the module's defaults (setWebglSkinDefaults) when omitted. |
options.splat | WebglSkinTarget | - |
Returns
Promise<WebglAosrigSplat>
The runtime; call render after the body animator updates.
Example
ts
const splat = createWebglSkinnedSplat(renderer, { capacity: d.splatCount, colorSpace: 'srgb' });
const character = await createWebglAosrigSplat({ bundle, rig, renderer, splat });
character.render(expression, [0, 0, 0, 0], camera);cutLeavesOut()
ts
function cutLeavesOut(
f,
u,
o,
cuts,
places
): boolean;Whether a segment cut leaves a point out: the point is bound mostly to one of the cut's members and its rest centre projects onto the segment before cutAt.
Parameters
| Parameter | Type | Description |
|---|---|---|
f | Float32Array | The records as floats. |
u | Uint32Array | The same as u32. |
o | number | The point's first lane. |
cuts | readonly BoneSegmentCut[] | The cuts. |
places | Float32Array | The joints' rest places, xyz a joint (the character's space). |
Returns
boolean
True when a cut leaves the point out.
debugLiftDispatch()
ts
function debugLiftDispatch(slotCount): number;Dispatch size for a slot range.
Parameters
| Parameter | Type | Description |
|---|---|---|
slotCount | number | Slots 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)); // 279fitVertsTransform()
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
| Parameter | Type | Description |
|---|---|---|
aabb | VertsAABB | The backend's neutral-pose bounds, in its own units. |
options | { height?: number; offset?: readonly [number, number, number]; scale?: number; } | Placement overrides. |
options.height? | number | Object-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? | number | A 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
| Parameter | Type | Description |
|---|---|---|
vertexCount | number | Vertices to colour. |
colour | readonly [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
| Parameter | Type | Description |
|---|---|---|
rigNames | string[] | 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
| Parameter | Type | Description |
|---|---|---|
gaze | ArrayLike<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
| Parameter | Type | Description |
|---|---|---|
pack | AosRigPack | The parsed .aosrig, supplying the neutral, the basis and the eyes. |
headExt | ArrayLike<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
| Parameter | Type | Default value | Description |
|---|---|---|---|
pack | AosRigPack | undefined | The parsed .aosrig. |
headExt | ArrayLike<number> | undefined | One frame's packed rig vector, as gnmForward takes it. |
jointWorld | Float32Array | pack.restWorld | J*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.
hairRestMismatch()
ts
function hairRestMismatch(
data,
count,
restCentre
): string | null;Her rule that the files belong to this splat (hairMismatch, on a row reader instead of a whole centres array): the row count, and the 16 check rows' rest centres within 0.1 mm.
Parameters
| Parameter | Type | Description |
|---|---|---|
data | HairData | The hair. |
count | number | The splat's rows. |
restCentre | (row) => readonly [number, number, number] | null | The rest centre of a row in the character's frame (x, y, z), or null past the end. |
Returns
string | null
Why the files do not fit, or null when they do.
Example
ts
const why = hairRestMismatch(data, descriptor.splatCount, (row) => centreOf(row));
if (why) console.warn('aosrig-splat: the hair is not used:', why);hairSlotCount()
ts
function hairSlotCount(data): number;The bone slots the hair adds after the rig's joints: her proxies, then every strand point.
Parameters
| Parameter | Type | Description |
|---|---|---|
data | HairData | The hair. |
Returns
number
The slot count.
Example
ts
const slots = hairSlotCount(data); // the bone table grows by this manyheadExtNames()
ts
function headExtNames(layout): string[];Every control name the layout declares, in order — the manifest's control_names.
Parameters
| Parameter | Type | Description |
|---|---|---|
layout | HeadExtLayout | The 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
| Parameter | Type | Description |
|---|---|---|
d | Pick<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
| Parameter | Type | Description |
|---|---|---|
verts | Float32Array | (V,3) vertices in the rig's own units. |
vertexCount | number | Vertices 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
| Parameter | Type | Description |
|---|---|---|
gapM | number | The 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
| Parameter | Type | Description |
|---|---|---|
pack | AosRigPack | The parsed .aosrig, for skinIndex, skinWeight and eyeWeights. |
Returns
Uint32Array
One packed RGBA per vertex, opaque.
legJointsOf()
ts
function legJointsOf(skeleton): boolean[];Which joints hang below the Hips on a leg rather than up the spine: a joint whose chain to the Hips does not pass through the Head's own chain.
Parameters
| Parameter | Type | Description |
|---|---|---|
skeleton | SomaSkeleton | The skeleton. |
Returns
boolean[]
Per joint index, true for a leg joint.
levelForScreenHeight()
ts
function levelForScreenHeight(
px,
levels,
thresholds?,
current?,
band?
): string;The level a figure's height on screen asks for: the heaviest level whose threshold it clears. A level without a threshold (the lightest) is always allowed. With current and a band, the answer sticks near a threshold.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
px | number | undefined | The figure's height on screen, pixels. |
levels | readonly string[] | undefined | The character's levels, lightest first. |
thresholds | Readonly<Record<string, number>> | DEFAULT_THRESHOLDS | Pixels a level needs. |
current? | string | undefined | The level shown now, for the band (optional). |
band? | number | 0.15 | The band either side of a threshold, a share of it. |
Returns
string
The level.
Example
ts
levelForScreenHeight(900, ['128', '256', '512']); // '512'levelLine()
ts
function levelLine(who, level): string;The loader's first line for a character: who, how many points, which level (or that it is a build).
Parameters
| Parameter | Type | Description |
|---|---|---|
who | string | What the page calls the character (its manifest id), or the package folder's name. |
level | CharacterLevel | The package as read. |
Returns
string
The line.
Example
ts
import { levelLine, levelOfPackage } from 'gameable/character';
console.log(levelLine('Elio', levelOfPackage('/c/F22Elio_ogs256/character.json', 128_699)));
// 'Elio: 128,699 points, the _ogs256 level'levelOfPackage()
ts
function levelOfPackage(url, points): CharacterLevel;What a package is, from where its character.json sits and how many points it declares.
Parameters
| Parameter | Type | Description |
|---|---|---|
url | string | The character.json's address (where the fetch ended). |
points | number | The package's points (splatCount). |
Returns
The folder's name, its level name (null for a build) and the points.
Example
ts
import { levelOfPackage } from 'gameable/character';
const level = levelOfPackage('https://play.test/chars/F22Elio_ogs256/character.json', 128_699);
console.log(level.name, level.points); // '_ogs256' 128699levelVerdict()
ts
function levelVerdict(
who,
level,
device?,
insteadPoints?
): LevelVerdict | null;Whether a device may take a package: a phone or a headset refuses a package with more than HANDHELD_POINTS_CAP points, a computer takes it and says the same line as a warning; a package with no level name on its folder but within the cap is taken by every device and said (the kit's own games name no level on some folders: a name is said, the points decide).
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
who | string | undefined | What the page calls the character. |
level | CharacterLevel | undefined | The package as read. |
device | DeviceKind | ... | The device the page runs on; the page's own when omitted. |
insteadPoints | number | null | null | The points of the _ogs256 level beside the package, when known, for the line. |
Returns
LevelVerdict | null
Null when the package is a level any device takes; else the line and whether it is refused.
Example
ts
import { levelVerdict, levelOfPackage } from 'gameable/character';
const build = levelOfPackage('/c/F22Elio/character.json', 1_735_813);
console.log(levelVerdict('Elio', build, 'phone', 128_699)?.line);
// 'Elio: 1,735,813 points is a build, not a level; on a phone load _ogs256 (128,699 points)'
console.log(levelVerdict('Elio', levelOfPackage('/c/F22Elio_ogs256/character.json', 128_699), 'phone')); // null
console.log(levelVerdict('Ari', levelOfPackage('/c/ari_phone/character.json', 130_993), 'phone')); // null
console.log(levelVerdict('Kael', levelOfPackage('/c/kael/character.json', 200_000), 'phone')?.refuse); // falseloadAosrigSplatBundle()
ts
function loadAosrigSplatBundle(
url,
signal?,
options?
): Promise<AosrigSplatBundle>;Load an extracted character archive, validating all content hashes.
Parameters
| Parameter | Type | Description |
|---|---|---|
url | string | URL of character.json. |
signal? | AbortSignal | Optional cancellation signal. |
options? | AosrigSplatLoadOptions | Which 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
| Parameter | Type | Description |
|---|---|---|
url | string | The bundle directory, or any string the supplied resolver understands. |
options | LoadCharacterBundleOptions | fetch / 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);mapMouthGlb()
ts
function mapMouthGlb(
parts,
mesh,
rest
): MouthCorners;Lay mouth.glb's parts on the pack's mouth mesh, triangle for triangle: part k of the mesh is the glb's node named mouth_<name> (the bag: mouth_bag), or its k-th node.
Parameters
| Parameter | Type | Description |
|---|---|---|
parts | MouthGlbPart[] | parseMouthGlb's parts. |
mesh | MouthMesh | The pack's mouth (buildMouthMesh). |
rest | ArrayLike<number> | The pack's rest vertices in the character frame (restVertices). |
Returns
MouthCorners
The corners and each vertex's offset.
Throws
When a part's triangles are not the pack's, or stand too far from them.
mouthRecess()
ts
function mouthRecess(
face,
rest,
rim,
forward
): number;How far the splat's lips stand behind the head model's (metres, 0 when they do not): for each vertex of the lips' inner rim, the front-most opaque point within 3 mm of the line through it along the face's forward (and 15 mm of it), less the vertex; the median, turned round. The studio's rule (face_gnm_api.mouth_recess, measured there on the lips' outside: Tala 5.9 mm), here on the rim, the lips the engine has; a package that states its setBackM is taken at its word instead.
Parameters
| Parameter | Type | Description |
|---|---|---|
face | FaceSplats | The face's splats. |
rest | ArrayLike<number> | The pack's rest vertices in the character frame. |
rim | ArrayLike<number> | The lips' inner rim, pack vertex ids. |
forward | readonly [number, number, number] | The face's forward at the mouth (unit). |
Returns
number
Metres.
orlDriveMode()
ts
function orlDriveMode(vertexCount): OrlDriveMode;Decide the drive mode from the branch mesh alone.
Parameters
| Parameter | Type | Description |
|---|---|---|
vertexCount | number | The 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.
ownIdleClip()
ts
function ownIdleClip(idle, skeleton): AnimationClip;Build the character's own idle from the package's idle and its skeleton.
Parameters
| Parameter | Type | Description |
|---|---|---|
idle | SomaClip | The package's idle clip (idle, a copy of the shared standing clip). |
skeleton | SomaSkeleton | The package's skeleton, whose rest is the stance. |
Returns
AnimationClip
The clip, named as the idle, looping, tagged as the idle was.
Example
ts
import { ownIdleClip } from 'gameable/character';
const idle = bundle.soma.clips.find((c) => c.name === 'idle');
const clip = ownIdleClip(idle, bundle.soma.skeleton);
console.log(clip.name, clip.duration > 0); // 'idle' truepackAosRig()
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
| Parameter | Type | Description |
|---|---|---|
header | Omit<AosRigHeader, "buffers"> | Everything but buffers, which is derived from the laid-out blobs. |
blobs | AosRigBlob[] | 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
| Parameter | Type | Default value | Description |
|---|---|---|---|
r | number | undefined | Red in [0, 1]; out-of-range values are clamped. |
g | number | undefined | Green in [0, 1]. |
b | number | undefined | Blue in [0, 1]. |
a | number | 1 | Alpha 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
| Parameter | Type | Description |
|---|---|---|
bytes | ArrayBuffer | Uint8Array<ArrayBufferLike> | The downloaded pack, as bytes or a whole buffer. |
Returns
The parsed pack: the header plus typed views over the blobs. Only skinWeight is materialised (widened from its f16 lanes) and the mouth's small blobs are copied (the mouth keeps them); 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 387parseCharacterLook()
ts
function parseCharacterLook(value, where): CharacterLook;Validate a look as JSON gives it: an object whose pointShape and blend, when present, are known names.
Parameters
| Parameter | Type | Description |
|---|---|---|
value | unknown | The raw value. |
where | string | Named in the error. |
Returns
The look, its two fields when present.
Example
ts
import { parseCharacterLook } from 'gameable/character';
parseCharacterLook({ pointShape: 'studio' }, 'character.json'); // { pointShape: 'studio' }parseCorrectiveIndex()
ts
function parseCorrectiveIndex(value, descriptor): CorrectiveInfo[];Validate corrective/index.json against the package it rides with.
Parameters
| Parameter | Type | Description |
|---|---|---|
value | unknown | Parsed JSON. |
descriptor | { jointNames: readonly string[]; splatCount: number; } | The package's splat count and joint names. |
descriptor.jointNames | readonly string[] | - |
descriptor.splatCount | number | - |
Returns
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/.
parseHairFiles()
ts
function parseHairFiles(json, bind): HairData | null;Read hair.json and hair.bind.bin (her format, FORMAT.md) into her HairData.
Parameters
| Parameter | Type | Description |
|---|---|---|
json | Uint8Array | The bytes of hair.json. |
bind | Uint8Array | The bytes of hair.bind.bin (HBND0001). |
Returns
HairData | null
The data, or null when either file is not hers.
Example
ts
const data = parseHairFiles(files.get('hair/hair.json'), files.get('hair/hair.bind.bin'));
if (data) console.log(data.strands.length, 'strands on', data.idx.length, 'rows');parseMouthGlb()
ts
function parseMouthGlb(bytes): MouthGlbPart[];The parts of mouth.glb (one triangle-list primitive per node, positions and texture coordinates, indexed or not), each expanded to its corners in triangle order.
Parameters
| Parameter | Type | Description |
|---|---|---|
bytes | Uint8Array | The GLB. |
Returns
The parts, in the file's node order.
Throws
When it is not a GLB of that shape.
parseMouthHidden()
ts
function parseMouthHidden(
bytes,
splatCount,
headVertexCount
): MouthHidden;Parse mouth_hidden.bin.
Parameters
| Parameter | Type | Description |
|---|---|---|
bytes | Uint8Array | The file. |
splatCount | number | The character's splat count (rows must be under it). |
headVertexCount | number | The head model's vertex count (the lip vertices must be under it). |
Returns
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.
parseMouthMeshInfo()
ts
function parseMouthMeshInfo(value): MouthMeshInfo;Read mouth.json.
Parameters
| Parameter | Type | Description |
|---|---|---|
value | unknown | The parsed JSON. |
Returns
What the engine reads.
Throws
When it does not name the two files.
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
| Parameter | Type | Description |
|---|---|---|
value | unknown | The parsed JSON. |
skeleton | SomaSkeleton | The 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
| Parameter | Type | Description |
|---|---|---|
value | unknown | The parsed JSON. |
jointNames | readonly string[] | character.json's jointNames. |
Returns
The skeleton.
Throws
When the file is not an aosrig-v2 skeleton of those joints.
planLevels()
ts
function planLevels(
characters,
budgetPoints,
options?
): Map<string, string>;The level each character should show under a scene budget in points: by rank (the game's own order first, then the looked-at or nearest), the heaviest level the budget leaves after every other character's lightest; a holder of a heavier level keeps its place in the rank unless a challenger beats it by holderMargin. With no current this is the plan a fight starts on: every character at its heaviest level when they all fit the budget, else the first in rank at the heaviest and the rest a step down, never over the budget.
Parameters
| Parameter | Type | Description |
|---|---|---|
characters | readonly LevelPlanCharacter[] | The characters, with their levels and points. |
budgetPoints | number | The budget, points posed a frame (252,000 on a phone or a headset). |
options | { holderMargin?: number; wanted?: (c) => string; } | wanted(c): the heaviest level a character's size asks for (default its heaviest); holderMargin (default 0.2). |
options.holderMargin? | number | - |
options.wanted? | (c) => string | - |
Returns
Map<string, string>
Each character's level, by id.
Example
ts
import { planLevels } from 'gameable/character';
const plan = planLevels(
[
{ id: 'jake', levels: ['128', '256'], points: (l) => (l === '128' ? 30_561 : 119_333), rank: () => 0 },
{ id: 'elio', levels: ['128', '256'], points: (l) => (l === '128' ? 30_007 : 117_210), rank: () => 1 },
{ id: 'desmond', levels: ['128', '256'], points: (l) => (l === '128' ? 26_683 : 103_851), rank: () => 2 },
],
252_000,
);
console.log([...plan.values()]); // ['256', '128', '128']: after the first at 256, the others do not fit at 256plugPush()
ts
function plugPush(gapM): number;How far back along the head's forward a plug is pushed for a lips' gap.
Parameters
| Parameter | Type | Description |
|---|---|---|
gapM | number | The gap, metres. |
Returns
number
Metres.
poseMovedPastGate()
ts
function poseMovedPastGate(
bones,
sorted,
places,
sortedPlaces,
reach,
joints,
gate
): boolean;Whether the pose moved past the gate since the last order (the studio's rule, made about points and about the order): a joint's turn (the largest change of its skin matrix's 3 x 3 entries) times its reach, or a joint's place moved RELATIVE to the whole body's move (the mean of the joints' moves: a body that only sways or walks keeps its draw order, its points all move alike), further than gate.positionM; a turn past gate.rotation counts on its own.
Parameters
| Parameter | Type | Description |
|---|---|---|
bones | Float32Array | The skin matrices now, 16 a joint. |
sorted | Float32Array | The ones the order was made for. |
places | Float32Array | The joints' places now, xyz a joint. |
sortedPlaces | Float32Array | The ones the order was made for. |
reach | ArrayLike<number> | Each joint's reach (jointReach). |
joints | number | How many joints to compare. |
gate | { positionM: number; rotation: number; } | The gate. |
gate.positionM | number | - |
gate.rotation | number | - |
Returns
boolean
True when a new order is due for the pose.
poseScheduler()
ts
function poseScheduler(): PoseScheduler;The page's one scheduler (every fallback skin asks it).
Returns
The scheduler.
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
| Parameter | Type | Description |
|---|---|---|
renderer | RendererLike | The 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 sessionreadFightSet()
ts
function readFightSet(source, parse): FightSet;the pack, its joints and its fight block (as the pack carries it, JSON text) from a set's own words (what decodeMotionSet or the text gives)
Parameters
| Parameter | Type |
|---|---|
source | unknown |
parse | (value) => FightPack |
Returns
regionSlices()
ts
function regionSlices(layout): Record<string, {
end: number;
start: number;
}>;Where each expression region sits inside head_ext.
Parameters
| Parameter | Type | Description |
|---|---|---|
layout | HeadExtLayout | The 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 goresolveSkinOptions()
ts
function resolveSkinOptions(options, base): WebglSkinOptionsInForce;Options over a base, fields left out keeping the base's.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | WebglSkinOptions | undefined | The fields given. |
base | WebglSkinOptionsInForce | What they change. |
Returns
The whole set.
screenHeightPx()
ts
function screenHeightPx(
heightM,
distanceM,
viewportHeightPx,
verticalFovDeg
): number;A figure's height on screen, pixels, from its height in metres, its distance to the eye and the eye's own numbers.
Parameters
| Parameter | Type | Description |
|---|---|---|
heightM | number | The figure's height, metres. |
distanceM | number | Its distance from the eye, metres. |
viewportHeightPx | number | The eye's picture height, pixels. |
verticalFovDeg | number | The eye's vertical field of view, degrees. |
Returns
number
Pixels (0 at or behind the eye).
Example
ts
screenHeightPx(1.7, 1.5, 1346, 98); // about 660setBoneMaskDefault()
ts
function setBoneMaskDefault(choice): BoneMaskChoice;The mask every character made from now on starts with (a page sets it before its characters load; the WebGL skin's own boneMask option overrides it per character).
Parameters
| Parameter | Type | Description |
|---|---|---|
choice | BoneMaskChoice | The preset or the list. |
Returns
The choice in force.
Example
ts
setBoneMaskDefault('arms and hands');setPoseBudgetPoints()
ts
function setPoseBudgetPoints(points): number;The scene budget: points re-posed a frame across every character; 0 (the default) is no limit.
Parameters
| Parameter | Type | Description |
|---|---|---|
points | number | The budget. |
Returns
number
The budget in force.
Example
ts
setPoseBudgetPoints(120_000); // four heroes at 256 pose in turn, about two a framesetWebglSkinDefaults()
ts
function setWebglSkinDefaults(options): WebglSkinOptionsInForce;The options every fallback skin made from now on starts with (a page sets them before its characters load; setSkinOptions on a runtime changes one that is already drawn).
Parameters
| Parameter | Type | Description |
|---|---|---|
options | WebglSkinOptions | The fields to change; the rest keep the built-in defaults. |
Returns
The options now in force for new skins.
Example
ts
setWebglSkinDefaults({ skin: 'pass' });shBasis()
ts
function shBasis(
k,
x,
y,
z
): number;Term k of SH_TERMS at a unit direction.
Parameters
| Parameter | Type | Description |
|---|---|---|
k | number | The term, 0 to 14. |
x | number | Direction x. |
y | number | Direction y. |
z | number | Direction z. |
Returns
number
The basis value.
shTermsForBands()
ts
function shTermsForBands(bands): number;Harmonics per channel a band count evaluates: band 1 is 3 terms, band 2 adds 5, band 3 adds 7 (SH_TERMS).
Parameters
| Parameter | Type | Description |
|---|---|---|
bands | number | Bands, 0 to 3. |
Returns
number
Terms per colour channel.
shTexelsFetched()
ts
function shTexelsFetched(shCount, bands): number;How many harmonics texels a point's colour reads for a band count, given the package's own count: the three channels' lanes are planar (all red, then all green, then all blue), so fewer bands skip whole texels only where the lanes line up (45 harmonics: 12 texels at 3 bands, 8 at 2, 5 at 1, 0 at 0).
Parameters
| Parameter | Type | Description |
|---|---|---|
shCount | number | Harmonics a point in the package (0, 9, 24 or 45). |
bands | number | Bands evaluated. |
Returns
number
Texels fetched per point.
siblingLevelUrl()
ts
function siblingLevelUrl(url, level): string | null;Where the _ogs256 level beside a package would sit: the package's folder with its level name swapped (or added, for a build), character.json inside it.
Parameters
| Parameter | Type | Description |
|---|---|---|
url | string | The package's character.json address (where the fetch ended). |
level | CharacterLevel | The package as read. |
Returns
string | null
The sibling's character.json address, or null when the package is that level already.
Example
ts
import { levelOfPackage, siblingLevelUrl } from 'gameable/character';
const build = levelOfPackage('https://play.test/c/F22Elio/character.json', 1_735_813);
console.log(siblingLevelUrl('https://play.test/c/F22Elio/character.json', build));
// 'https://play.test/c/F22Elio_ogs256/character.json'skeletonOfRig()
ts
function skeletonOfRig(rig): FightSkeletonLike;a character's rig at rest as a skeleton the fight body is built from
Parameters
| Parameter | Type |
|---|---|
rig | Object3D |
Returns
skinColor()
ts
function skinColor(
f,
o,
harmonics,
h,
shCount,
skinned,
at,
camera,
out,
to
): void;One point's colour with its view-dependent part, on the CPU, as the compute pass evaluates it: the harmonics read in the point's own rest frame (J orthonormalised), the result clamped to 0..1, as stored (before any colour-space decode).
Parameters
| Parameter | Type | Description |
|---|---|---|
f | Float32Array | The records. |
o | number | The point's first lane. |
harmonics | ArrayLike<number> | The point's harmonics, shCount floats a point, channel-planar (all red, all green, all blue). |
h | number | The point's first harmonic. |
shCount | number | Harmonics a point (0, 9, 24 or 45). |
skinned | Float32Array | What skinPoint wrote for the point with full (the centre at at, J at at + 10). |
at | number | Where it wrote. |
camera | ArrayLike<number> | The camera in the character's space, xyz. |
out | Float32Array | Where to write r g b. |
to | number | The first float to write. |
Returns
void
Nothing; out is written.
skinPoint()
ts
function skinPoint(
rules,
f,
u,
o,
bones,
head,
headRest,
bandShapes,
params,
out,
at,
full
): void;One point's skin, on the CPU: the reference the worker runs for the draw order and the tests hold both shaders to. Self-contained on purpose (no closure, no import: its source is copied into the worker as text), so every number it needs comes in as an argument.
Writes the posed centre to out[at .. at + 2]; with full, also the posed covariance c00 c01 c02 c11 c12 c22 to out[at + 3 .. at + 8], the opacity to out[at + 9] and the point's shape turn J (3 x 3, row-major) to out[at + 10 .. at + 18], which the colour's view direction is read through.
Parameters
| Parameter | Type | Description |
|---|---|---|
rules | SkinRules | The numbers (SKIN_RULES). |
f | Float32Array | The records, 44 floats a point. |
u | Uint32Array | The same records read as u32 (the whole-number lanes). |
o | number | The point's first lane (i * 44). |
bones | Float32Array | The skin matrices, 16 column-major floats a joint (the compute pass's uniform). |
head | ArrayLike<number> | The head's vertices this frame, xyz each (the character's space). |
headRest | ArrayLike<number> | The head's vertices at rest, xyz each. |
bandShapes | ArrayLike<number> | The lips' band points' own shapes, 8 floats a band point (turn wxyz, sizes xyz, pad). |
params | ArrayLike<number> | 16 floats: the head's up (xyz; w 1 while the hidden points play), the head's forward (xyz; w 1 while the band's size cap is on), the four correction weights, then four spare. |
out | Float32Array | Where to write. |
at | number | The first float to write. |
full | boolean | Whether to write the covariance, the opacity and the turn too. |
Returns
void
Nothing; out is written.
somaAnimationClip()
ts
function somaAnimationClip(
clip,
skeleton,
options?
): 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, its speed, its gait, its feet's reading (somaClipFeet: the planted feet's real travel on this skeleton) and its feet's contacts (contacts: left and right, one 0 or 1 per frame at fps, authored when the clip's file states them and false when they were read off the feet's height), as the engine's clip index reads them. A looping clip is closed with its first frame again at frames / fps, so its wrap skips no frame.
Parameters
| Parameter | Type | Description |
|---|---|---|
clip | SomaClip | A parsed clip. |
skeleton | SomaSkeleton | The skeleton it plays on. |
options | SomaClipOptions | How it is placed. |
Returns
AnimationClip
The clip, named as the package names it.
somaClipFeet()
ts
function somaClipFeet(clip, skeleton): SomaClipFeet;Read a clip's feet on a skeleton: the lowest they reach and the planted feet's travel. Walks only the leg chains, once per clip at load; nothing per frame.
Parameters
| Parameter | Type | Description |
|---|---|---|
clip | SomaClip | The clip. |
skeleton | SomaSkeleton | The skeleton it plays on. |
Returns
The feet's reading.
Example
ts
import { somaClipFeet } from 'gameable/character';
const feet = somaClipFeet(walk, skeleton);
console.log(feet.travel > 0.5); // true for a walksomaRestFootY()
ts
function somaRestFootY(skeleton): number;Where a skeleton's feet stand at rest: the middle of the ankle and the ball, rig frame, metres (the two feet averaged). A planted foot in a clip stands here.
Parameters
| Parameter | Type | Description |
|---|---|---|
skeleton | SomaSkeleton | The skeleton. |
Returns
number
The height.
Example
ts
import { somaRestFootY } from 'gameable/character';
console.log(somaRestFootY(skeleton) > 0); // truesortByDepth()
ts
function sortByDepth(
centers,
count,
view,
near,
far,
bins,
order,
scratch,
share?,
keep?
): number;The draw order from posed centres: the fork's counting sort on the CPU (_sortCPU), depth binned over [near, far] into bins, the far bin first, a permutation of [0, count). Self-contained, for the worker.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
centers | Float32Array | undefined | xyz a point, the splat's own space. |
count | number | undefined | Points. |
view | ArrayLike<number> | undefined | The splat-to-view matrix, 16 column-major floats (camera.matrixWorldInverse * splat.matrixWorld). |
near | number | undefined | The depth range's near, metres. |
far | number | undefined | Its far. |
bins | number | undefined | Depth bins. |
order | Uint32Array | undefined | Where the permutation goes, count entries (the capacity's extra slots, if any, are left as they are). |
scratch | Uint32Array | undefined | Work space: count u32 for the bins, then bins + 1 u32 twice for counts and offsets (the extra bin holds the points a share leaves out; a scratch of the older size, count + 2 * bins, sorts every point). |
share | number | 1 | The share of points kept, 0 to 1 (a test lever: a quarter, a sixteenth): a fixed, evenly spread subset by each point's own index, so the picture thins rather than loses a limb; the rest go after the kept ones in order, where a draw count of the kept number leaves them undrawn. Default 1, every point. |
keep | Uint8Array<ArrayBufferLike> | null | null | One byte a point, 0 for a point a live bone mask leaves out: it goes after the kept ones too (the draw count stops before it), with no new set and no new upload. Null: every point. |
Returns
number
How many points were kept at the front of order (count at share 1 with no mask).
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
| Parameter | Type | Description |
|---|---|---|
headExt | ArrayLike<number> | One frame's rig vector, at least layout.dim long. A longer one is accepted and its tail ignored. |
layout | HeadExtLayout | The 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? | Float32Array | Destination for the exprDim expression coefficients. |
out.gaze? | Float32Array | Destination 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);upperArmCuts()
ts
function upperArmCuts(jointNames, cutAt?): BoneSegmentCut[];The arms preset's cuts: each upper arm at its mid-length, applied to the upper arm's points, its twists' and the clavicle's. Empty when the rig has no such joints.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
jointNames | readonly string[] | undefined | The rig's joint names. |
cutAt | number | 0.5 | Where along the upper arm, 0 at the shoulder joint, 1 at the elbow. Default 0.5. |
Returns
BoneSegmentCut[]
The cuts, one a side that has both joints.
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
| Parameter | Type | Description |
|---|---|---|
uv | Float32Array | (V,2) texture coordinates. |
vertexCount | number | Vertices 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
| Parameter | Type | Description |
|---|---|---|
device | GPUDevice | The 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
| Parameter | Type | Description |
|---|---|---|
params | DebugLiftParams | The values to write. |
into? | ArrayBuffer | An 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
| Parameter | Type | Default value | Description |
|---|---|---|---|
ms | number | 8 | Work allowed between yields. |
Returns
The pause.
() => Promise<void>
Example
ts
const pause = yieldingPause();
const bundle = await loadAosrigSplatBundle(url, signal, { pause });