AuraJS 3D API Contract
Public handbook: docs/external/game-dev-api/README.md
Exact frozen signatures, validation rules, and reason-code semantics remain in this file. This file replaces the old api-contract-3d-v2.md path.
Table of Contents
- Animation Mixer and Crossfade
- Physics3D Joint Integration
- Terrain Runtime Integration
- Conventions
aura.draw3d
aura.atmosphere
aura.camera3d
aura.light
aura.mesh
aura.material
aura.compute
- Error Semantics Summary
- Reference Examples
- Frozen Surface Summary
This document defines the exact public 3D API surface:
aura.draw3d
aura.atmosphere
aura.camera3d
aura.light
aura.mesh
aura.material
aura.compute
It freezes JS-visible signatures, argument validation behavior, and error semantics so renderer and binding work can proceed without drift.
Related 3D gameplay physics note:
- The public
aura.physics3d contract, including rigid bodies, queries, and
joints, is defined in docs/api-contract.md.
- This file still documents the 3D-side integration expectations relevant to
authored scenes, materials, and rendering.
- Native
.mp4 video playback for in-world screens remains defined separately
in docs/native-mp4-video-contract-v1.md.
Split Routes
Use one of these narrower exact-reference pages before reading this file
end-to-end:
- Draw3D, Camera, Lighting, and PostFX Exact Reference
- Scene3D, Mesh, Material, glTF, and Avatar Exact Reference
- Physics3D, Terrain, Navmesh, Character3D, and Compute Exact Reference
- Net, Multiplayer, and Runtime Sessions Exact Reference
0.5 Animation Mixer and Crossfade
Deterministic clip blending is provided through
aura.animation (documented in the core contract addendum):
- Use
aura.animation.crossfade(timelineId, { duration, fromTime?, toTime?, eventTag? })
for mixer-style clip blend orchestration.
crossfade(...) emits deterministic crossfade_complete event callbacks in the same
ordering phase used by timeline transitions.
- Transition/marker payloads include
blendMode ("crossfade" or "transition") so
gameplay logic can branch deterministically without per-frame polling.
pause/resume/seek behavior with active blends remains deterministic and stable.
0.6 Physics3D Joint Integration
Public 3D mechanism behavior is now expected to use the documented
aura.physics3d surface rather than gameplay-specific proxies:
- canonical joint kinds are
revolute, ball, prismatic, and fixed
- public joint views expose stable
id, kind, bodyA, bodyB, getState(),
remove(), and state
Truthful integration scope:
- use
aura.physics3d to author visible mechanisms such as hinged doors,
slider platforms, pendulums, and locked mounts inside a rendered 3D scene
- joint-backed gameplay is part of the supported 3D gameplay surface
- non-goals remain unchanged: this does not imply cloth, soft-body, vehicles,
or breakable-constraint authoring
Minimal example:
const hinge = aura.physics3d.joint("revolute", frame.id, door.id, {
anchorA: { x: 0.5, y: 0, z: 0 },
anchorB: { x: -0.5, y: 0, z: 0 },
axis: { x: 0, y: 1, z: 0 },
limits: { min: -0.8, max: 0.8 },
motor: { targetVel: 1.0, maxForce: 40 }
});
const hingeState = hinge.getState();
Use the canonical kind names above in authored game code and docs. Legacy alias
strings are compatibility-only normalization inputs, not the public contract.
0.7 Terrain Runtime Integration
Public terrain gameplay now uses the canonical aura.terrain surface rather
than older helper-only terrain submission hooks:
- terrain creation and mutation live on
aura.terrain.create(...),
setSplatMap(...), setLayerTexture(...), setHeightmap(...), and
setVisible(...)
- stable runtime inspection lives on
aura.terrain.getInfo(...),
getVegetationInfo(...), getSubmissionState(), and sampleHeight(...)
- vegetation scatter uses
aura.terrain.addVegetation(...) /
removeVegetation(...) on the same canonical terrain handle
Truthful scope:
- the public terrain runtime supports real native heightfield rendering,
texture splat layering, vegetation submission, and deterministic runtime
telemetry
- the public surface is appropriate for authored runtime terrain slices and
gameplay-driven terrain queries
- non-goals remain unchanged: this does not imply a terrain editor, biome
painter, chunk streaming, or vegetation placement tooling
Minimal example:
const terrainId = aura.terrain.create({
width: 16,
depth: 16,
segmentsX: 6,
segmentsZ: 6,
maxHeight: 5
});
aura.terrain.setSplatMap(terrainId, "assets/terrain-splat.png");
aura.terrain.setLayerTexture(terrainId, 0, "assets/terrain-grass.png", 4);
aura.terrain.setHeightmap(terrainId, heightmapValues);
const vegetationId = aura.terrain.addVegetation(terrainId, {
texture: "assets/terrain-grass-blade.png",
density: 0.45,
minHeight: 0.7,
maxHeight: 1.35,
splatChannel: 0
});
const terrainState = aura.terrain.getSubmissionState();
1) Conventions (must match the core contract)
- Coordinates: Right-handed world space.
+X right, +Y up, +Z forward.
- Angles: Radians.
- Color format: Aura v1 color convention (
{r,g,b,a} float channels in [0.0, 1.0]; a defaults to 1.0 where applicable).
- Handle style: Opaque numeric handles for mesh/material/light resources.
- Transform inputs:
{ position?: Vec3, rotation?: Vec3, scale?: Vec3 } where rotation is Euler radians (x, y, z).
Type aliases (TypeScript notation):
type MeshHandle = number;
type MaterialHandle = number;
type LightHandle = number;
type DataTextureHandle = number;
type Vec3 = { x: number; y: number; z: number };
type Color = { r: number; g: number; b: number; a?: number };
type ColorRgb = { r: number; g: number; b: number };
type GradientTextureStop = { offset: number; color: Color };
type DataTextureCreateOptions = {
format?: "r8" | "rg8" | "rgba8";
filter?: "linear" | "nearest" | "pixelated";
wrap?: "repeat" | "clamp" | "mirror";
};
type GradientTextureOptions = DataTextureCreateOptions & {
direction?: "horizontal" | "vertical" | "radial";
};
type NoiseTextureOptions = DataTextureCreateOptions & {
mode?: "white";
seed?: number;
};
type Mat4 = [
number, number, number, number,
number, number, number, number,
number, number, number, number,
number, number, number, number
];
2) `aura.draw3d`
2.0 `aura.draw3d.createMesh(vertices, indices)`
aura.draw3d.createMesh(
vertices: Array<{
position: [number, number, number];
normal?: [number, number, number];
uv?: [number, number];
color?: [number, number, number, number];
jointIndices?: [number, number, number, number];
jointWeights?: [number, number, number, number];
}>,
indices: number[]
): MeshHandle
| Property |
Value |
| Description |
Create a JS-authored mesh handle for the legacy aura.draw3d queue surface. |
| Validation |
vertices must be an array of objects and indices must be an array of numbers. jointIndices, when provided, must be exactly 4 finite non-negative integers in u32 range. jointWeights, when provided, must be exactly 4 finite non-negative numbers with a strictly positive sum. |
| Normalization |
Missing normal, uv, and color still fall back to the existing defaults. Missing jointIndices explicitly fall back to [0, 0, 0, 0]. Missing jointWeights explicitly fall back to [1, 0, 0, 0]. Provided jointWeights are normalized deterministically to sum to 1.0. |
| Error behavior |
Malformed skinning arrays throw deterministic JS errors with stable reason-code fragments: invalid_skinning_joint_indices, invalid_skinning_joint_index_value, invalid_skinning_joint_weights, invalid_skinning_joint_weight_value, or invalid_skinning_joint_weight_total. |
| Scope note |
This contract covers JS-authored mesh input only. glTF mesh ingest remains unchanged in this task. |
aura.draw3d.drawMesh(mesh: MeshHandle, material: MaterialHandle, transform?: {
position?: Vec3;
rotation?: Vec3;
scale?: Vec3;
}): void
| Property |
Value |
| Description |
Queue a mesh draw for the current frame using the provided material and transform. |
| Validation |
Invalid mesh/material handle -> warning + no-op. Invalid transform fields -> warning + field fallback (position={0,0,0}, rotation={0,0,0}, scale={1,1,1}). |
| Error behavior |
Never throws for validation failures; warning + no-op/fallback. |
aura.draw3d.drawMeshInstanced(
mesh: MeshHandle,
material: MaterialHandle,
transforms: Array<{
position?: Vec3;
rotation?: Vec3;
scale?: Vec3;
}>
): void
| Property |
Value |
| Description |
Queue a contiguous instanced mesh submission for the current frame. |
| Validation |
Invalid mesh/material handle -> warning + no-op. transforms must be a non-empty array. Invalid transform fields use the same per-field fallbacks as drawMesh. |
| Error behavior |
Never throws for validation failures; warning + no-op/fallback. |
2.3 `aura.draw3d.drawSkybox(path)`
aura.draw3d.drawSkybox(path: string): void
| Property |
Value |
| Description |
Set or draw skybox from path (cubemap directory or equirect source per renderer support). |
| Validation |
Non-string/empty path -> warning + no-op. |
| Error behavior |
Missing/invalid asset path -> throws Error("Asset not found: <path>"). |
2.4 `aura.draw3d.clear3d(color?)`
aura.draw3d.clear3d(color?: Color): void
| Property |
Value |
| Description |
Clear 3D background/depth for the frame before mesh draws. |
| Validation |
Missing/invalid color -> warning, fallback to default { r: 0.08, g: 0.08, b: 0.12, a: 1.0 }. |
| Error behavior |
Never throws on invalid color input. |
2.4a `aura.draw3d.setFog(options)`
aura.draw3d.setFog(options: {
mode: "linear" | "exp" | "exp2";
color?: Color | ColorRgb;
near?: number;
far?: number;
density?: number;
atmosphere?: {
enabled?: boolean;
baseY?: number;
falloff?: number;
rayStrength?: number;
rayDecay?: number;
rayExposure?: number;
shaftSource?: {
screenUv: {
x: number;
y: number;
};
} | null;
excludeStencilRef?: number | null;
localZone?: {
enabled?: boolean;
x?: number;
y?: number;
z?: number;
radius?: number;
height?: number;
strength?: number;
softness?: number;
};
localZones?: Array<{
enabled?: boolean;
x?: number;
y?: number;
z?: number;
radius?: number;
height?: number;
strength?: number;
softness?: number;
}>;
localVolumes?: Array<{
enabled?: boolean;
x?: number;
y?: number;
z?: number;
radius?: number;
height?: number;
strength?: number;
softness?: number;
}>;
};
}): void
| Property |
Value |
| Description |
Configure retained global fog for the forward 3D pass. |
| Validation |
options must be an object. mode is required and must be one of "linear", "exp", or "exp2". near clamps to >= 0. For "linear", far is sanitized to stay strictly greater than near. density clamps to >= 0. When atmosphere is present, falloff clamps to >= 0.1, rayStrength clamps to [0, 1], rayDecay clamps to [0.05, 0.99], and rayExposure clamps to [0, 1]. shaftSource, when provided, must be an object that currently contains screenUv, and screenUv.x / screenUv.y must be finite; authored values clamp to [0, 1]. excludeStencilRef, when provided, must be an integer in range 1..255; null clears it. atmosphere.localZone, atmosphere.localZones, and atmosphere.localVolumes are mutually exclusive. localVolumes is the preferred bounded local fog-volume surface; legacy localZone and localZones remain valid compatibility aliases. localZones and localVolumes, when provided, must be arrays of at most two entries. An empty array clears all local volumes while leaving the global Atmosphere V1 overlay enabled. Enabled entries require x and z. Both array entries may author y, height, and softness; when a later entry omits those fields, it inherits them from the primary entry for compatibility. |
| Truth note |
atmosphere enables one bounded Atmosphere V1 overlay on top of retained fog: height-aware fog plus one renderer-owned screen-space shaft lane. That shaft lane uses the current directional light by default, or one authored shaftSource.screenUv anchor. excludeStencilRef only suppresses the fullscreen Atmosphere V1 pass where matching materials wrote stencil in the main swapchain 3D pass; it does not remove the base forward fog term, extend to render targets, or imply general per-object volumetric masking. The lane remains perspective-camera-only, global, and full-frame. Local depth is capped to one legacy localZone or one bounded two-entry localVolumes / localZones stack of cylindrical fog volumes. It does not promise arbitrary volume counts or shapes, true volumetric scattering, cube-camera capture, or render-target-capture support. |
| Error behavior |
Invalid input warns and leaves the current retained fog state unchanged. Never throws. Unsupported local-volume shapes warn with explicit reason tags, including [reason:atmosphere_local_zone_shape_conflict], [reason:atmosphere_local_zones_cap_exceeded], and [reason:atmosphere_primary_zone_required]. Invalid shaft or exclusion inputs also warn with [reason:invalid_atmosphere_shaft_source] and [reason:invalid_atmosphere_exclusion_stencil_ref]. |
2.4b `aura.draw3d.clearFog()`
aura.draw3d.clearFog(): void
| Property |
Value |
| Description |
Clear the retained global fog configuration and any coupled Atmosphere V1 overlay. |
| Error behavior |
Never throws. |
2.4c `aura.draw3d.setAtmosphereProfile(profileOrNull, overrides?)`
aura.draw3d.setAtmosphereProfile(
profileOrNull: "neon-night" | "storm-haze" | null,
overrides?: {
skyGradient?: {
zenith?: Color | ColorRgb;
horizon?: Color | ColorRgb;
ground?: Color | ColorRgb;
skyExponent?: number;
groundExponent?: number;
};
mode?: "linear" | "exp" | "exp2";
color?: Color | ColorRgb;
near?: number;
far?: number;
density?: number;
atmosphere?: {
enabled?: boolean;
baseY?: number;
falloff?: number;
rayStrength?: number;
rayDecay?: number;
rayExposure?: number;
shaftSource?: {
screenUv: {
x: number;
y: number;
};
} | null;
excludeStencilRef?: number | null;
localZone?: {
enabled?: boolean;
x?: number;
y?: number;
z?: number;
radius?: number;
height?: number;
strength?: number;
softness?: number;
};
localZones?: Array<{
enabled?: boolean;
x?: number;
y?: number;
z?: number;
radius?: number;
height?: number;
strength?: number;
softness?: number;
}>;
localVolumes?: Array<{
enabled?: boolean;
x?: number;
y?: number;
z?: number;
radius?: number;
height?: number;
strength?: number;
softness?: number;
}>;
};
}
): void
| Property |
Value |
| Description |
Apply one bounded authored atmosphere preset over the existing retained sky-gradient, fog, and Atmosphere V1 overlay lane. Built-in presets are "neon-night" and "storm-haze". Passing null clears the profile-owned sky gradient, retained fog, and coupled Atmosphere V1 overlay together. |
| Validation |
profileOrNull must be one of the built-in profile names or null. overrides, when provided, must be an object. skyGradient, when provided, must be an object. mode accepts only "linear", "exp", or "exp2". near clamps to >= 0. For "linear", far is sanitized to stay strictly greater than near. density clamps to >= 0. skyExponent and groundExponent clamp to >= 0.01. When atmosphere.enabled is false, the profile keeps the retained sky/fog result but disables the coupled Atmosphere V1 overlay. When enabled, falloff clamps to >= 0.1, rayStrength clamps to [0, 1], rayDecay clamps to [0.05, 0.99], and rayExposure clamps to [0, 1]. shaftSource, when provided, must be an object that currently contains screenUv, and screenUv.x / screenUv.y must be finite; authored values clamp to [0, 1]. excludeStencilRef, when provided, must be an integer in range 1..255; null clears it. atmosphere.localZone, atmosphere.localZones, and atmosphere.localVolumes are mutually exclusive. localVolumes is the preferred bounded local fog-volume surface; legacy localZone and localZones remain valid compatibility aliases. localZones and localVolumes, when provided, must be arrays of at most two entries. Enabled entries require x and z. Both array entries may author y, height, and softness; when a later entry omits those fields, it inherits them from the primary entry for compatibility. |
| Truth note |
This is a composition helper over existing retained state, not a new volumetric renderer. It drives only procedural sky-gradient state, retained global fog, and the bounded Atmosphere V1 full-frame shaft overlay. That shaft lane uses the current directional light by default, or one authored shaftSource.screenUv anchor. excludeStencilRef only suppresses the fullscreen Atmosphere V1 pass where matching materials wrote stencil in the main swapchain 3D pass; it does not remove the base forward fog term, extend to render targets, or imply general per-object volumetric masking. Local depth is capped to one legacy localZone or one bounded two-entry localVolumes / localZones stack of cylindrical fog volumes. It does not imply arbitrary local fog graphs, skybox asset presets, cube-camera capture support, or render-target capture support. |
| Error behavior |
Invalid input warns and leaves the current retained atmosphere-related state unchanged. Never throws. |
2.4d `aura.atmosphere.setVolumetricFog(options)`
aura.atmosphere.setVolumetricFog(options: {
density?: number;
color?: [number, number, number] | ColorRgb;
heightFalloff?: number;
scatterIntensity?: number;
maxDistance?: number;
quality?: "low" | "medium" | "high";
medium: {
x: number;
y?: number;
z: number;
radius?: number;
height?: number;
softness?: number;
};
}): void
| Property |
Value |
| Description |
Configure one bounded renderer-owned volumetric medium over the retained Stage 204 atmosphere floor. |
| Validation |
options must be an object. density clamps to >= 0. color accepts either [r, g, b] or { r, g, b } and sanitizes each channel to [0, 1]. heightFalloff clamps to >= 0.001. scatterIntensity clamps to [0, 2]. maxDistance clamps to >= 1. quality accepts only "low", "medium", or "high" and maps to step counts 16, 32, and 64. medium is required and must be an object. medium.x and medium.z are required finite numbers. medium.y defaults to 0. medium.radius defaults to 18 and clamps to >= 0.1. medium.height defaults to 12 and clamps to >= 0.1. medium.softness defaults to 0.35 and clamps to [0.05, 0.95]. |
| Truth note |
This is one bounded cylindrical volumetric medium, not arbitrary volumetric authoring. The accepted lane is perspective-camera-only and executes on the main swapchain postfx path, plus one active depth-backed aura.draw3d.setRenderTarget(...) lane and one active cube-camera lane for the internal volumetric pass only. The cube-camera lane follows the existing explicit cube-camera face budget, up to 6 faces per updateCubeCamera(...) wave. It uses one active directional-light coupling when present, plus at most one bounded point-light contribution and one bounded spot-light contribution selected from the active light state against that medium. When the retained directional-shadow lane is active, this same surface also drives one bounded directional shadow-marched shaft floor through that medium. It does not promise multiple point-light or multiple spot-light volumetric contributors, point- or spot-light shadow-marched shafts, area-light volumetric scattering, capture-graph volumetric parity, more than one accepted render-target lane, more than one accepted cube-camera lane, more than 6 accepted cube-camera faces per update, render-target volumetric execution without depth, general render-target or cube-camera postfx stacks, arbitrary medium counts, non-cylindrical medium shapes, general per-object volumetric masking, or editor/workbench-style atmosphere-graph authoring beyond the retained createAtmosphereGraph(...) owner. |
| Error behavior |
Invalid input warns and leaves the current retained volumetric-medium state unchanged. Never throws. Invalid medium input warns with explicit reason tags including [reason:invalid_volumetric_fog_options] and [reason:invalid_volumetric_fog_medium]. |
2.4e `aura.atmosphere.clearVolumetricFog()`
aura.atmosphere.clearVolumetricFog(): void
| Property |
Value |
| Description |
Clear the bounded renderer-owned volumetric medium state configured by aura.atmosphere.setVolumetricFog(...). |
| Error behavior |
Never throws. |
2.5 `aura.draw3d.billboard(textureHandleOrSource, options)`
aura.draw3d.billboard(
textureHandleOrSource:
| number
| {
dataTextureHandle: number;
},
options: {
x: number;
y: number;
z: number;
width: number;
height: number;
mode?: "face" | "axis";
color?: Color;
frameX?: number;
frameY?: number;
frameW?: number;
frameH?: number;
atlasWidth?: number;
atlasHeight?: number;
}
): void
| Property |
Value |
| Description |
Queue a camera-facing billboard quad. Existing numeric handles remain the material/video lane. Procedural data textures use the bridge object form { dataTextureHandle } so overlapping handle spaces stay unambiguous. |
| Validation |
textureHandleOrSource must be a positive integer or an object with positive-integer dataTextureHandle. options must be an object. x, y, z, width, and height must be finite, with width > 0 and height > 0. mode accepts "face" or "axis" and otherwise falls back to "face" with a warning. color, when provided, must be color-like or it falls back to opaque white. Atlas-frame selection requires frameX, frameY, frameW, frameH, atlasWidth, and atlasHeight together as finite numbers inside the atlas bounds. |
| Error behavior |
Never throws for validation failures; warning + no-op. |
2.5a `aura.three`
aura.three.getInterop(): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "aura-runtime-owned";
scope: "custom-root-only";
THREE: object | null;
getCamera(): object | null;
borrowRetainedNodeAnchor(nodeId: number): object | null;
borrowImportedSceneAnchor(importId: number): object | null;
borrowImportedSceneSocket(importId: number, socketName: string): object | null;
borrowAuraGeometry(meshHandle: MeshHandle): object | null;
borrowAuraMaterial(materialHandle: MaterialHandle): object | null;
borrowAuraTexture(materialHandle: MaterialHandle): object | null;
createAuraMesh(meshHandle: MeshHandle, materialHandle: MaterialHandle): object | null;
add(object3d: object): boolean;
remove(object3d: object): boolean;
clear(): number;
getChildCount(): number;
}
aura.three.getState(): {
namespace: "aura.three";
available: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "aura-runtime-owned";
scope: "custom-root-only";
webOnly: true;
nativeSupported: false;
nativeReasonCode: "aura_web_three_native_unsupported";
customShaderMaterialLaneAvailable: boolean;
customShaderMaterialOwnership: "user-owned-web-only-material";
rawShaderMaterialLaneAvailable: boolean;
rawShaderMaterialOwnership: "user-owned-web-only-raw-material";
customPassLaneAvailable: boolean;
customPassOwnership: "aura-runtime-owned-composer-user-authored-pass";
registeredCustomPassCount: number;
frameHookLaneAvailable: boolean;
frameHookOwnership: "aura-runtime-owned-borrowed-frame-context";
registeredFrameHookCount: number;
}
aura.three.createShaderMaterial(options: {
vertexShader: string;
fragmentShader: string;
uniforms?: Record<string, unknown>;
side?: "front" | "back" | "double";
transparent?: boolean;
depthWrite?: boolean;
depthTest?: boolean;
wireframe?: boolean;
}): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "user-owned-web-only-material";
material: object | null;
}
aura.three.createRawShaderMaterial(options: {
vertexShader: string;
fragmentShader: string;
uniforms?: Record<string, unknown>;
side?: "front" | "back" | "double";
transparent?: boolean;
depthWrite?: boolean;
depthTest?: boolean;
wireframe?: boolean;
}): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "user-owned-web-only-raw-material";
material: object | null;
}
aura.three.registerCustomPass(name: string, options: {
fragmentShader: string;
vertexShader?: string;
uniforms?: Record<string, unknown>;
}): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "aura-runtime-owned-composer-user-authored-pass";
pass: string | null;
}
aura.three.unregisterCustomPass(name: string): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "aura-runtime-owned-composer-user-authored-pass";
pass: string | null;
}
aura.three.registerFrameHook(name: string, callback: (context: {
THREE: object | null;
renderer: object | null;
scene: object | null;
camera: object | null;
interopRoot: object | null;
phase: "before-present";
postfxActive: boolean;
activePostfxPasses: string[];
frameIndex: number;
}) => void): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "aura-runtime-owned-borrowed-frame-context";
hook: string | null;
}
aura.three.unregisterFrameHook(name: string): {
ok: boolean;
backend: string | null;
requestedBackend: string | null;
reasonCode: string | null;
ownership: "aura-runtime-owned-borrowed-frame-context";
hook: string | null;
}
| Property |
Value |
| Description |
Expose one explicit browser-only namespace for advanced users who intentionally need direct Three / WebGL behavior on web while Aura keeps runtime ownership. aura.three.getInterop() returns the bounded custom-root seam. On active threejs, the returned surface exposes the vendored THREE namespace, one read-only getCamera() borrow for the active Aura-owned browser-three camera, bounded borrowRetainedNodeAnchor(...), borrowImportedSceneAnchor(...), and borrowImportedSceneSocket(...) helpers for retained-node follow plus accepted imported-scene anchors/sockets, borrowed Aura-owned borrowAuraGeometry(...) / borrowAuraMaterial(...) / borrowAuraTexture(...) helpers, one convenience createAuraMesh(...) helper that reuses those borrowed Aura-owned resources, plus add(...), remove(...), clear(), and getChildCount() over one Aura-owned custom root. aura.three.getState() exposes machine-readable availability plus web-only/native-non-portable truth, including the bounded custom shader-material lane, the bounded raw shader-material lane, one bounded custom-pass lane, and one bounded borrowed frame-hook lane. aura.three.createShaderMaterial(...) exposes one bounded web-only custom shader-material workflow that returns a user-owned THREE.ShaderMaterial for use under the custom root. aura.three.createRawShaderMaterial(...) exposes one bounded web-only raw GLSL workflow that returns a user-owned THREE.RawShaderMaterial for use under the same custom root. aura.three.registerCustomPass(...) and unregisterCustomPass(...) expose one web-only fullscreen custom-pass seam on the Aura-owned browser-three composer; the canonical pass name is always custom:<name>, and activation still flows through aura.draw3d.setPostFXPass("custom:name", options?). aura.three.registerFrameHook(...) and unregisterFrameHook(...) expose one web-only borrowed frame-context seam that runs immediately before final browser-three presentation, so advanced users can do bounded offscreen WebGL / render-target work without taking ownership of the main scene lifecycle. |
| Validation |
Never throws. When browser-three is not the active backend, when the browser-three bridge is unavailable, or when the browser-three runtime is not yet ready, getInterop() returns ok: false, THREE: null, getCamera(): null, anchor/resource helpers return null, createAuraMesh(...) returns null, other helper methods become deterministic no-ops, and reasonCode stays explicit. getState() returns the same backend, requestedBackend, reasonCode, ownership, and scope truth, plus webOnly: true, nativeSupported: false, nativeReasonCode: "aura_web_three_native_unsupported", customShaderMaterialLaneAvailable, customShaderMaterialOwnership, rawShaderMaterialLaneAvailable, rawShaderMaterialOwnership, customPassLaneAvailable, customPassOwnership, registeredCustomPassCount, frameHookLaneAvailable, frameHookOwnership, and registeredFrameHookCount. createShaderMaterial(...) and createRawShaderMaterial(...) both require object options plus non-empty string vertexShader and fragmentShader. uniforms, when provided, must be an object. side, when provided, must be "front", "back", or "double". transparent, depthWrite, depthTest, and wireframe, when provided, must be booleans. registerCustomPass(...) requires a symbol-like pass name and a non-empty fragmentShader; vertexShader is optional and otherwise defaults to the bounded fullscreen-pass vertex shader. unregisterCustomPass(...) requires a symbol-like pass name. registerFrameHook(...) requires a symbol-like hook name and a callback function. unregisterFrameHook(...) requires a symbol-like hook name. borrowRetainedNodeAnchor(...) requires a valid positive scene node id. borrowImportedSceneAnchor(...) requires a valid positive accepted imported-scene id and currently returns a borrowed anchor only when that imported scene exposes one accepted root node. borrowImportedSceneSocket(...) requires a valid positive accepted imported-scene id plus one accepted socket name. borrowAuraGeometry(...) requires a valid mesh handle. borrowAuraMaterial(...) / borrowAuraTexture(...) require a valid material handle. add(...) / remove(...) expect object-like Three objects; invalid values return false. |
| Error behavior |
Stable interop reason codes include browser3d_threejs_interop_unavailable when active backend truth does not permit the seam and browser3d_threejs_interop_runtime_unavailable when threejs is active but runtime ownership is not ready yet. Existing browser-three initialization failure reason codes may also surface through reasonCode. getState().nativeReasonCode is always aura_web_three_native_unsupported. createShaderMaterial(...) returns aura_web_three_shader_material_invalid_options for validation failures and aura_web_three_shader_material_create_failed if Three rejects the bounded shader options at creation time. createRawShaderMaterial(...) returns aura_web_three_raw_shader_material_invalid_options for validation failures and aura_web_three_raw_shader_material_create_failed if Three rejects the bounded raw shader options at creation time. registerCustomPass(...) returns aura_web_three_custom_pass_invalid_name or aura_web_three_custom_pass_invalid_options for validation failures; availability truth is surfaced through reasonCode, including browser3d_threejs_postfx_unavailable when the browser-three postfx lane is unavailable. unregisterCustomPass(...) returns aura_web_three_custom_pass_invalid_name for validation failures and aura_web_three_custom_pass_missing when the canonical custom pass was not registered. registerFrameHook(...) returns aura_web_three_frame_hook_invalid_name or aura_web_three_frame_hook_invalid_callback for validation failures; availability truth is surfaced through reasonCode, including browser3d_threejs_interop_unavailable when the browser-three runtime cannot lend the frame context. unregisterFrameHook(...) returns aura_web_three_frame_hook_invalid_name for validation failures and aura_web_three_frame_hook_missing when the canonical hook was not registered. Runtime telemetry uses browser3d_threejs_frame_hook_unavailable when borrowed frame execution cannot run and browser3d_threejs_frame_hook_callback_failed when a registered callback throws. When aura.draw3d.setPostFXPass("custom:name", ...) targets an unregistered web-only custom pass, it returns browser3d_threejs_postfx_custom_pass_missing. |
| Truth note |
aura.three is a web-only escape hatch, not portable Aura gameplay API surface and not part of the native/mobile portability contract. The bounded shader-material lane is also web-only and user-owned: Aura still owns backend selection, canvas/stage lifecycle, render loop, camera lifecycle, resource lifecycle, retained scene submission, and the accepted bounded postFX/culling/imported-scene lanes, while the returned ShaderMaterial belongs to the caller for use under the custom root. The bounded raw shader-material lane is also web-only and user-owned: it exposes more literal GLSL ownership through THREE.RawShaderMaterial, but it remains limited to caller-owned materials under the Aura-owned custom root and does not widen into raw scene ownership or native portability. The bounded custom-pass lane is also web-only but stays Aura-owned at execution time: the caller authors and registers one fullscreen pass shader, while Aura still owns composer order, swapchain execution, and final screen presentation. The bounded frame-hook lane is also web-only and borrowed: the caller receives a borrowed { THREE, renderer, scene, camera, interopRoot } context immediately before final presentation, and Aura automatically restores the active render target after each callback so the main scene can continue rendering. That lane exists so advanced users can do bounded offscreen WebGL / render-target work, not so they can replace the main scene lifecycle, composer ownership, or native portability contract. This remains a bounded custom-root seam plus bounded anchor/socket/resource borrows plus bounded ShaderMaterial and RawShaderMaterial lanes plus one composer-owned fullscreen custom-pass lane plus one borrowed frame-hook lane, not raw renderer replacement, not direct scene ownership, not arbitrary imported-scene traversal or bone ownership, not React Three Fiber parity, and not arbitrary direct-Three app compatibility. |
2.6 `aura.draw3d.setPostFXPass(pass, options?)`
aura.draw3d.setPostFXPass(
pass: string,
options?: {
enabled?: boolean;
strength?: number;
radius?: number;
threshold?: number;
customParams?: Record<string, number>;
targetChain?: {
intermediateTargets?: string[];
pingPong?: boolean;
composeMode?: "replace" | "additive" | "multiply";
};
}
): {
ok: boolean;
operation: "setPostFXPass";
reasonCode: string;
pass: string | null;
}
| Property |
Value |
| Description |
Add or update one deterministic postfx pass in the retained composer state when no retained postfx graph currently owns the composer lane. |
| Validation |
pass must be a supported builtin pass name or custom:<symbol>. options, when provided, must be an object. enabled must be boolean. strength, radius, and threshold must be finite numbers and are clamped to deterministic per-pass ranges. customParams must be an object with symbol-like keys and finite numeric values. targetChain, when provided, must be an object with string intermediateTargets, boolean pingPong, and compose mode in the supported set. |
| Error behavior |
Never throws for validation failures; returns { ok: false, reasonCode, pass }. Stable reason codes include postfx_invalid_pass_name, postfx_pass_unsupported, postfx_invalid_options, postfx_invalid_target_chain, postfx_invalid_intermediate_target, postfx_invalid_compose_mode, postfx_invalid_ping_pong, postfx_invalid_custom_params, and postfx_custom_param_unsupported. postfx_invalid_options also covers the bounded Stage 213 mixed-ownership rejection when a retained postfx graph already owns the composer lane. |
2.7 `aura.draw3d.setPostFXEnabled(pass, enabled)`
aura.draw3d.setPostFXEnabled(
pass: string,
enabled: boolean
): {
ok: boolean;
operation: "setPostFXEnabled";
reasonCode: string;
pass: string | null;
}
| Property |
Value |
| Description |
Toggle one previously configured postfx pass on or off without removing it from retained composer state, as long as no retained postfx graph currently owns the composer lane. |
| Validation |
pass must resolve to a supported builtin pass or custom:<symbol>. enabled must be a boolean. |
| Error behavior |
Never throws for validation failures; returns { ok: false, reasonCode, pass }. Stable reason codes include postfx_invalid_pass_name, postfx_pass_unsupported, postfx_invalid_enabled, postfx_pass_missing, and postfx_invalid_options when retained postfx graph ownership is active. |
2.8 `aura.draw3d.removePostFXPass(pass)`
aura.draw3d.removePostFXPass(pass: string): {
ok: boolean;
operation: "removePostFXPass";
reasonCode: string;
pass: string | null;
}
| Property |
Value |
| Description |
Remove one configured postfx pass from retained composer state, as long as no retained postfx graph currently owns the composer lane. |
| Validation |
pass must resolve to a supported builtin pass or custom:<symbol>. |
| Error behavior |
Never throws for validation failures; returns { ok: false, reasonCode, pass }. Stable reason codes include postfx_invalid_pass_name, postfx_pass_unsupported, postfx_pass_missing, and postfx_invalid_options when retained postfx graph ownership is active. |
2.9 `aura.draw3d.getPostFXState()`
aura.draw3d.getPostFXState(): {
passes: Array<{
pass: string;
enabled: boolean;
strength: number;
radius: number;
threshold: number;
customParams: Record<string, number>;
index: number;
isCustom: boolean;
}>;
resolvedSteps: Array<{
pass: string;
targetSlot: string | null;
pingPongPhase: boolean;
usesShaderPipeline: boolean;
inputMatchesOriginalScene: boolean;
index: number;
}>;
targetChain: {
intermediateTargets: string[];
intermediateTargetCount: number;
pingPong: boolean;
composeMode: "replace" | "additive" | "multiply";
};
customShaderBindings: Array<{
binding: number;
name: string;
type: string;
semantic: string;
}>;
customShaderContract: {
deterministicOrder: boolean;
supportsDepthTexture: boolean;
supportsOriginalSceneTexture: boolean;
supportsCameraInfoInUniform: boolean;
supportsNormalBuffer: boolean;
supportsIntermediateBufferReads: boolean;
customParamSlots: number;
inputTextureSemantic: string;
originalSceneSemantic: string;
orderingSemantic: string;
};
totalPasses: number;
enabledPasses: number;
mutationCount: number;
orderFingerprint: number;
targetChainFingerprint: number;
customPassCount: number;
customParamFingerprint: number;
lastOperation: string;
lastReasonCode: string;
lastOk: boolean;
}
| Property |
Value |
| Description |
Return deterministic retained composer telemetry for tools, inspectors, and runtime assertions. |
| Truth note |
resolvedSteps reflects the deterministic runtime execution order, including target-chain expansion. customShaderBindings and customShaderContract describe the public custom-postfx input surface. When a retained postfx graph is active, this getter reports the currently applied graph-owned composer state rather than an independently editable low-level pass stack. |
| Scope note |
input_texture is the current scene color immediately before a custom pass. original_scene_texture is a stable scene-color copy from before the postfx chain began. depth_texture is the current swapchain 3D depth surface. Normal-buffer / G-buffer reads and arbitrary intermediate-buffer reads remain unsupported. |
2.10 `aura.draw3d.registerPostFXShader(name, wgslSource)`
aura.draw3d.registerPostFXShader(
name: string,
wgslSource: string
): {
ok: boolean;
name: string;
error?: string;
}
| Property |
Value |
| Description |
Queue a custom WGSL postfx shader for renderer compilation. The canonical custom pass name is always custom:<name>. Re-registering replaces the pending/compiled shader for that canonical name. |
| Validation |
name must be a non-empty symbol-like string not reserved by builtin passes. wgslSource must be a non-empty string and contain fn fs_main. |
| Error behavior |
Validation failures do not throw; they return { ok: false, name, error }. Native compilation happens later in the renderer and is surfaced through runtime compile-result tracking rather than synchronously from registration. |
2.11 PostFX custom-shader input contract
aura.draw3d.registerPostFXShader(name, wgslSource) and
aura.draw3d.setPostFXPass("custom:name", options?) now rely on one explicit
custom-shader input surface:
input_texture is the current scene color immediately before this custom
pass
original_scene_texture is a stable scene-color copy from before the postfx
chain started
depth_texture is the current swapchain 3D depth texture
u_postfx includes resolution, texel size, time, 8 custom param slots,
camera near/far, projection-mode flag, and step/input-contract flags
Deterministic truth constraints:
aura.draw3d.getPostFXState() exposes resolvedSteps,
customShaderBindings, and customShaderContract so tooling can inspect the
actual runtime ordering and binding map
- Stage 213's retained postfx-graph owner reuses this same custom-shader input
contract rather than widening the shader-binding surface
- normal-buffer / G-buffer reads remain unsupported
- arbitrary intermediate-buffer reads remain unsupported
- custom shaders are still single-pass effects within the existing deterministic
postfx chain or one bounded retained postfx-graph chain rather than a full
user-defined render graph
Supplementary contract note:
- Stage 213 freezes the bounded follow-on graph/chaining surface in
docs/custom-postfx-graph-contract-v1.md
and now ships that bounded retained runtime owner on top of the same custom
shader floor described above
2.11a `aura.draw3d.createPostFXGraph(options)`
aura.draw3d.createPostFXGraph(options: {
enabled?: boolean;
passes: Array<{
pass: string;
enabled?: boolean;
strength?: number;
radius?: number;
threshold?: number;
customParams?: Record<string, number>;
}>;
targetChain?: {
intermediateTargets?: string[];
pingPong?: boolean;
composeMode?: "replace" | "additive" | "multiply";
};
}): {
ok: boolean;
type: "postFXGraph";
handle: number;
passCount: number;
enabledPassCount: number;
customPassCount: number;
enabled: boolean;
}
| Property |
Value |
| Description |
Create one retained postfx-graph owner over the already-shipped postfx composer floor. The graph owns one bounded ordered pass chain plus one shared target-chain descriptor for that graph. |
| Validation |
options must be an object. passes is required and must contain between 1 and 4 entries. Each pass entry follows the same bounded validation rules as setPostFXPass(...), except per-pass targetChain overrides are rejected in v1. targetChain, when provided, follows the same validation rules as setPostFXPass(...) and may contain at most 2 intermediate targets. enabled, when provided, must be a boolean. |
| Truth note |
This is a retained graph owner, not a general render graph. Pass order inside the graph preserves the authored array order. While any retained postfx graph exists, low-level composer mutation through setPostFXPass(...), setPostFXEnabled(...), and removePostFXPass(...) is outside the accepted mixed-ownership lane and currently rejects with postfx_invalid_options. |
| Error behavior |
Invalid input warns and no-ops. Never throws. |
2.11b `aura.draw3d.updatePostFXGraph(graph, patch?)`
aura.draw3d.updatePostFXGraph(
graph: number | { type: "postFXGraph"; handle: number },
patch?: {
enabled?: boolean;
passes?: Array<{
pass: string;
enabled?: boolean;
strength?: number;
radius?: number;
threshold?: number;
customParams?: Record<string, number>;
}>;
targetChain?: {
intermediateTargets?: string[];
pingPong?: boolean;
composeMode?: "replace" | "additive" | "multiply";
} | null;
}
): void
| Property |
Value |
| Description |
Update one retained postfx graph by replacing its ordered pass chain, replacing its shared target-chain descriptor, toggling whether that graph is currently enabled, or any combination of those patches. |
| Validation |
graph must be a positive handle or an object with type: "postFXGraph" plus a positive-integer .handle. patch, when provided, must be an object. passes, when provided, follows the same bounded rules as createPostFXGraph(...). targetChain, when provided, follows the same bounded rules as createPostFXGraph(...); explicit null clears that graph back to the default empty target-chain descriptor. enabled, when provided, must be a boolean. |
| Truth note |
Updating a graph moves it to the end of retained route order, so last enabled graph ownership wins deterministically. The active graph's authored pass order is preserved, but the runtime lane still remains a single linear chain with one shared target-chain descriptor per graph. |
| Error behavior |
Invalid or unknown handles warn and leave current graph ownership unchanged. Never throws. |
2.11c `aura.draw3d.destroyPostFXGraph(graph)`
aura.draw3d.destroyPostFXGraph(
graph: number | { type: "postFXGraph"; handle: number }
): void
| Property |
Value |
| Description |
Destroy one retained postfx graph and remove the graph-owned composer state it currently contributes. |
| Validation |
graph must be a positive handle or an object with type: "postFXGraph" plus a positive-integer .handle. |
| Truth note |
Destroying the last graph clears the graph-owned postfx chain instead of restoring any older low-level manual composer stack. This is part of the bounded Stage 213 exclusive-owner rule rather than a previous-graph fallback system. |
| Error behavior |
Invalid or unknown handles warn and no-op. Never throws. |
2.11d `aura.draw3d.getPostFXGraphState(graph?)`
aura.draw3d.getPostFXGraphState(): {
graphCount: number;
enabledGraphCount: number;
routeOrder: number[];
enabledRouteOrder: number[];
resolutionMode: "lastEnabledWins";
fallbackMode: "clear";
activeGraphHandle: number | null;
}
aura.draw3d.getPostFXGraphState(
graph: number | { type: "postFXGraph"; handle: number }
): {
ok: boolean;
type: "postFXGraph";
handle: number;
enabled: boolean;
routeIndex: number;
enabledRouteIndex: number | null;
active: boolean;
passCount: number;
enabledPassCount: number;
customPassCount: number;
targetChain: {
intermediateTargets: string[];
intermediateTargetCount: number;
pingPong: boolean;
composeMode: "replace" | "additive" | "multiply";
};
orderFingerprint: number;
targetChainFingerprint: number;
} | null
| Property |
Value |
| Description |
Return truthful inspection state for the retained postfx-graph lane. Without arguments, this exposes overall route order, enabled route order, active graph ownership, and the current fallback mode. With a graph handle, it exposes that graph's enabled state, route position, ordered pass-count summary, and shared target-chain descriptor. |
| Validation |
Omitting graph, or passing null / undefined, returns the overall retained postfx-graph summary. When provided, graph must be a positive handle or an object with type: "postFXGraph" plus a positive-integer .handle. Invalid or unknown handles return null and warn. |
| Truth note |
This getter is inspection-only. routeOrder is the retained graph order from oldest to newest after updates reorder a graph to the end. enabledRouteOrder filters that list to only enabled graphs. resolutionMode is currently fixed to "lastEnabledWins". fallbackMode is currently fixed to "clear", which means the active graph does not fall back to earlier graph-owned target-chain or pass state when ownership changes. Use getPostFXState() for the currently applied active-graph composer telemetry. |
| Error behavior |
Never throws. Invalid or unknown handles warn and return null. |
2.12 `aura.draw3d.createReflectionProbe(options?)`
aura.draw3d.createReflectionProbe(options?: {
position?: Vec3;
near?: number;
far?: number;
resolution?: number;
facesPerFrame?: number;
materials?: MaterialHandle[];
useAsEnvironmentMap?: boolean;
}): {
ok: boolean;
type: "reflectionProbe";
handle: number;
cubeCameraHandle: CubeCameraHandle;
resolution: number;
near: number;
far: number;
facesPerFrame: number;
materialCount: number;
useAsEnvironmentMap: boolean;
}
| Property |
Value |
| Description |
Create one bounded reflection-probe ownership wrapper over the existing cube-camera and environment-map floor. The probe owns one cube camera plus an optional material list and an optional shared-scene environment assignment. |
| Validation |
options, when provided, must be an object. position, when provided, must be a Vec3. near and far must be finite and > 0 to override defaults. resolution accepts finite values in [1, 4096]. facesPerFrame accepts finite values in [1, 6]. materials, when provided, must be an array of positive material handles. useAsEnvironmentMap, when provided, must be a boolean. |
| Truth note |
This is an ownership helper, not a new reflection renderer. Internally it still uses one cube camera, the existing shared setEnvironmentMap(cubeCamera) lane, and the existing per-material setCubeMapTexture(...) lane. It does not imply automatic update scheduling, reflection-probe blending, probe volumes, skinned-mesh parity, or universal capture-path support. |
| Error behavior |
Invalid input warns and no-ops. Never throws. |
2.13 `aura.draw3d.updateReflectionProbe(probe, patch?)`
aura.draw3d.updateReflectionProbe(
probe: number | { handle: number },
patch?: {
position?: Vec3;
materials?: MaterialHandle[];
useAsEnvironmentMap?: boolean;
}
): void
| Property |
Value |
| Description |
Mark the owned probe capture dirty and optionally replace its owned material list, move the underlying cube camera, or toggle whether it owns the shared scene environment assignment. |
| Validation |
probe must be a positive handle or an object with positive-integer .handle. patch, when provided, must be an object. position, when provided, must be a Vec3. materials, when provided, must be an array of positive material handles and replaces the previous owned list rather than merging with it. useAsEnvironmentMap, when provided, must be a boolean. |
| Truth note |
Calling updateReflectionProbe(...) is still the explicit refresh trigger. It does not make reflections automatic every frame. Rebinding materials or toggling useAsEnvironmentMap only re-routes the already-shipped underlying cube-camera consumers. |
| Error behavior |
Invalid input warns and leaves current probe ownership unchanged. Never throws. |
2.14 `aura.draw3d.destroyReflectionProbe(probe)`
aura.draw3d.destroyReflectionProbe(
probe: number | { handle: number }
): void
| Property |
Value |
| Description |
Destroy the owned reflection probe, clear its owned material bindings, clear the shared scene environment assignment if it currently owns it, and destroy the underlying cube camera. |
| Validation |
probe must be a positive handle or an object with positive-integer .handle. |
| Truth note |
Destroying a probe clears only the ownership it created. It does not imply generalized reflection fallback, probe blending, or automatic reassignment to another probe. |
| Error behavior |
Invalid or unknown handles warn and no-op. Never throws. |
2.15 `aura.draw3d.createReflectionGraph(options?)`
aura.draw3d.createReflectionGraph(options?: {
routes?: Array<{
materials: MaterialHandle[];
source?: ReflectionGraphSource | ReflectionGraphSource[];
sources?: ReflectionGraphSource[];
}>;
environment?: ReflectionGraphEnvironmentSource | ReflectionGraphEnvironmentSource[];
environmentMap?: ReflectionGraphEnvironmentSource | ReflectionGraphEnvironmentSource[];
enabled?: boolean;
}): {
ok: boolean;
type: "reflectionGraph";
handle: number;
routeCount: number;
materialCount: number;
environmentSourceCount: number;
enabled: boolean;
}
Where:
type ReflectionGraphSource =
| { type: "cubeCamera"; handle: CubeCameraHandle }
| { type: "reflectionProbe"; handle: number }
| { cubeCamera: CubeCameraHandle | { type: "cubeCamera"; handle: CubeCameraHandle } }
| { reflectionProbe: number | { type: "reflectionProbe"; handle: number } }
| { probe: number | { type: "reflectionProbe"; handle: number } }
| { environmentMap: true }
| null;
type ReflectionGraphEnvironmentSource =
| string
| { path: string }
| { type: "cubeCamera"; handle: CubeCameraHandle }
| { type: "reflectionProbe"; handle: number }
| { cubeCamera: CubeCameraHandle | { type: "cubeCamera"; handle: CubeCameraHandle } }
| { reflectionProbe: number | { type: "reflectionProbe"; handle: number } }
| { probe: number | { type: "reflectionProbe"; handle: number } };
| Property |
Value |
| Description |
Create one retained reflection-graph owner over the already-shipped reflection-probe, cube-camera, per-material cubemap, and shared environment-map floor. Each route owns one material list plus an ordered source list; the graph also owns one ordered environment-source list. |
| Validation |
options, when provided, must be an object. routes, when provided, must be an array of route objects with a non-empty materials array and a source or sources entry. Material handles must be positive integers. Material sources accept typed cube-camera or reflection-probe handles, { cubeCamera }, { reflectionProbe }, { probe }, { environmentMap: true }, or null for the shared environment fallback. environment/environmentMap, when provided, must be a path, a typed cube-camera/probe handle, or an array of those sources. enabled, when provided, must be a boolean. |
| Truth note |
This is a retained ownership helper, not a new reflection renderer. Internally it still re-routes the already-shipped cube-camera, reflection-probe, per-material cubemap, and shared setEnvironmentMap(...) seams. Ordered source lists are explicit fallback, not automatic probe blending, probe volumes, editor ownership, or universal capture-path support. |
| Error behavior |
Invalid input warns and no-ops. Never throws. |
2.16 `aura.draw3d.updateReflectionGraph(graph, patch?)`
aura.draw3d.updateReflectionGraph(
graph: number | { type: "reflectionGraph"; handle: number },
patch?: {
routes?: Array<{
materials: MaterialHandle[];
source?: ReflectionGraphSource | ReflectionGraphSource[];
sources?: ReflectionGraphSource[];
}>;
environment?: ReflectionGraphEnvironmentSource | ReflectionGraphEnvironmentSource[];
environmentMap?: ReflectionGraphEnvironmentSource | ReflectionGraphEnvironmentSource[];
enabled?: boolean;
}
): void
| Property |
Value |
| Description |
Update one retained reflection graph by replacing its owned route list, replacing its owned environment-source list, toggling whether the graph is currently active, or any combination of those patches. |
| Validation |
graph must be a positive handle or an object with type: "reflectionGraph" plus a positive-integer .handle. patch, when provided, must be an object. routes and environment/environmentMap follow the same validation rules as createReflectionGraph(...). enabled, when provided, must be a boolean. |
| Truth note |
enabled: false keeps the retained graph definition but drops its current ownership of per-material cubemap bindings and shared environment assignment until re-enabled. Updating a graph reorders ownership over the same already-shipped reflection consumers; it does not imply automatic reflection scheduling, editor semantics, or new reflection sources. |
| Error behavior |
Invalid input warns and leaves current graph ownership unchanged. Never throws. |
2.17 `aura.draw3d.destroyReflectionGraph(graph)`
aura.draw3d.destroyReflectionGraph(
graph: number | { type: "reflectionGraph"; handle: number }
): void
| Property |
Value |
| Description |
Destroy the owned reflection graph and clear only the per-material cubemap bindings and shared environment assignment that the graph currently owns. |
| Validation |
graph must be a positive handle or an object with type: "reflectionGraph" plus a positive-integer .handle. |
| Truth note |
Destroying a graph clears only the ownership it created. It does not imply generalized fallback reassignment, automatic probe blending, probe volumes, or editor ownership. |
| Error behavior |
Invalid or unknown handles warn and no-op. Never throws. |
2.18 `aura.draw3d.createCaptureGraph(options?)`
aura.draw3d.createCaptureGraph(options?: {
captures?: CaptureGraphEntry[];
entries?: CaptureGraphEntry[];
enabled?: boolean;
}): {
ok: boolean;
type: "captureGraph";
handle: number;
captureCount: number;
enabled: boolean;
}
Where:
type CaptureGraphTarget = number | { handle: number };
type CaptureGraphEntry = {
target?: CaptureGraphTarget;
renderTarget?: CaptureGraphTarget;
camera?: {
position: Vec3;
target: Vec3;
up?: Vec3;
fovDegrees?: number;
near?: number;
far?: number;
} | null;
enabled?: boolean;
};
| Property |
Value |
| Description |
Create one retained capture-graph owner over the current bounded split-render floor. Each entry owns one offscreen render-target pass, optionally with its own capture-camera override, and mirrors accepted main-scene draws into those targets in explicit order. |
| Validation |
options, when provided, must be an object. captures/entries, when provided, must be an array with at most 4 entry objects. Each entry must provide target or renderTarget as a positive render-target handle or an object with a positive-integer .handle that refers to an existing render target. camera, when provided, follows the same validation rules as setRenderTarget(handle, camera). enabled, when provided at either the graph or entry level, must be a boolean. |
| Truth note |
This is a retained ownership helper, not a general render graph. Internally it still compiles down to the already-shipped split-render pass floor and only mirrors the accepted main-scene capture path: clear3d(...), drawMesh(...), billboard(...), and billboard-backed consumers that submit through that same lane. It does not imply recursive graphs, arbitrary scene-DAG capture, blanket postFX/stencil parity, or that explicit setRenderTarget(...) capture becomes automatic graph ownership. |
| Error behavior |
Invalid input warns and no-ops. Never throws. |
2.19 `aura.draw3d.updateCaptureGraph(graph, patch?)`
aura.draw3d.updateCaptureGraph(
graph: number | { type: "captureGraph"; handle: number },
patch?: {
captures?: CaptureGraphEntry[];
entries?: CaptureGraphEntry[];
enabled?: boolean;
}
): void
| Property |
Value |
| Description |
Update one retained capture graph by replacing its owned capture-entry list, toggling whether the graph is currently active, or both. |
| Validation |
graph must be a positive handle or an object with type: "captureGraph" plus a positive-integer .handle. patch, when provided, must be an object. captures/entries, when provided, follow the same validation rules as createCaptureGraph(...). enabled, when provided, must be a boolean. |
| Truth note |
enabled: false keeps the retained graph definition but stops future main-scene mirroring into the owned offscreen targets until re-enabled. Updating a capture graph only reorders or replaces the already-accepted split-render captures; it does not imply recursive ownership, new renderer scheduling semantics, or broader offscreen feature parity beyond the documented lane. |
| Error behavior |
Invalid input warns and leaves current capture ownership unchanged. Never throws. |
2.20 `aura.draw3d.destroyCaptureGraph(graph)`
aura.draw3d.destroyCaptureGraph(
graph: number | { type: "captureGraph"; handle: number }
): void
| Property |
Value |
| Description |
Destroy the owned capture graph so future accepted main-scene draws stop mirroring into the graph's retained offscreen targets. |
| Validation |
graph must be a positive handle or an object with type: "captureGraph" plus a positive-integer .handle. |
| Truth note |
Destroying a capture graph clears only the retained mirroring ownership it created. It does not destroy the underlying render targets, alter explicit setRenderTarget(...) usage, or imply a general render-graph teardown. |
| Error behavior |
Invalid or unknown handles warn and no-op. Never throws. |
2.20a `aura.draw3d.createAtmosphereGraph(options?)`
aura.draw3d.createAtmosphereGraph(options?: {
profile?: AtmosphereGraphProfileSource;
atmosphereProfile?: AtmosphereGraphProfileSource;
volumetricFog?: AtmosphereGraphVolumetricFog;
enabled?: boolean;
}): {
ok: boolean;
type: "atmosphereGraph";
handle: number;
profileConfigured: boolean;
volumetricFogConfigured: boolean;
enabled: boolean;
}
Where:
type AtmosphereGraphProfileSource =
| "neon-night"
| "storm-haze"
| {
name?: "neon-night" | "storm-haze";
profile?: "neon-night" | "storm-haze";
overrides?: object;
}
| null;
type AtmosphereGraphVolumetricFog =
| {
density?: number;
color?: [number, number, number] | ColorRgb;
heightFalloff?: number;
scatterIntensity?: number;
maxDistance?: number;
quality?: "low" | "medium" | "high";
medium: {
x: number;
y?: number;
z: number;
radius?: number;
height?: number;
softness?: number;
};
}
| null;
| Property |
Value |
| Description |
Create one retained atmosphere-graph owner over the already-shipped setAtmosphereProfile(...) and aura.atmosphere.setVolumetricFog(...) floor. Each graph owns at most one bounded profile source plus one bounded volumetric-medium source, and may be toggled on or off without destroying the retained definition. |
| Validation |
options, when provided, must be an object. profile and atmosphereProfile are aliases. When provided, they accept one built-in profile name, null, or an object with name or profile plus optional overrides; the overrides object follows the same bounded rules accepted by setAtmosphereProfile(profileOrNull, overrides?). volumetricFog, when provided, must be null or an object that follows the same validation rules accepted by aura.atmosphere.setVolumetricFog(...). enabled, when provided, must be a boolean. |
| Truth note |
This is a retained ownership helper, not a new atmosphere renderer or authoring editor. Internally it re-applies the already-shipped profile/local-volume and volumetric-medium seams, and last enabled graph ownership wins. Use getAtmosphereGraphState(...) for explicit route order and fallback inspection. The bounded Stage 210 atmosphere-workbench shell contract still routes through this same retained owner and does not change these renderer semantics. This surface does not imply graph blending, multiple simultaneously active atmosphere graphs, arbitrary fog DAGs, broader capture-path semantics, or a visual-editor graph canvas. |
| Error behavior |
Invalid input warns and no-ops. Never throws. |
2.20b `aura.draw3d.updateAtmosphereGraph(graph, patch?)`
aura.draw3d.updateAtmosphereGraph(
graph: number | { type: "atmosphereGraph"; handle: number },
patch?: {
profile?: AtmosphereGraphProfileSource;
atmosphereProfile?: AtmosphereGraphProfileSource;
volumetricFog?: AtmosphereGraphVolumetricFog;
enabled?: boolean;
}
): void
| Property |
Value |
| Description |
Update one retained atmosphere graph by replacing its owned profile source, replacing its owned volumetric-medium source, toggling whether the graph is currently active, or any combination of those patches. |
| Validation |
graph must be a positive handle or an object with type: "atmosphereGraph" plus a positive-integer .handle. patch, when provided, must be an object. profile and atmosphereProfile are aliases and follow the same rules as createAtmosphereGraph(...); explicit null clears the graph-owned profile binding. volumetricFog, when provided, follows the same rules as createAtmosphereGraph(...); explicit null clears the graph-owned volumetric-medium binding. enabled, when provided, must be a boolean. |
| Truth note |
enabled: false keeps the retained graph definition but drops its current ownership of the shipped atmosphere/profile and volumetric-medium seams until re-enabled. Updating a graph reorders ownership over those same already-shipped surfaces, and the current fallback remains explicit clear instead of "use the previous graph" when the last enabled graph omits one of those surfaces. It does not imply layered graph composition or broader authoring semantics. |
| Error behavior |
Invalid input warns and leaves the current graph ownership unchanged. Never throws. |
2.20c `aura.draw3d.destroyAtmosphereGraph(graph)`
aura.draw3d.destroyAtmosphereGraph(
graph: number | { type: "atmosphereGraph"; handle: number }
): void
| Property |
Value |
| Description |
Destroy the owned atmosphere graph and clear only the profile and volumetric-medium ownership that graph currently holds over the shipped retained atmosphere floor. |
| Validation |
graph must be a positive handle or an object with type: "atmosphereGraph" plus a positive-integer .handle. |
| Truth note |
Destroying a graph clears only the retained ownership it created. It does not clear unrelated manually-authored atmosphere state, create a new fallback graph, or imply editor/workbench teardown semantics. |
| Error behavior |
Invalid or unknown handles warn and no-op. Never throws. |
2.20d `aura.draw3d.getAtmosphereGraphState(graph?)`
aura.draw3d.getAtmosphereGraphState(): {
graphCount: number;
enabledGraphCount: number;
routeOrder: number[];
enabledRouteOrder: number[];
resolutionMode: "lastEnabledWins";
profileFallback: "clear";
volumetricFogFallback: "clear";
activeGraphHandle: number | null;
activeProfileOwnerHandle: number | null;
activeVolumetricFogOwnerHandle: number | null;
}
aura.draw3d.getAtmosphereGraphState(
graph: number | { type: "atmosphereGraph"; handle: number }
): {
ok: boolean;
type: "atmosphereGraph";
handle: number;
enabled: boolean;
routeIndex: number;
enabledRouteIndex: number | null;
active: boolean;
profileConfigured: boolean;
volumetricFogConfigured: boolean;
ownsProfile: boolean;
ownsVolumetricFog: boolean;
resolutionMode: "lastEnabledWins";
profileFallback: "clear";
volumetricFogFallback: "clear";
} | null
| Property |
Value |
| Description |
Return truthful inspection state for the retained atmosphere-graph lane. Without arguments, this exposes overall route order, enabled route order, active ownership, and the current fallback mode. With a graph handle, it exposes that graph's enabled state, route position, and whether it currently owns the profile or volumetric-medium surfaces. |
| Validation |
Omitting graph, or passing null / undefined, returns the overall retained atmosphere-graph summary. When provided, graph must be a positive handle or an object with type: "atmosphereGraph" plus a positive-integer .handle. Invalid or unknown handles return null and warn. |
| Truth note |
This getter is inspection-only. routeOrder is the retained graph order from oldest to newest after updates reorder a graph to the end. enabledRouteOrder filters that list to only enabled graphs. resolutionMode is currently fixed to "lastEnabledWins". Both profileFallback and volumetricFogFallback are currently fixed to "clear", which means if the last enabled graph omits one of those surfaces, ownership clears instead of falling back to an earlier graph. In the bounded Stage 210 atmosphere-workbench shell lane, this getter remains the renderer-owned active-graph truth surface while shell-selected asset state stays outside aura.draw3d in the separate authored shell contract. It does not expose browser-local draft state or general editor-shell state. |
| Error behavior |
Never throws. Invalid or unknown handles warn and return null. |
3) `aura.camera3d`
3.1 `aura.camera3d.perspective(fovDeg, near, far)`
aura.camera3d.perspective(fovDeg: number, near: number, far: number): void
| Property |
Value |
| Validation |
Non-number args -> warning + no-op. fovDeg clamped to [1, 179]. near <= 0 or far <= near -> warning + no-op. |
| Error behavior |
Never throws for invalid arguments. |
3.2 `aura.camera3d.lookAt(x, y, z)`
aura.camera3d.lookAt(x: number, y: number, z: number): void
| Property |
Value |
| Validation |
Non-number args -> warning + no-op. |
| Error behavior |
Never throws. |
3.3 `aura.camera3d.setPosition(x, y, z)`
aura.camera3d.setPosition(x: number, y: number, z: number): void
3.4 `aura.camera3d.setTarget(x, y, z)`
aura.camera3d.setTarget(x: number, y: number, z: number): void
3.5 `aura.camera3d.setFOV(fovDeg)`
aura.camera3d.setFOV(fovDeg: number): void
| Property |
Value |
| Validation |
Non-number input -> warning + no-op. Value clamped to [1, 179] with warning. |
| Error behavior |
Never throws. |
3.6 `aura.camera3d.getViewMatrix()`
aura.camera3d.getViewMatrix(): Mat4
3.7 `aura.camera3d.getProjectionMatrix()`
aura.camera3d.getProjectionMatrix(): Mat4
| Property |
Value |
| Return value |
16-number matrix (column-major) suitable for shader uniform upload. |
| Error behavior |
Never throws. Returns last valid matrix state. |
3.8 `aura.camera3d.setControlProfile(profile, options?)`
aura.camera3d.setControlProfile(
profile: "none" | "orbit",
options?: {
rotateSpeed?: number;
panSpeed?: number;
zoomSpeed?: number;
damping?: number; // clamped to [0, 1]
minDistance?: number; // > 0, clamped to deterministic safe range
maxDistance?: number; // > 0, clamped to deterministic safe range
minPitchDeg?: number; // clamped to [-89, 89]
maxPitchDeg?: number; // clamped to [-89, 89]
}
): void
| Property |
Value |
| Validation |
Non-string/unsupported profile -> warning + no-op. Non-object options -> warning + no-op. Invalid numeric fields -> warning + no-op. Range values clamp where documented. |
| Error behavior |
Never throws. |
aura.camera3d.updateControls(
dtSeconds: number,
input?: {
rotateX?: number;
rotateY?: number;
panX?: number;
panY?: number;
zoom?: number;
}
): void
| Property |
Value |
| Validation |
dtSeconds must be finite and >= 0. Non-object input or invalid numeric fields -> warning + no-op. Input deltas are clamped to deterministic bounds. |
| Error behavior |
Never throws. No-op when no active control profile is enabled. |
3.10 `aura.camera3d.getControlState()`
aura.camera3d.getControlState(): {
profile: "none" | "orbit";
active: boolean;
orbit: {
yaw: number;
pitch: number;
distance: number;
target: Vec3;
rotateSpeed: number;
panSpeed: number;
zoomSpeed: number;
damping: number;
minDistance: number;
maxDistance: number;
minPitchDeg: number;
maxPitchDeg: number;
};
}
| Property |
Value |
| Return value |
Deterministic snapshot of current control profile + orbit state. |
| Error behavior |
Never throws. |
4) `aura.light`
4.1 `aura.light.ambient(color, intensity)`
aura.light.ambient(color: Color, intensity: number): LightHandle
4.2 `aura.light.hemisphere(skyColor, groundColor, intensity, upDirection?)`
aura.light.hemisphere(
skyColor: Color,
groundColor: Color,
intensity: number,
upDirection?: Vec3
): LightHandle
| Property |
Value |
| Description |
Set the singleton sky-ground ambient split used for outdoor daylight readability. |
| Return value |
Returns the ambient/light-environment sentinel handle (0). |
| Validation |
skyColor and groundColor are required. Structurally invalid required args throw TypeError. Invalid color payloads warn and fall back deterministically (skyColor -> white, groundColor -> black). intensity clamps to >= 0. Missing/invalid upDirection falls back to { x: 0, y: 1, z: 0 }. |
| Scope note |
This is a deterministic hemisphere/skylight term, not propagated block-light or full GI. |
4.3 `aura.light.directional(direction, color, intensity)`
aura.light.directional(direction: Vec3, color: Color, intensity: number): LightHandle
4.4 `aura.light.point(position, color, intensity, range)`
aura.light.point(position: Vec3, color: Color, intensity: number, range: number): LightHandle
4.5 `aura.light.spot(position, direction, color, intensity, range, angleRadians)`
aura.light.spot(
position: Vec3,
direction: Vec3,
color: Color,
intensity: number,
range: number,
angleRadians: number
): LightHandle
4.6 `aura.light.update(handle, props)`
aura.light.update(handle: LightHandle, props: {
position?: Vec3;
direction?: Vec3;
color?: Color;
intensity?: number;
range?: number;
angle?: number;
angleRadians?: number;
skyColor?: Color;
groundColor?: Color;
hemisphereIntensity?: number;
skyIntensity?: number;
upDirection?: Vec3;
}): void
4.7 `aura.light.remove(handle)`
aura.light.remove(handle: LightHandle): void
| Property |
Value |
| Validation |
Invalid color/vector/intensity/range -> warning + no-op (or per-field fallback for update). Invalid handle on update/remove -> silent no-op. |
| Error behavior |
ambient/hemisphere/directional/point/spot throw TypeError only for structurally invalid required args (null/wrong primitive type). update/remove never throw on invalid handles. |
| Limits |
Runtime supports 1 directional + up to 8 point lights + up to 8 spot lights per frame; overflow logs warning and drops newest light command. |
| Singleton note |
update(0, props) updates the ambient singleton and, when skyColor, groundColor, hemisphereIntensity / skyIntensity, or upDirection are present, also updates the hemisphere singleton. remove(0) resets ambient to defaults and clears the hemisphere contribution. |
4.7 `aura.light.setShadowCasting(lightId, enabled)`
aura.light.setShadowCasting(lightId: LightHandle, enabled: boolean): void
4.8 `aura.light.setShadowQuality(lightId, quality)`
aura.light.setShadowQuality(
lightId: LightHandle,
quality: "low" | "medium" | "high"
): void
4.9 `aura.light.setShadowBudget(maxLights)`
aura.light.setShadowBudget(maxLights: number): void
aura.light.configureDirectionalShadows(options: {
enabled?: boolean;
quality?: "low" | "medium" | "high";
bias?: number;
normalBias?: number;
filterMode?: "hard" | "pcf";
filterRadius?: number;
cascadeCount?: number;
tileResolution?: number;
lambda?: number;
blendWidth?: number;
shadowFar?: number;
stabilizeCascades?: boolean;
}): void
aura.light.configureShadow(lightId: LightHandle, options: {
enabled?: boolean;
quality?: "low" | "medium" | "high";
bias?: number;
normalBias?: number;
filterMode?: "hard" | "pcf";
filterRadius?: number;
}): void
4.12 `aura.light.getShadowState(lightId?)`
aura.light.getShadowState(lightId?: LightHandle): {
shadowBudget: number;
shadowCastingCount: number;
budgetSaturated: boolean;
shadowedPointLightCount: number;
shadowedSpotLightCount: number;
rendererActive: boolean;
effectiveShadowCastingCount: number;
effectivePointLightCount: number;
effectiveSpotLightCount: number;
directionalPassCount: number;
multiLightSlotCount: number;
multiLightPassCount: number;
shadowDrawCount: number;
directional: {
enabled: boolean;
quality: "low" | "medium" | "high";
bias: number;
normalBias: number;
filterMode: "hard" | "pcf";
filterRadius: number;
cascadeCount: number;
tileResolution: number;
lambda: number;
blendWidth: number;
shadowFar: number;
stabilizeCascades: boolean;
};
} | {
lightId: LightHandle;
type: "directional" | "point" | "spot";
enabled: boolean;
quality: "low" | "medium" | "high";
bias: number;
normalBias: number;
filterMode: "hard" | "pcf";
filterRadius: number;
rendererActive: boolean;
effectiveEnabled: boolean;
effectiveQuality: "low" | "medium" | "high";
effectiveBias: number;
effectiveSlotCount: number;
} | null
4.13 `aura.light.getShadowStats()`
aura.light.getShadowStats(): {
shadowCastingCount: number;
shadowBudget: number;
budgetSaturated: boolean;
directionalShadowEnabled: boolean;
directionalQuality: "low" | "medium" | "high";
directionalBias: number;
directionalNormalBias: number;
directionalFilterMode: "hard" | "pcf";
directionalFilterRadius: number;
directionalCascadeCount: number;
directionalTileResolution: number;
directionalLambda: number;
directionalBlendWidth: number;
directionalShadowFar: number;
directionalStabilizeCascades: boolean;
directionalTexelSnapErrorMax: number;
shadowedPointLightCount: number;
shadowedSpotLightCount: number;
rendererActive: boolean;
effectiveShadowCastingCount: number;
effectivePointLightCount: number;
effectiveSpotLightCount: number;
directionalPassCount: number;
multiLightSlotCount: number;
multiLightPassCount: number;
shadowDrawCount: number;
}
| Property |
Value |
| Validation |
setShadowCasting / setShadowQuality / configureShadow require a valid existing light handle. Unknown handles log warning and no-op. configureDirectionalShadows and configureShadow require an options object and throw TypeError when missing. |
| Clamping |
shadowBudget clamps to 1..=4. Directional bias clamps to 0.0..=0.05, normalBias to 0.0..=0.1, filterRadius to 0.0..=4.0, cascadeCount to 2..=4, tileResolution to 64..=4096, lambda to 0.0..=1.0, blendWidth to 0.0..=50.0, and shadowFar to 10.0..=10000.0. Per-light bias clamps to 0.0..=0.05, normalBias to 0.0..=0.1, and filterRadius to 0.0..=4.0. Invalid shadow quality strings warn and fall back to "medium". Invalid filterMode strings warn and fall back to "pcf". stabilizeCascades uses JS boolean coercion when provided. |
| Runtime note |
These APIs define the JS/runtime control contract. configureDirectionalShadows() and configureShadow() feed renderer-owned effective shadow state, while getShadowState(), getShadowStats(), and aura.debug.inspectorStats().scene3dRuntime.shadow report the clamped/runtime-effective values that were actually consumed. directionalTexelSnapErrorMax is the stabilization metric for active directional cascades: values near 0.0 mean the live cascades are currently snapped to the texel grid after clamping. Use those surfaces as the truth source instead of relying on renderer internals. |
5) `aura.mesh`
5.1 `aura.mesh.load(path)`
aura.mesh.load(path: string): MeshHandle
5.2 `aura.mesh.createBox(width?, height?, depth?)`
aura.mesh.createBox(width?: number, height?: number, depth?: number): MeshHandle
5.3 `aura.mesh.createSphere(radius?, segments?)`
aura.mesh.createSphere(radius?: number, segments?: number): MeshHandle
5.4 `aura.mesh.createPlane(width?, depth?)`
aura.mesh.createPlane(width?: number, depth?: number): MeshHandle
5.5 `aura.mesh.createCylinder(radius?, height?, segments?)`
aura.mesh.createCylinder(radius?: number, height?: number, segments?: number): MeshHandle
5.6 `aura.mesh.createCone(radius?, height?, segments?)`
aura.mesh.createCone(radius?: number, height?: number, segments?: number): MeshHandle
5.7 `aura.mesh.createTorus(majorRadius?, minorRadius?, radialSegments?, tubularSegments?)`
aura.mesh.createTorus(
majorRadius?: number,
minorRadius?: number,
radialSegments?: number,
tubularSegments?: number
): MeshHandle
5.8 `aura.mesh.createCapsule(radius?, height?, segments?, rings?)`
aura.mesh.createCapsule(
radius?: number,
height?: number,
segments?: number,
rings?: number
): MeshHandle
5.9 `aura.mesh.createRing(innerRadius?, outerRadius?, thetaSegments?)`
aura.mesh.createRing(
innerRadius?: number,
outerRadius?: number,
thetaSegments?: number
): MeshHandle
| Property |
Value |
| Description |
Build a flat indexed ring mesh on the XZ plane. |
| Defaults |
innerRadius=0.5, outerRadius=1.0, thetaSegments=32. |
| Validation |
innerRadius must be finite and >= 0. outerRadius must be finite and strictly greater than innerRadius. thetaSegments must be a finite non-negative integer. Runtime mesh generation clamps thetaSegments to at least 3. |
| Error behavior |
Throws Error for malformed numeric input or invalid radius ordering. |
| Usage note |
Use this for small procedural slices, decals, portals, and simple parametric props. Prefer authored assets when the shape needs bespoke topology or sculpted wear. |
5.10 `aura.mesh.createExtrude(shape2d, optionsOrDepth?, segments?)`
aura.mesh.createExtrude(
shape2d: Array<{ x: number; y: number }>,
optionsOrDepth?: { depth?: number; segments?: number } | number,
segments?: number
): MeshHandle
| Property |
Value |
| Description |
Extrude a closed 2D polygon into indexed 3D geometry. |
| Defaults |
depth=1.0, segments=1. |
| Validation |
shape2d must be an array of at least 3 {x, y} points with finite coordinates. depth must be finite and > 0. segments must be a finite non-negative integer and runtime clamps it to at least 1. If you pass an options object, do not also pass trailing positional depth/segments. |
| Error behavior |
Throws Error for malformed points, invalid depth, or mixed options + positional arguments. |
| Usage note |
Preferred form is the options object because it freezes the authored meaning of depth and segments directly in call sites. |
5.11 `aura.mesh.createLathe(points, optionsOrSegments?, phiStart?, phiLength?)`
aura.mesh.createLathe(
points: Array<{ x: number; y: number }>,
optionsOrSegments?: {
segments?: number;
phiStart?: number;
phiLength?: number;
} | number,
phiStart?: number,
phiLength?: number
): MeshHandle
| Property |
Value |
| Description |
Revolve a 2D profile around the Y axis to create indexed lathe geometry. |
| Defaults |
segments=12, phiStart=0, phiLength=TAU. |
| Validation |
points must be an array of at least 2 {x, y} points with finite coordinates. Profile x values must stay >= 0. segments must be a finite non-negative integer and runtime clamps it to at least 3. phiStart must be finite. phiLength must be finite and > 0. If you pass an options object, do not also pass trailing positional segments/phiStart/phiLength. |
| Error behavior |
Throws Error for malformed points, negative profile radii, non-positive phiLength, or mixed options + positional arguments. |
| Usage note |
Use this for small parametric props like vessels, columns, bulbs, or procedural geometry. Prefer authored assets when the silhouette needs hand-tuned asymmetry or broader asset-pipeline reuse. |
5.12 `aura.mesh.createFromVertices(vertices, indices, normals?, uvs?, colorsOrSkinning?, skinning?)`
aura.mesh.createFromVertices(
vertices: Float32Array | number[],
indices: Uint32Array | Uint16Array | number[],
normals?: Float32Array | number[] | null,
uvs?: Float32Array | number[] | null,
colorsOrSkinning?: Float32Array | number[] | {
jointIndices: Uint16Array | Uint32Array | number[];
jointWeights: Float32Array | number[];
} | null,
skinning?: {
jointIndices: Uint16Array | Uint32Array | number[];
jointWeights: Float32Array | number[];
} | null
): MeshHandle
| Property |
Value |
| Description |
Create a mesh from authored flat arrays. vertices and optional normals are xyz triples. Optional uvs are uv pairs. |
| Vertex colors |
If colorsOrSkinning is a numeric array, it is interpreted as flat per-vertex RGBA data with vertexCount * 4 entries. When omitted, vertex colors default to white. |
| Skinning |
Existing createFromVertices(..., skinning) calls remain valid. If the fifth argument is an object, Aura treats it as the legacy skinning payload. If colors are supplied, pass skinning as the sixth argument. |
| Validation |
vertices must contain whole xyz triples and indices must resolve to complete triangles. Optional normals, uvs, and vertex-color arrays must match the authored vertex count (*3, *2, and *4 respectively). Skinning payloads must provide aligned jointIndices and jointWeights quads per vertex. |
| Error behavior |
Throws TypeError for non-array vertex/index input and Error for malformed authored buffers, vertex-color payloads, or skinning data. |
| Usage note |
For voxel ambient occlusion and other baked per-vertex shading, prefer this vertex-color path over compensating scene-wide lighting hacks. Chunk meshes can use createFromVertices(..., colors) to preserve authored per-vertex lighting. |
5.13 `aura.mesh.getData(handle)`
aura.mesh.getData(handle: MeshHandle): {
vertexCount: number;
indexCount: number;
morphTargetCount: number;
bounds: { min: Vec3; max: Vec3 };
} | null
5.14 `aura.mesh.setMorphTargets(handle, targets)`
aura.mesh.setMorphTargets(handle: MeshHandle, targets: Array<{
positions: Float32Array | number[];
normals?: Float32Array | number[];
}>): void
| Property |
Value |
| Description |
Replace the mesh morph target set used by the draw3d morph shader path. Up to 4 targets are accepted per mesh. |
| Validation |
Invalid handle -> warning + no-op. targets must be an array of objects. Each positions buffer must contain vertexCount * 3 floats. Optional normals buffer must also contain vertexCount * 3 floats. More than 4 targets are truncated with warning. |
| Error behavior |
Throws TypeError for non-array input and Error for malformed target buffers. |
5.15 `aura.mesh.setMorphWeights(handle, weights)`
aura.mesh.setMorphWeights(handle: MeshHandle, weights: Float32Array | number[]): void
| Property |
Value |
| Description |
Update the per-draw morph blend weights. Missing entries default to 0. |
| Validation |
Invalid handle -> warning + no-op. weights must be a numeric array. Values past index 3 are ignored. |
| Error behavior |
Throws TypeError for non-array input. |
5.16 `aura.mesh.unload(handle)`
aura.mesh.unload(handle: MeshHandle): void
| Property |
Value |
| Validation |
Basic primitive creators (createBox, createSphere, createPlane, createCylinder, createCone, createTorus, createCapsule) keep their warning + fallback behavior for numeric range issues (1 unit defaults, segments>=3, rings>=2, torus defaults radial=24, tubular=16). getData with invalid handle returns null. unload with invalid handle is silent no-op. |
| Error behavior |
load(path) throws TypeError for non-string and Error for missing/unsupported mesh assets. Advanced authored generators (createRing, createExtrude, createLathe) throw on malformed authored input instead of guessing. |
| Supported formats |
glTF 2.0 binary (.glb) primary, OBJ (.obj) fallback. |
6) `aura.material`
6.1 `aura.material.create(options?)`
aura.material.create(options?: {
color?: Color;
texture?: string;
normalMap?: string;
metallic?: number;
roughness?: number;
alphaMode?: "opaque" | "mask" | "blend" | "hash";
alphaCutoff?: number;
doubleSided?: boolean;
sheenColor?: ColorRgb;
sheenRoughness?: number;
specularFactor?: number;
specularColor?: ColorRgb;
}): MaterialHandle
6.2 `aura.material.setColor(handle, color)`
aura.material.setColor(handle: MaterialHandle, color: Color): void
6.3 `aura.material.setTexture(handle, texturePath)`
aura.material.setTexture(handle: MaterialHandle, texturePath: string | DataTextureHandle | null): void
aura.material.setNormalMap(
handle: MaterialHandle,
input: string | DataTextureHandle | null
): void | { ok: false; reasonCode: string; reason: string }
6.3.2 `aura.material.setMetallicRoughnessTexture(handle, input)`
aura.material.setMetallicRoughnessTexture(
handle: MaterialHandle,
input: string | DataTextureHandle | null
): void | { ok: false; reasonCode: string; reason: string }
6.3.3 `aura.material.setOcclusionTexture(handle, input)`
aura.material.setOcclusionTexture(
handle: MaterialHandle,
input: string | DataTextureHandle | null
): void | { ok: false; reasonCode: string; reason: string }
6.3.4 `aura.material.setEmissiveTexture(handle, input)`
aura.material.setEmissiveTexture(
handle: MaterialHandle,
input: string | DataTextureHandle | null
): void | { ok: false; reasonCode: string; reason: string }
aura.material.setMetallic(handle: MaterialHandle, metallic: number): void
6.5 `aura.material.setRoughness(handle, roughness)`
aura.material.setRoughness(handle: MaterialHandle, roughness: number): void
aura.material.setMetallicRoughness(handle: MaterialHandle, metallic: number, roughness: number): void
6.7 `aura.material.setAlphaCutoff(handle, value)`
aura.material.setAlphaCutoff(handle: MaterialHandle, value: number): void | { ok: false; reasonCode: string; reason: string }
6.7.1 `aura.material.setAlphaMode(handle, mode)`
aura.material.setAlphaMode(
handle: MaterialHandle,
mode: "opaque" | "mask" | "blend" | "hash"
): void | { ok: false; reasonCode: string; reason: string }
6.8 `aura.material.setDoubleSided(handle, enabled)`
aura.material.setDoubleSided(handle: MaterialHandle, enabled: boolean): void | { ok: false; reasonCode: string; reason: string }
6.9 `aura.material.setSheen(handle, roughness, color)`
aura.material.setSheen(handle: MaterialHandle, roughness: number, color: ColorRgb): void | { ok: false; reasonCode: string; reason: string }
6.10 `aura.material.setSpecularFactor(handle, value)`
aura.material.setSpecularFactor(handle: MaterialHandle, value: number): void | { ok: false; reasonCode: string; reason: string }
6.10.1 `aura.material.setSpecularColor(handle, color)`
aura.material.setSpecularColor(
handle: MaterialHandle,
color: ColorRgb
): void | { ok: false; reasonCode: string; reason: string }
6.10.2 `aura.material.setOcclusionStrength(handle, value)`
aura.material.setOcclusionStrength(
handle: MaterialHandle,
value: number
): void | { ok: false; reasonCode: string; reason: string }
6.11 `aura.material.createCustom(options)`
aura.material.createCustom(options: {
vertex: string;
fragment: string;
uniforms?: Record<string, "float" | "vec2" | "vec3" | "vec4" | "mat4">;
texture?: boolean;
}): MaterialHandle
| Property |
Value |
| Description |
Create a custom WGSL-backed material that still uses Aura's fixed camera/model binding contract. |
| Required fields |
vertex and fragment must be non-empty WGSL strings. |
| Uniform contract |
uniforms is an object mapping names to "float", "vec2", "vec3", "vec4", or "mat4". Texture sampling is declared separately with texture: true; do not declare uniforms.texture = "texture". |
| Error behavior |
Throws TypeError for missing/invalid options, invalid shader-source strings, non-object uniforms, non-string uniform types, unknown uniform types, duplicate/empty uniform names, or non-boolean texture. |
| Runtime note |
Handle allocation happens before native shader compilation. If WGSL later fails native compilation, the handle is retired and later mutations resolve as missing_material_handle. |
| Guidance |
Use createCustom for narrow advanced materials or materials that genuinely need custom WGSL. Prefer the standard create(...) material path for normal lit scene content. |
aura.material.setUniform(
handle: MaterialHandle,
name: string,
value: number | number[] | { x: number; y?: number; z?: number; w?: number }
): void | { ok: false; reasonCode: string; reason: string }
| Property |
Value |
| Description |
Mutate one declared custom-shader uniform on an existing custom material. |
| Accepted values |
float accepts one finite number. vec2/vec3/vec4 accept flat arrays or {x,y[,z[,w]]} objects. mat4 accepts a flat 16-number array. |
| Failure reasons |
Structured failure objects use stable reason codes: invalid_material_handle, missing_material_handle, not_custom_shader_material, invalid_uniform_name, unknown_custom_uniform, invalid_uniform_value. |
| Guidance |
Keep these mutations narrow and declarative. If a material needs a large evolving authoring graph, it should probably not live on this seam. |
6.13 `aura.material.setCustomTexture(handle, texturePath)`
aura.material.setCustomTexture(
handle: MaterialHandle,
texturePath: string | null
): void | { ok: false; reasonCode: string; reason: string }
| Property |
Value |
| Description |
Bind or clear the single declared sampled 2D texture on a custom material. |
| Validation |
texture: true must have been declared at material creation time. null or '' clears the texture. |
| Failure reasons |
Structured failure objects use stable reason codes: invalid_material_handle, missing_material_handle, not_custom_shader_material, custom_texture_not_declared, invalid_custom_texture_path. |
| Runtime note |
String-path acceptance happens at the JS/binding seam. Actual file decode/bind occurs later on the native renderer path. |
6.14 `aura.material.reset(handle)`
aura.material.reset(handle: MaterialHandle): void
Resets material to deterministic defaults:
color = { r: 1, g: 1, b: 1, a: 1 }
metallic = 0
roughness = 1
texture = null (fallback/default texture)
6.14.1 `aura.material.setCubeMapTexture(handle, cubeCameraOrNull)`
aura.material.setCubeMapTexture(
handle: MaterialHandle,
cubeCamera: CubeCameraHandle | { handle: CubeCameraHandle } | null
): void
6.15 `aura.material.clone(handle)`
aura.material.clone(handle: MaterialHandle): MaterialHandle
6.16 `aura.material.unload(handle)`
aura.material.unload(handle: MaterialHandle): void
6.17 `aura.material.createGradientTexture(width, height, stops, options?)`
aura.material.createGradientTexture(
width: number,
height: number,
stops: GradientTextureStop[],
options?: GradientTextureOptions
): DataTextureHandle
6.18 `aura.material.createNoiseTexture(width, height, options?)`
aura.material.createNoiseTexture(
width: number,
height: number,
options?: NoiseTextureOptions
): DataTextureHandle
6.19 `aura.material.createDataTexture(width, height, pixelData, options?)`
aura.material.createDataTexture(
width: number,
height: number,
pixelData: Uint8Array | ArrayBuffer,
options?: DataTextureCreateOptions
): DataTextureHandle
6.20 `aura.material.updateDataTexture(handle, pixelData)`
aura.material.updateDataTexture(
handle: DataTextureHandle,
pixelData: Uint8Array | ArrayBuffer
): void
6.21 `aura.material.destroyDataTexture(handle)`
aura.material.destroyDataTexture(handle: DataTextureHandle): void
| Property |
Value |
| Validation |
Invalid handle for standard mutators/unload -> structured failure on the newer advanced setters and silent no-op on the oldest frozen setters. Numeric ranges clamp where documented (metallic, roughness, alphaCutoff, sheenRoughness, specularFactor, and occlusionStrength to [0,1]; ior to [1,3]; thickness>=0). setCubeMapTexture accepts a positive cube-camera handle, an object with positive-integer .handle, or null to clear the override. |
| Error behavior |
create throws TypeError only for non-object options. setTexture throws for non-string/non-number/non-null input and for unknown data-texture handles. setNormalMap, setMetallicRoughnessTexture, setOcclusionTexture, and setEmissiveTexture return structured failures on invalid handles or bad texture input using stable reason codes such as invalid_material_handle, missing_material_handle, invalid_texture_input, and unknown_data_texture_handle. setAlphaMode, setSpecularColor, and setOcclusionStrength also use structured failures such as invalid_alpha_mode, invalid_specular_color, and invalid_occlusion_strength_value. createGradientTexture throws on malformed stops. createNoiseTexture throws on non-numeric seed and on unsupported mode. clone of invalid handle throws Error("Invalid material handle"). createCustom throws on malformed declarations, while bad WGSL compilation later retires the handle. setCubeMapTexture never throws for bad handle input; it warns and no-ops. |
| Runtime note |
setTexture live-binds albedo data textures. The secondary texture setters also accept data-texture handles, but the current native implementation snapshots the data texture into the material slot at bind time rather than live-linking later updateDataTexture(...) changes. createGradientTexture(...) and createNoiseTexture(...) are CPU-authored helpers layered over the same data-texture queue. setCubeMapTexture(...) now ships one bounded per-material cube-camera override over the existing live cube-camera lane: it affects the main forward-material consumer path and accepts null to revert to the shared scene IBL source. It does not imply generalized reflection sources, skinned-mesh parity, or cube-camera capture recursion. The shipped noise helper is seeded white noise only; GPU compute texture generation and render-to-texture baking remain outside this contract. |
Phase-2 supported controls:
- Runtime mutators:
setColor, setTexture, setNormalMap, setMetallicRoughnessTexture, setOcclusionTexture, setEmissiveTexture, setMetallic, setRoughness, setMetallicRoughness, setAlphaCutoff, setAlphaMode, setDoubleSided, setSheen, setSpecularFactor, setSpecularColor, setOcclusionStrength, setCubeMapTexture, setUniform, setCustomTexture, reset.
- Resource lifecycle:
create, clone, unload.
- Procedural texture authoring:
createGradientTexture, createNoiseTexture, createDataTexture, updateDataTexture, destroyDataTexture.
- Advanced material creation:
createCustom, plus create-time knobs such as alphaMode, alphaCutoff, doubleSided, sheenColor, sheenRoughness, specularFactor, and specularColor.
- PBR texture inputs accepted at create time:
texture, normalMap, metallicRoughnessTexture, aoTexture/occlusionTexture, emissiveTexture.
Explicit non-goals in this phase:
- No node-graph/shader-graph authoring surface.
- No per-material pipeline state overrides (blend/depth/cull/sampler mode).
- No full runtime parity for every create-time PBR knob yet (for example
clearcoat and the sheen/specular texture-backed lanes remain outside this runtime wave).
- No subsurface authoring surface.
- No generalized per-material reflection graph beyond cube-camera overrides layered over the shipped shared IBL lane.
- No GPU compute texture generation or render-to-texture material baking in this surface.
Operator guidance:
- Prefer
createRing, createExtrude, and createLathe for small procedural shapes,
blockouts, and compact parametric props whose authored shape is cheaper to
express in code than to import as an asset.
- Prefer authored assets for scene-critical organic meshes, asymmetric props,
or content that artists need to iterate outside code.
- Prefer
createCustom for narrow, explicit WGSL needs where the standard PBR
material surface cannot express the effect.
- Prefer standard materials for most scene content so lighting,
textures, and tooling remain on the common path.
7) `aura.compute`
This section freezes the native compute surface exposed by the host runtime.
Browser capability declarations for compute are documented separately from this
native compute reference.
Type aliases (TypeScript notation):
type ComputePipelineHandle = number;
type ComputeBufferHandle = number;
type ComputeBindGroupHandle = number;
type ComputeBufferUsage =
| 'storage'
| 'uniform'
| 'storage-read'
| 'storage_read'
| 'readback'
| 'staging';
type ComputeBindGroupEntry = {
binding: number;
buffer: ComputeBufferHandle;
};
7.1 `aura.compute.createPipeline(wgslCode, entryPoint?)`
aura.compute.createPipeline(
wgslCode: string,
entryPoint?: string
): ComputePipelineHandle
| Property |
Value |
| Validation |
wgslCode must be a string. entryPoint defaults to "main" when omitted or non-string. |
| Error behavior |
Invalid wgslCode throws TypeError. WGSL compilation/runtime failures surface asynchronously through aura.compute.getError(handle). |
7.2 `aura.compute.createBuffer(size, usage?)`
aura.compute.createBuffer(
size: number,
usage?: ComputeBufferUsage
): ComputeBufferHandle
| Property |
Value |
| Validation |
size must be a finite number greater than 0. usage defaults to "storage" and accepts "storage", "uniform", "storage-read" / "storage_read", "readback" / "staging". |
| Error behavior |
Invalid size type throws TypeError. size <= 0 throws RangeError. Unknown usage throws TypeError. |
7.3 `aura.compute.writeBuffer(bufferHandle, data, offset?)`
aura.compute.writeBuffer(
bufferHandle: ComputeBufferHandle,
data: TypedArray | ArrayBuffer,
offset?: number
): void
| Property |
Value |
| Validation |
bufferHandle must be numeric. data must be a TypedArray or ArrayBuffer. offset defaults to 0. |
| Error behavior |
Invalid argument types throw TypeError. Invalid runtime handles report through aura.compute.getError(handle) after host processing. |
7.4 `aura.compute.createBindGroup(pipelineHandle, entries)`
aura.compute.createBindGroup(
pipelineHandle: ComputePipelineHandle,
entries: ComputeBindGroupEntry[]
): ComputeBindGroupHandle
| Property |
Value |
| Validation |
pipelineHandle must be numeric. entries must be an array of objects with numeric binding and buffer fields. |
| Error behavior |
Invalid argument types throw TypeError. Invalid runtime handles report through aura.compute.getError(handle) after host processing. |
7.5 `aura.compute.dispatch(pipelineHandle, bindGroupHandle, workgroupsX, workgroupsY?, workgroupsZ?)`
aura.compute.dispatch(
pipelineHandle: ComputePipelineHandle,
bindGroupHandle: ComputeBindGroupHandle,
workgroupsX: number,
workgroupsY?: number,
workgroupsZ?: number
): void
| Property |
Value |
| Validation |
pipelineHandle, bindGroupHandle, and workgroupsX must be numeric. workgroupsY/workgroupsZ default to 1. Workgroup counts are clamped to a minimum of 1. |
| Error behavior |
Invalid argument types throw TypeError. Runtime dispatch failures report through aura.compute.getError(handle). |
7.6 `aura.compute.readBuffer(bufferHandle, offset?, size?)`
aura.compute.readBuffer(
bufferHandle: ComputeBufferHandle,
offset?: number,
size?: number
): Float32Array | Uint8Array | null
| Property |
Value |
| Validation |
bufferHandle must be numeric. Returned data is Float32Array when byte length is 4-byte aligned; otherwise Uint8Array. |
| Error behavior |
Invalid argument types throw TypeError. The API is queued: passing a positive size enqueues readback and returns null; a later call returns the data once the host has processed it. |
7.7 `aura.compute.destroyPipeline(handle)`
aura.compute.destroyPipeline(handle: ComputePipelineHandle): void
7.8 `aura.compute.destroyBuffer(handle)`
aura.compute.destroyBuffer(handle: ComputeBufferHandle): void
7.9 `aura.compute.destroyBindGroup(handle)`
aura.compute.destroyBindGroup(handle: ComputeBindGroupHandle): void
7.10 `aura.compute.getError(handle)`
aura.compute.getError(
handle: ComputePipelineHandle | ComputeBufferHandle | ComputeBindGroupHandle
): string | null
| Property |
Value |
| Validation |
handle must be numeric. |
| Error behavior |
Invalid argument types throw TypeError. Returns the last queued runtime error for the handle, or null when no error is present. Reading the error consumes it. |
8) Error semantics summary (all 3D calls)
| Namespace |
Invalid Arguments |
Missing Asset/Resource |
Invalid Handle |
Runtime Failure |
aura.draw3d |
Warning + no-op/fallback |
drawSkybox throws Error |
N/A |
Warning; no crash |
aura.camera3d |
Warning + no-op/clamp |
N/A |
N/A |
Warning; keeps last valid camera state |
aura.light |
Constructors may throw TypeError for structurally invalid required args; otherwise warning |
N/A |
update/remove: silent no-op |
Warning; no crash |
aura.mesh |
Primitive creators warn + fallback defaults |
load throws Error |
getData -> null; unload no-op |
Warning; no crash |
aura.material |
Warning + clamp/no-op |
setTexture throws Error for missing path |
Mutators/unload no-op; clone throws on invalid handle |
Warning; no crash |
aura.compute |
Create/read/write/dispatch argument type mismatches throw TypeError; createBuffer(<=0) throws RangeError |
N/A |
Runtime handle failures surface via getError(handle) after queued host processing |
Warning/error string; no host crash |
Severity order mirrors v1: throw > warning + no-op/fallback > silent no-op.
9) Reference examples
9.1 `aura.draw3d`
| Call |
Input |
Expected |
drawMesh(1, 2) |
valid handles |
mesh draw enqueued |
drawMesh(-1, 2) |
invalid mesh handle |
warning, no-op |
drawMesh(1, 2, { scale: { x: 2, y: 2, z: 2 } }) |
valid transform |
scaled draw |
drawMesh(1, 2, { position: null }) |
null vec |
warning, fallback position |
drawSkybox("env/sky") |
existing asset |
skybox set |
drawSkybox("missing/sky") |
missing asset |
throws Error |
clear3d() |
no color |
default clear color |
clear3d({ r: 1, g: 0, b: 0 }) |
missing alpha |
alpha defaults to 1.0 |
9.2 `aura.camera3d`
| Call |
Input |
Expected |
perspective(60, 0.1, 1000) |
valid |
camera projection updated |
perspective(200, 0.1, 1000) |
fov out of range |
clamp to 179, warning |
perspective(60, -1, 1000) |
invalid near |
warning, no-op |
setPosition(0, 5, 10) |
valid |
camera moved |
setTarget(0, 0, 0) |
valid |
camera target updated |
lookAt(0, 0, 0) |
valid |
target set to point |
setFOV("wide") |
invalid type |
warning, no-op |
getViewMatrix() |
any time |
returns 16 numbers |
getProjectionMatrix() |
any time |
returns 16 numbers |
setControlProfile("orbit", { damping: 2 }) |
damping out of range |
clamp damping to 1, warning |
setControlProfile("freecam") |
unsupported profile |
warning, no-op |
updateControls(1/60, { rotateX: 2000 }) |
oversized delta |
clamp input to deterministic bounds |
updateControls(-1, {}) |
invalid dtSeconds |
warning, no-op |
getControlState() |
any time |
deterministic object snapshot with profile/active/orbit |
9.3 `aura.light`
| Call |
Input |
Expected |
ambient({r:1,g:1,b:1,a:1}, 0.3) |
valid |
returns light handle |
directional({x:1,y:-1,z:0}, {r:1,g:1,b:0.9,a:1}, 1) |
valid |
returns light handle |
point({x:0,y:2,z:0}, {r:1,g:0.8,b:0.5,a:1}, 2, 25) |
valid |
returns light handle |
spot({x:0,y:3,z:2}, {x:0,y:-1,z:0}, {r:1,g:1,b:1,a:1}, 2, 30, 0.7853982) |
valid |
returns light handle |
point({x:0,y:2,z:0}, {r:1,g:1,b:1,a:1}, 2, -1) |
invalid range |
warning + clamp/fallback |
update(10, { intensity: 0.5 }) |
valid handle |
light updated |
update(-1, { intensity: 0.5 }) |
invalid handle |
silent no-op |
remove(10) |
valid handle |
light removed |
remove(-1) |
invalid handle |
silent no-op |
9.4 `aura.mesh`
| Call |
Input |
Expected |
load("models/crate.glb") |
existing glb |
returns mesh handle |
load("models/crate.obj") |
existing obj |
returns mesh handle |
load("models/missing.glb") |
missing |
throws Error |
load(42 as any) |
non-string |
throws TypeError |
createBox() |
defaults |
returns mesh handle |
createSphere(1, 24) |
valid |
returns mesh handle |
createSphere(1, -2) |
invalid segments |
warning + fallback segments |
createPlane(10, 10) |
valid |
returns mesh handle |
createCylinder(1, 1, 16) |
valid |
returns mesh handle (vertexCount=68, indexCount=192) |
createCone(1, 1, 16) |
valid |
returns mesh handle (vertexCount=51, indexCount=96) |
createTorus(1, 0.3, 24, 16) |
valid |
returns mesh handle (vertexCount=425, indexCount=2304) |
createCapsule(0.5, 2, 16, 8) |
valid |
returns mesh handle (vertexCount=306, indexCount=1632) |
createFromVertices(vertices, indices) |
valid authored positions/indices |
returns mesh handle with default white vertex colors |
createFromVertices(vertices, indices, normals, uvs, colors) |
valid color array |
returns mesh handle with per-vertex RGBA colors |
createFromVertices(vertices, indices, normals, uvs, { jointIndices, jointWeights }) |
legacy 5th-arg skinning |
returns mesh handle using supplied skinning |
createFromVertices(vertices, indices, normals, uvs, colors, { jointIndices, jointWeights }) |
colors + supplied skinning |
returns mesh handle using both contracts together |
createFromVertices(vertices, indices, normals, uvs, [1, 0, 0]) |
malformed color array |
throws Error |
getData(handle) |
valid handle |
metadata object |
getData(-1) |
invalid handle |
null |
unload(handle) |
valid |
mesh released |
unload(-1) |
invalid handle |
silent no-op |
9.5 `aura.material`
| Call |
Input |
Expected |
create() |
defaults |
returns material handle |
create({ metallic: 0.8, roughness: 0.2 }) |
valid |
handle created |
create(null as any) |
invalid options type |
throws TypeError |
setColor(handle, {r:1,g:0,b:0,a:1}) |
valid |
color updated |
setMetallic(handle, 2) |
out of range |
clamped to 1, warning |
setRoughness(handle, -1) |
out of range |
clamped to 0, warning |
setTexture(handle, "textures/albedo.png") |
existing texture |
texture bound |
setTexture(handle, "textures/missing.png") |
missing texture |
throws Error |
setTexture(handle, null) |
clear texture |
texture removed |
clone(handle) |
valid handle |
returns new independent handle |
clone(-1) |
invalid handle |
throws Error("Invalid material handle") |
unload(handle) |
valid handle |
material released |
unload(-1) |
invalid handle |
silent no-op |
9.6 `aura.compute`
| Call |
Input |
Expected |
createPipeline(shader) |
valid WGSL string |
returns pipeline handle |
createPipeline(42 as any) |
invalid WGSL type |
throws TypeError |
createBuffer(4, "storage-read") |
valid |
returns buffer handle |
createBuffer(0) |
invalid size |
throws RangeError |
writeBuffer(buffer, new Float32Array([2])) |
valid |
buffer upload queued |
writeBuffer(buffer, {}) |
invalid data |
throws TypeError |
createBindGroup(pipeline, [{ binding: 0, buffer }]) |
valid |
returns bind group handle |
dispatch(pipeline, bindGroup, 1) |
valid |
compute dispatch queued |
readBuffer(buffer, 0, 4) |
first readback request |
returns null, readback queued |
readBuffer(buffer) |
later frame with completed readback |
returns Float32Array([3]) once readback completes |
getError(handle) |
no runtime fault |
null |
destroyBindGroup(handle) / destroyPipeline(handle) / destroyBuffer(handle) |
valid handle |
resource release queued |
9.7 Three.js-style reference example
| Vector |
Expected |
Scene graph hierarchy (scene3d.createNode/setParent/setLocalTransform/getWorldTransform/traverse/queryRaycast) |
Deterministic parent-child propagation, traversal order, and ray-hit ordering across repeated runs. |
Camera controls (camera3d.perspective/setPosition/setTarget/lookAt/setControlProfile/updateControls/getControlState) |
Deterministic, finite matrices and deterministic control-state snapshots across repeated runs. |
Interaction query (scene3d.queryRaycast) |
Deterministic nearest-hit selection and stable multi-hit ordering (distance/toi ASC, tie-break by nodeId ASC). |
Light lifecycle (light.ambient/directional/point/update/remove) |
Handles are allocated and mutable controls execute without runtime instability. |
Material lifecycle (material.create/clone/setColor/setMetallicRoughness/unload) |
Clone remains distinct from source and mutators are behaviorally active in the example loop. |
Draw submission (draw3d.clear3d/drawMesh) + mesh lifecycle (mesh.createBox/createCylinder/createCone/createTorus/createCapsule/getData/unload) |
Scene draw submissions and procedural mesh metadata remain deterministic across reruns. |
Non-goals for this example:
- Pixel-perfect frame matching with Three.js output.
- Post-processing and custom shader graphs beyond the documented API.
9.8 `aura.scene3d`
Query helper contract:
aura.scene3d.screenToRay(pixelX, pixelY)
aura.scene3d.pick(pixelX, pixelY, options?)
aura.scene3d.queryRaycast(originOrOptions, maybeDirection?, maybeOptions?)
aura.scene3d.setDrawMeshQueryTag(meshHandle, tagOrNull)
aura.scene3d.raycast(origin, direction, options?)
| Method |
Contract |
screenToRay(pixelX, pixelY) |
Returns { ok: true, origin, direction } for valid window pixel coordinates, or { ok: false, reason } for invalid inputs or missing camera/viewport state. Stable failure reasons are invalid_screen_coords, missing_camera_or_window, invalid_viewport_size, missing_camera_matrices, singular_view_projection, degenerate_unproject, and degenerate_ray_direction. |
pick(pixelX, pixelY, options?) |
Convenience screen-space retained-scene query. Uses the current scene3d node graph rather than submitted draw meshes or physics bodies. Accepts maxDistance, firstOnly, and layerMask. Successful hits expose nodeId, distance, point, layer, meshHandle, materialHandle, visible, hasRenderBinding, normal: null, and triangleIndex: -1. |
queryRaycast(originOrOptions, maybeDirection?, maybeOptions?) |
Retained-scene query over authored scene3d nodes using node world transforms and scale-derived proxy bounds. Supports (origin, direction, options?) or { origin, direction, ...options }. Options are maxDistance, firstOnly, visibleOnly, requireRenderBinding, layerMask, includeNodeIds, and excludeNodeIds. Successful responses expose hit, hitCount, firstHit, hits, maxDistance, firstOnly, visibleOnly, requireRenderBinding, and layerMask. Each hit exposes nodeId, distance, toi (same numeric value as distance), point, radius, layer, meshHandle, materialHandle, visible, and hasRenderBinding. Stable validation failures return { ok: false, reason } with invalid_raycast_args, invalid_raycast_first_only, invalid_raycast_visible_only, invalid_raycast_require_render_binding, invalid_raycast_layer_mask, invalid_raycast_include_node_ids, or invalid_raycast_exclude_node_ids. |
setDrawMeshQueryTag(meshHandle, tagOrNull) |
Stores one authored string tag for a submitted draw-mesh handle. Accepts a positive integer meshHandle plus a non-empty trimmed string tag, or null to clear the authored tag. Successful responses return { ok: true, reason: null, meshHandle, queryTag }. Stable validation failures return { ok: false, reason } with invalid_mesh_handle or invalid_query_tag. |
raycast(origin, direction, options?) |
Draw-scene mesh query over submitted render data, not retained scene3d node identity. Accepts maxDistance, firstOnly, and testTriangles. Returns an array of mesh hits shaped like { meshHandle, distance, point, normal, triangleIndex, queryTag }, where triangleIndex is a numeric triangle id for triangle hits or null for bounds-only hits, and queryTag is the authored draw-mesh tag or null. Use this lane when you need draw-mesh, triangle-hit, or authored draw-mesh tag truth rather than retained node identity. |
Query split:
- Use
scene3d.queryRaycast(...) when gameplay needs authored nodeId identity, render-binding identity, or retained-scene filtering.
- Use
scene3d.pick(...) for screen-space picking on that same retained-scene lane.
- Use
scene3d.raycast(...) when gameplay needs draw-mesh hits, triangle tests, or authored draw-mesh tags against submitted render data.
- Use
physics3d.queryRaycast(...) when gameplay needs physics-body ownership or physics filtering.
Bounded imported-scene note:
- The bounded browser-three imported-scene lane also includes
loadGltfScene(...), getImportedScene(...), playImportedAnimation(...), and updateClips(...) on the accepted web subset.
- On active
threejs, the accepted node-matrix imported-scene lane reports matrix-authored transform truth through getImportedScene(importId).usesNodeMatrix, nodeMatrixNodeCount, and nodeHandles[*].transformSource. This is bounded imported-scene transform audit truth, not general transform parity or general glTF parity.
- On active
threejs, the accepted inverse-bind exported-skinned lane reports inverse-bind truth through getImportedScene(importId).skinBindings[*].hasInverseBindMatrices and inverseBindMatrixCount. This is bounded imported-scene audit truth, not general skeleton ownership or general glTF parity.
- On active
threejs, the accepted emissive-textured imported-material lane reports emissive-texture truth through getImportedScene(importId).materials[*].emissiveTexturePath plus runtimeBinding.emissiveTexturePath. This is bounded imported-material audit truth, not general material parity or general glTF parity.
- On active
threejs, the accepted normal-textured imported-material lane reports normal-texture truth through getImportedScene(importId).materials[*].normalTexturePath plus runtimeBinding.normalTexturePath. This is bounded imported-material audit truth, not general material parity or general glTF parity.
- On active
threejs, the accepted metallic-roughness-textured imported-material lane reports metallic-roughness-texture truth through getImportedScene(importId).materials[*].metallicRoughnessTexturePath plus runtimeBinding.metallicRoughnessTexturePath. This is bounded imported-material audit truth, not general material parity or general glTF parity.
9.9 `aura.collision3d`
Additive capsule-contact contract:
aura.collision3d.capsulePoint(start, end, radius, point)
aura.collision3d.capsuleSphere(start, end, radius, sphere)
aura.collision3d.capsuleCapsule(aStart, aEnd, aRadius, bStart, bEnd, bRadius)
| Method |
Contract |
capsulePoint(start, end, radius, point) |
Pure-math capsule-vs-point contact helper. Returns false on invalid input or miss, or { hit: true, depth, point, normal } on overlap. Exact tangency is a hit with depth: 0. normal points from the authored point target back toward the capsule. Zero-length capsules are accepted. |
capsuleSphere(start, end, radius, sphere) |
Pure-math capsule-vs-sphere contact helper. Returns false on invalid input or miss, or { hit: true, depth, point, normal } on overlap. Exact tangency is a hit with depth: 0. normal points from the sphere target back toward the capsule. Zero-length capsules are accepted. |
capsuleCapsule(aStart, aEnd, aRadius, bStart, bEnd, bRadius) |
Pure-math capsule-vs-capsule contact helper. Returns false on invalid input or miss, or { hit: true, depth, point, normal } on overlap. Exact tangency is a hit with depth: 0. Zero-length capsules are accepted. When overlap has no stable separation axis, fallback normals are deterministic instead of NaN; fully degenerate coincident cases resolve to normal: { x: 0, y: 1, z: 0 }. |
10) Frozen surface summary
// aura.draw3d
drawMesh(mesh, material, transform?)
drawSkybox(path)
clear3d(color?)
billboard(textureHandleOrSource, options)
// aura.camera3d
perspective(fovDeg, near, far)
lookAt(x, y, z)
setPosition(x, y, z)
setTarget(x, y, z)
setFOV(fovDeg)
getViewMatrix()
getProjectionMatrix()
setControlProfile(profile, options?)
updateControls(dtSeconds, input?)
getControlState()
// aura.scene3d (helper)
createNode(initialTransform?)
removeNode(nodeId)
setParent(nodeId, parentId)
getParent(nodeId)
setLocalTransform(nodeId, transform)
getLocalTransform(nodeId)
getWorldTransform(nodeId)
traverse(rootOrCallback, maybeCallback)
screenToRay(pixelX, pixelY)
pick(pixelX, pixelY, options?)
queryRaycast(originOrOptions, maybeDirection?, maybeOptions?)
setDrawMeshQueryTag(meshHandle, tagOrNull)
raycast(origin, direction, options?)
// aura.light
ambient(color, intensity)
hemisphere(skyColor, groundColor, intensity, upDirection?)
directional(direction, color, intensity)
point(position, color, intensity, range)
spot(position, direction, color, intensity, range, angleRadians)
update(handle, props)
remove(handle)
// aura.mesh
load(path)
createBox(width?, height?, depth?)
createSphere(radius?, segments?)
createPlane(width?, depth?)
createCylinder(radius?, height?, segments?)
createCone(radius?, height?, segments?)
createTorus(majorRadius?, minorRadius?, radialSegments?, tubularSegments?)
createCapsule(radius?, height?, segments?, rings?)
createFromVertices(vertices, indices, normals?, uvs?, colorsOrSkinning?, skinning?)
getData(handle)
unload(handle)
// aura.material
create(options?)
setColor(handle, color)
setTexture(handle, texturePath)
setMetallic(handle, metallic)
setRoughness(handle, roughness)
setMetallicRoughness(handle, metallic, roughness)
reset(handle)
clone(handle)
unload(handle)
// aura.compute
createPipeline(wgslCode, entryPoint?)
createBuffer(size, usage?)
writeBuffer(bufferHandle, data, offset?)
createBindGroup(pipelineHandle, entries)
dispatch(pipelineHandle, bindGroupHandle, workgroupsX, workgroupsY?, workgroupsZ?)
readBuffer(bufferHandle, offset?, size?)
destroyPipeline(handle)
destroyBuffer(handle)
destroyBindGroup(handle)
getError(handle)
Any signature or error-behavior change above is a breaking API change and requires a major contract version bump.