AURA

JSGG

AuraJS
DOCSEXAMPLESGITHUB
API Contract
Canonical core API signatures, validation rules, and JS binding semantics.
docs/api-contract.md

AuraJS 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-v1.md path.


Table of Contents

  1. Namespace Overview
  2. Lifecycle Callbacks
  3. aura.window
  4. aura.input
  5. aura.draw2d
  6. aura.audio
  7. aura.assets
  8. aura.storage
  9. aura.math
  10. aura.timer
  11. aura.collision
  12. Color and Vector Utilities
  13. Error Semantics Table
  14. Reference Examples
  15. Versioning Policy

This document defines the exact public aura.* signatures, argument validation rules, return types, and error semantics. If examples or legacy surfaces elsewhere conflict with this file, this file wins.

Split Routes

Use one of these narrower exact-reference pages before reading this file end-to-end:

Contract Reconciliation Notes

This core contract is canonical for JS-visible signatures.

  • Developer-experience examples in spec.md must follow this contract.
  • If a sample or legacy runtime surface conflicts with this document, this document wins.

Compatibility aliases are non-canonical and migration-only:

Legacy Alias Canonical Surface
aura.input.isDown/isPressed/isReleased aura.input.isKeyDown/isKeyPressed/isKeyReleased
aura.collide.* aura.collision.*
aura.draw2d.image(...) aura.draw2d.sprite(...)
aura.draw2d.push/pop aura.draw2d.pushTransform/popTransform
aura.storage.set/get aura.storage.save/load
aura.window.width/height/fps aura.window.getSize()/getFPS()
aura.color(...), aura.colors.* aura.rgb/rgba, aura.Color.*

Runtime hardening notes:

  • Native runtime exposes aura.API_VERSION === 1 as a read-only property.
  • Native runtime exposes aura.VERSION (semver string) as a read-only property.
  • aura.math.PI and aura.math.TAU are read-only constants.
  • Migration aliases above remain supported in the core contract and can only be removed in a major release that increments aura.API_VERSION per Section 15.

Boundary drift notes:

  • Runtime bootstrap may install bridge-only namespaces not modeled as standalone capability-matrix contract rows: aura.draw3d, aura.camera3d, aura.light, aura.mesh, aura.material, aura.input.mouse, aura.input.gamepad, aura.input.actionMap.
  • Boundary inventory may include compatibility and bridge methods outside the canonical API surface:
    • legacy aliases: aura.draw2d.image/push/pop, aura.storage.get/set
    • inspector hooks: aura.animation.__inspectorStats, aura.scene3d.__inspectorStats, aura.tween.__inspectorStats
    • optional-module bridge internals: aura.physics.__*, aura.net.__*
  • These surfaces remain documented as additive/compatibility behavior and do not change canonical gameplay signatures.

Core contract scope boundary:

  • Required/core scopes: lifecycle, window, input, draw2d, camera, audio, assets, storage, math, timer, collision, color/vector utilities.
  • Additive deterministic helper available in the core contract: aura.animation (timeline primitives for play/pause/seek/loop/speed with reason-coded validation failures).
  • Additive deterministic helper available in the core contract: aura.tween (single-axis tween/easing primitives with deterministic IDs/update ordering and reason-coded validation failures).
  • Additive deterministic helper available in the core contract: aura.particles (constrained particles-lite emit/update/draw/stop helper with deterministic emitter IDs and pool behavior).
  • Deferred from the original core baseline: 3D (aura.draw3d, aura.camera3d, aura.light, aura.mesh, aura.material).
  • Additive hierarchy helper available in the core contract: aura.scene3d (deterministic parent/child transform composition plus clip/skinning runtime helpers; does not own rendering or scene submission).
  • Additive scene lifecycle/state helper available in the core contract: aura.scene (deterministic scene create/start/pause/resume/update transitions with reason-coded responses).
  • Additive multiplayer helper available in the core contract: aura.multiplayer (host/client session lifecycle, state sync, and deterministic callback dispatch).
  • Optional modules (physics, net, multiplayer) are contract-defined only when enabled via modules.* (see Sections 1.1 and 1.2).
  • 3D work is defined under the separate AuraJS 3D API Contract. This file remains the core-contract reference only.
  • Current 3D runtime pipeline uses camera/model MVP uniforms, material baseColor+metallic+roughness+albedo texture, and light uniforms (ambient + 1 directional + up to 8 point lights).

Build and Packaging Contract Notes

This document is primarily for JS API signatures, but v1 build behavior is pinned here to prevent docs/help drift:

  • In v1, aura build emits exactly one current-host desktop native output for desktop targets (windows, mac, or linux).
  • --target windows|mac|linux|all|web|android|ios|mobile is accepted. --target all and cross-target desktop native requests still resolve to current host only in v1.
  • Output contract root is build/<target>/, containing the configured executable (identity.executable, platform extension applied), js/game.bundle.js, assets-manifest.json, and build-manifest.json (schema: "aurajs.build-manifest.v1").
  • --target web emits browser artifacts under build/web for the current browser-backed subset.
  • --target android|ios|mobile produces host artifacts, stages mobile package roots, and runs Gradle/Xcode export when prerequisites are present; otherwise manifests stay reason-coded and staged.
  • Mobile targets require --asset-mode embed in v1.
  • Web output contract includes build/web/js/game.bundle.js, build/web/web-build-manifest.json, build/web/runtime-config.json, build/web/js/aura-web-loader.js, build/web/index.html, and build/web/assets/*.
  • Mobile staged roots are build/android/** and build/ios/**, with android-build-manifest.json and ios-build-manifest.json as the target-owned sources of truth for those lanes.
  • Web target does not currently emit native assets-manifest.json or build-manifest.json; web-build-manifest.json is the browser source of truth.
  • Browser support taxonomy is explicit: supported (loader lifecycle plus the current browser 2D/window/camera/input/assets/storage/collision/math/timer subset), partial (browser-backed starter coverage plus manifest-backed sound handles without browser audio playback), unsupported (browser 3D namespaces such as aura.draw3d.*, aura.camera3d.*, aura.light.*, aura.mesh.*, aura.material.*, aura.scene3d.*, aura.character3d.*, plus aura.state.*, aura.net.*, aura.multiplayer.*, and aura run --target web).
  • If this behavior changes in a future version, update this addendum and CLI help in the same change.

1. Namespace Overview

Namespace Description
aura.setup / aura.update / aura.draw / aura.onResize / aura.onFocus / aura.onBlur Lifecycle callbacks defined by the game, invoked by the Rust host each frame.
aura.window Query and mutate the application window (title, size, fullscreen, cursor visibility/lock, pixel ratio, FPS).
aura.input Poll keyboard, mouse, relative mouse delta, and gamepad state for the current frame.
aura.draw2d Immediate-mode 2D drawing primitives (shapes, sprites, text, transforms).
aura.camera Canonical 2D camera state (x, y, zoom, rotation) plus additive deterministic camera-rig helpers (follow, setDeadzone, setBounds, pan/zoom/rotate/shake, update) consumed by draw2d geometry/sprite calls in deterministic call order.
aura.audio Play, pause, stop, and mix audio assets.
aura.assets Load and query bundled assets (images, audio, fonts, JSON, text).
aura.storage Persist and retrieve JSON-serializable data to local storage.
aura.math Common math utilities (lerp, clamp, random, distance, angle, constants).
aura.timer Schedule deferred and repeating callbacks; query elapsed time.
aura.collision Axis-aligned 2D collision detection (rect-rect, circle-circle, mixed).
aura.animation Additive deterministic timeline helper (create, play, pause, resume, seek, transition, onComplete, onEvent, setLoop, setSpeed, update, getState) plus constrained atlas/spritesheet helpers (registerAtlas, resolveAtlasFrame, createAtlasClip, stepAtlasClip, getAtlasClipState) with reason-coded invalid-operation responses.
aura.tween Additive deterministic tween/easing helper (create, pause, resume, cancel, onUpdate, onComplete, update, getState) with reason-coded invalid-operation responses.
aura.particles Additive deterministic particles-lite helper (emit, update, draw, stop, getState) with deterministic emitter IDs, constrained pool behavior, and reason-coded invalid-operation responses.
aura.anim2d Additive 2D animation state-machine helper (clip registration, deterministic transitions, completion callbacks).
aura.scene3d Additive 3D hierarchy helper for deterministic node parenting, world-transform composition, traversal ordering, and clip/skinning runtime helpers.
aura.scene Additive scene lifecycle/state helper for deterministic create/start/pause/resume/update transitions and reason-coded status payloads.
aura.tilemap Additive tilemap helper for constrained Tiled JSON import plus deterministic layer draw/culling in draw2d.
aura.multiplayer Additive multiplayer helper for host/client lifecycle, player/session state synchronization, and deterministic callback delivery.
aura.physics (optional) Optional physics module APIs including fixed-step scheduler controls and diagnostics when modules.physics=true.
aura.physics3d (optional) Optional 3D rigid-body APIs including bodies, fixed-step controls, queries, snapshots, and public joints when modules.physics=true.
aura.net (optional) Optional networking APIs (connect, websocket) and deterministic callback dispatch semantics when modules.network=true.
aura.debug Dev-only terminal/overlay helpers (log, drawRect, drawCircle, drawText) plus runtime inspector controls (enableInspector, inspectorStats). Explicit no-op in release mode.
aura.rgb / aura.rgba / aura.vec2 / aura.vec3 / aura.Color Top-level color and vector construction utilities.

Optional module note:

  • The supported optional runtime surfaces are physics, net, and multiplayer. aura.steam is no longer part of public v1 runtime surface.
  • aura.providers.steam is reserved for opt-in provider host variants such as provider-steam. Default AuraJS v1 builds do not expose aura.providers.steam, and provider namespaces are not part of optional-module readiness.

Legacy config migration note:

  • If legacy Steam module keys are detected (modules.steam, modules: ["steam"], or AURA_MODULE_STEAM), the runtime emits deterministic deprecation guidance with reason code legacy_modules_steam_ignored.
  • Steam keys are ignored (non-fatal) and do not enable any runtime namespace. Remove Steam keys from config/env and keep only supported optional modules: physics, network, and multiplayer.
  • aura.multiplayer.* is available only when modules.multiplayer=true.
  • If modules.multiplayer is disabled, every aura.multiplayer.* call throws deterministic guidance with reason code optional_module_multiplayer_disabled and includes the enablement hint (modules.multiplayer = true).
  • When enabled but no active session exists, multiplayer query/mutation APIs return deterministic safe defaults ([], 0, null, or false) and host-only mutators (kick, setPlayerData, setState) return false unless the local runtime is hosting.
  • Browser target note: current --target web builds do not support multiplayer. Browser builds that declare capabilities.optionalModules.multiplayer: true fail before aura.setup() with web_optional_module_unsupported, and any reached aura.multiplayer.* call throws a deterministic web_multiplayer_*_unsupported runtime error.
  • Browser target note: current --target web builds do not support the current 3D runtime namespaces. Browser builds that declare current 3D APIs in aura.capabilities.json fail before aura.setup() with web_required_api_missing, and any reached 3D call throws a deterministic web_<3d-namespace>_*_unsupported runtime error.
  • modules.steam and AURA_MODULE_STEAM are compatibility-only deprecation inputs; they are parsed only to produce deterministic migration warnings and have no runtime behavior effect.
  • Provider-scoped Steam runtime work lives behind custom host variants and does not change the default public v1 aura surface.

1.1 Optional Physics Fixed-Step Addendum

This addendum defines deterministic fixed-step orchestration semantics for optional physics runtime behavior.

Availability:

  • aura.physics.* is available only when the optional module is enabled (modules.physics=true).
  • If disabled, calls throw deterministic guidance errors that include enablement instructions.

Scheduler API:

aura.physics.configureStep(options?: {
  stepSeconds?: number,
  maxSubSteps?: number,
  auto?: boolean,
  resetAccumulator?: boolean
}): {
  stepSeconds: number,
  maxSubSteps: number,
  auto: boolean,
  stepCount: number,
  accumulatorCarry: number
}

Semantics:

  • stepSeconds must be a positive finite number.
  • maxSubSteps must be a positive integer.
  • auto=true means host frame loop advances physics using fixed-step accumulator semantics.
  • auto=false means physics advances only via aura.physics.step(dt), using the same accumulator semantics.
  • resetAccumulator=true clears carry-over time before the next step sequence.
  • Return payload includes scheduler config plus diagnostics from the most recent scheduler run:
    • stepCount: fixed sub-steps executed in the last scheduler integration.
    • accumulatorCarry: leftover dt (seconds) carried into the next integration.

Determinism contract:

  • Identical frame-time sequences with identical initial state MUST produce identical stepCount, accumulatorCarry, and body transforms.
  • Variable frame-time partitions that sum to the same total simulation time MUST converge within a stable floating-point tolerance for this runtime.

1.1A Optional Physics3D Joints Addendum

This addendum defines the public joint/constraint surface for the optional 3D physics runtime.

Availability:

  • aura.physics3d.* is available only when modules.physics=true.
  • If disabled, calls follow the existing optional-module guidance path for physics and do not expose a partial joint surface.

Canonical API:

type Physics3DJointKind = "revolute" | "ball" | "prismatic" | "fixed";

aura.physics3d.joint(
  kind: Physics3DJointKind,
  bodyA: number,
  bodyB: number,
  options?: {
    anchorA?: Vec3,
    anchorB?: Vec3,
    axis?: Vec3,
    limits?: { min: number, max: number },
    motor?: { targetVel: number, maxForce: number },
    rotation?: Vec3
  }
): {
  id: number,
  kind: Physics3DJointKind,
  bodyA: number,
  bodyB: number,
  getState(): Physics3DJointState | null,
  remove(): boolean,
  readonly state: Physics3DJointState | null
}

aura.physics3d.removeJoint(jointId: number): boolean

Compatibility note:

  • The public docs and examples use canonical kinds: revolute, ball, prismatic, and fixed.
  • Legacy aliases (hinge, slider, ball_socket, ball socket, ball-socket) are normalized for compatibility, but new game code should use the canonical names above.

Validation and option rules:

  • bodyA and bodyB must be positive integer body handles that currently exist.
  • bodyA and bodyB must not reference the same body.
  • axis is required for revolute and prismatic joints and must be a non-zero vector.
  • limits are supported only on revolute and prismatic joints.
  • motor is supported only on revolute and prismatic joints.
  • rotation is supported only on fixed joints.
  • Invalid kinds throw deterministic guidance that includes type must be revolute|ball|prismatic|fixed.
  • Invalid/missing body handles, malformed option objects, unsupported option families, or zero-length axes throw deterministic JS errors instead of silently degrading.

Observable state contract:

type Physics3DJointState = {
  kind: Physics3DJointKind,
  bodyA: number,
  bodyB: number,
  currentValue: number,
  currentValueKind:
    | "angle_radians"
    | "relative_rotation_radians"
    | "translation_meters"
    | "locked",
  motorActive: boolean,
  motorSupported: boolean,
  motorConfigured: boolean,
  limitsEnabled: boolean,
  atLowerLimit: boolean,
  atUpperLimit: boolean,
  anchorA: Vec3,
  anchorB: Vec3,
  axis: Vec3,
  rotation: Vec3,
  limits: {
    enabled: boolean,
    atLower: boolean,
    atUpper: boolean,
    min: number | null,
    max: number | null
  },
  motor: {
    supported: boolean,
    configured: boolean,
    active: boolean,
    targetVel: number | null,
    maxForce: number | null
  }
}

Semantics:

  • currentValueKind is angle_radians for revolute, relative_rotation_radians for ball, translation_meters for prismatic, and locked for fixed.
  • remove() and aura.physics3d.removeJoint(jointId) remove the joint and return true only on the first successful removal.
  • Repeated removal calls for an already-removed joint return false.
  • After removal, getState() / state return null.

Truthful limits:

  • This surface covers the rigid joint families listed above.
  • It does not claim soft-body, cloth, rope, vehicle, breakable-constraint, or high-level ragdoll authoring helpers.
  • Solver internals beyond the documented observable state are intentionally not exposed.

1.2 Optional Callback Dispatch Contract

This addendum defines deterministic callback ordering and repeat behavior for optional physics/network modules.

Availability:

  • aura.physics.* callback behavior is normative only when modules.physics=true.
  • aura.net.* callback behavior is normative only when modules.network=true.
  • If a module is disabled, method calls throw deterministic guidance errors (including the modules.* = true hint).

Physics callback contract (Body.onCollide(callback)):

  • Callback receives one argument: otherBodyId (number).
  • Dispatch order is deterministic by registration order for the contact pair under test.
  • For a two-body contact between one dynamic body and one static body, first-contact dispatch order is:
    1. dynamic body listener (a: + static body id)
    2. static body listener (b: + dynamic body id)
  • Callback dispatch is edge-triggered for contact start; persistent overlap across subsequent substeps MUST NOT emit duplicate bursts.
  • A new callback pair may fire after separation and re-contact.

Networking callback contract (Connection.onDisconnect(callback)):

  • onDisconnect listeners fire exactly once per connection close transition.
  • Listener invocation order is registration order.
  • First successful close()/disconnect() call returns true and triggers disconnect callback dispatch.
  • Repeated close calls return false and MUST NOT re-dispatch disconnect callbacks.

1.3 Animation State Machine Addendum

This addendum defines the additive aura.anim2d helper surface for deterministic 2D animation playback.

Non-blank scaffolded projects also ship a JS-first authored layer on top of these primitives under src/starter-utils/animation-2d.js. Prefer that helper layer when you want named sprite states and resolved draw frames without rebuilding clip wiring in feature code. Use raw aura.anim2d directly when you need lower-level state-machine ownership.

aura.anim2d.registerClip(
  name: string,
  frames: number[] | number,
  options?: { frameDuration?: number, loop?: boolean }
): boolean

aura.anim2d.createMachine(initialState?: string | null): number

aura.anim2d.defineState(
  machineId: number,
  stateName: string,
  clipOrFrames: string | number[] | number,
  options?: { frameDuration?: number, loop?: boolean }
): boolean

aura.anim2d.play(machineId: number, stateName: string): boolean
aura.anim2d.update(dt: number): void
aura.anim2d.getState(machineId: number): {
  machineId: number,
  state: string | null,
  frameIndex: number,
  frame: number | null,
  completed: boolean,
  loops: number,
  frameDuration: number,
  loop: boolean
} | null

aura.anim2d.onComplete(
  machineId: number,
  callback: (stateSnapshot: object) => void,
  order?: number
): boolean

Semantics:

  • Invalid IDs/names/clip references return false and never throw.
  • registerClip accepts either explicit frame arrays or a positive frame-count integer.
  • update(dt) advances frame playback for all machines in ascending machine-id order.
  • Completion callbacks are dispatched once for non-looping clips, ordered by ascending order, then registration order.
  • getState returns a deterministic snapshot suitable for gameplay polling and tooling.

1.4 Scene3D Transform Hierarchy Addendum

This addendum defines the additive aura.scene3d helper for deterministic transform hierarchy composition and clip runtime control. It does not replace immediate-mode aura.draw3d.drawMesh(...) ownership.

aura.scene3d.createNode(initialTransform?: {
  position?: { x?: number, y?: number, z?: number },
  rotation?: { x?: number, y?: number, z?: number },
  scale?: { x?: number, y?: number, z?: number }
} | null): number

aura.scene3d.removeNode(nodeId: number): boolean
aura.scene3d.setParent(nodeId: number, parentId: number | null): boolean
aura.scene3d.getParent(nodeId: number): number | null

aura.scene3d.setLocalTransform(nodeId: number, transform: {
  position?: { x?: number, y?: number, z?: number },
  rotation?: { x?: number, y?: number, z?: number },
  scale?: { x?: number, y?: number, z?: number }
}): boolean

aura.scene3d.getLocalTransform(nodeId: number): {
  position: { x: number, y: number, z: number },
  rotation: { x: number, y: number, z: number },
  scale: { x: number, y: number, z: number }
} | null

aura.scene3d.getWorldTransform(nodeId: number): {
  position: { x: number, y: number, z: number },
  rotation: { x: number, y: number, z: number },
  scale: { x: number, y: number, z: number }
} | null

aura.scene3d.traverse(
  rootIdOrCallback?: number | ((nodeId: number, world: object, local: object, parentId: number | null) => void),
  callback?: (nodeId: number, world: object, local: object, parentId: number | null) => void
): boolean

aura.scene3d.bindRenderNode(
  nodeId: number,
  meshHandle: number,
  materialHandle: number,
  options?: { visible?: boolean, layer?: number, cull?: boolean, cullRadius?: number }
): { ok: boolean, reason: string | null }

aura.scene3d.unbindRenderNode(nodeId: number): { ok: boolean, reason: string | null }
aura.scene3d.setNodeVisibility(nodeId: number, visible: boolean): { ok: boolean, reason: string | null }
aura.scene3d.setNodeLayer(nodeId: number, layer: number): { ok: boolean, reason: string | null }
aura.scene3d.setNodeCulling(nodeId: number, cull: boolean, options?: { cullRadius?: number }): { ok: boolean, reason: string | null }
aura.scene3d.setNodeLod(
  nodeId: number,
  levels: Array<{ distance: number, meshHandle?: number | null }>,
  options?: { fallbackMeshHandle?: number | null }
): { ok: boolean, reason: string | null }
aura.scene3d.clearNodeLod(nodeId: number): { ok: boolean, reason: string | null }
aura.scene3d.getNodeLod(nodeId: number): { ok: boolean, reason: string | null }
aura.scene3d.getLodState(): object
aura.scene3d.setCameraLayerMask(layerMask: number): { ok: boolean, reason: string | null }
aura.scene3d.setCullBounds(bounds: { min: { x: number, y: number, z: number }, max: { x: number, y: number, z: number } } | { x: number, y: number, z: number, width: number, height: number, depth: number }): { ok: boolean, reason: string | null }
aura.scene3d.clearCullBounds(): { ok: boolean, reason: string | null }
aura.scene3d.submitRenderBindings(options?: { layerMask?: number, cullBounds?: object | null }): { ok: boolean, reason: string | null }
aura.scene3d.getRenderSubmissionState(): object

aura.scene3d.loadGltfScene(
  path: string,
  options?: {
    bindRenderNodes?: boolean,
    layer?: number,
    visible?: boolean,
    cull?: boolean,
    cullRadius?: number
  }
): {
  ok: boolean,
  reason: string | null,
  importId?: number,
  path?: string,
  nodeCount?: number,
  primitiveCount?: number,
  materialCount?: number,
  rootSourceNodeIndices?: number[],
  orderFingerprint?: number,
  sceneFingerprint?: number,
  runtimeFingerprint?: number,
  nodeHandles?: Array<{
    sourceNodeIndex: number,
    nodeId: number,
    parentSourceNodeIndex: number | null,
    parentNodeId: number | null
  }>,
  primitiveBindings?: Array<{
    primitiveIndex: number,
    nodeId: number,
    meshHandle: number,
    materialHandle: number,
    sourceMeshIndex: number,
    sourcePrimitiveIndex: number,
    materialIndex: number | null
  }>,
  materials?: Array<{
    sourceMaterialIndex: number,
    name: string | null,
    baseColor: { r: number, g: number, b: number, a: number },
    metallic: number,
    roughness: number,
    materialHandle: number
  }>
}

aura.scene3d.getImportedScene(importId: number): {
  ok: boolean,
  reason: string | null,
  importId?: number,
  path?: string,
  nodeCount?: number,
  primitiveCount?: number,
  materialCount?: number,
  rootSourceNodeIndices?: number[],
  orderFingerprint?: number,
  sceneFingerprint?: number,
  runtimeFingerprint?: number,
  nodeHandles?: object[],
  primitiveBindings?: object[],
  materials?: object[]
}

aura.scene3d.getImportedSceneMetadata(importId: number): {
  defaultSceneIndex: number | null,
  scenes: Array<{
    name: string | null,
    rootNodeIndices: number[]
  }>
} | null

aura.scene3d.createClip(options: {
  nodeId: number,
  name?: string,
  duration: number,
  loop?: boolean,
  playing?: boolean,
  weight?: number,
  speed?: number,
  skinningInfluence?: number,
  boneCount?: number
}): { ok: boolean, reason: string | null, clipId?: number }

aura.scene3d.playClip(clipId: number): { ok: boolean, reason: string | null }
aura.scene3d.pauseClip(clipId: number): { ok: boolean, reason: string | null }
aura.scene3d.resumeClip(clipId: number): { ok: boolean, reason: string | null }
aura.scene3d.seekClip(clipId: number, time: number): { ok: boolean, reason: string | null }
aura.scene3d.setClipWeight(clipId: number, weight: number): { ok: boolean, reason: string | null }
aura.scene3d.setClipLoop(clipId: number, loop: boolean): { ok: boolean, reason: string | null }
aura.scene3d.setClipSkinning(clipId: number, options: { influence?: number, boneCount?: number }): { ok: boolean, reason: string | null }
aura.scene3d.crossfadeClips(fromClipId: number, toClipId: number, options: { duration: number, fromTime?: number, toTime?: number, eventTag?: string }): { ok: boolean, reason: string | null }
aura.scene3d.updateClips(dt: number): { ok: boolean, reason: string | null }
aura.scene3d.onClipEvent(clipId: number, callback: (event: object) => void, order?: number): { ok: boolean, reason: string | null, callbackId?: number }
aura.scene3d.getClipState(clipId: number): object | null

Semantics:

  • Node IDs are monotonically increasing positive integers; IDs are never recycled during a runtime session.
  • Invalid IDs or structurally invalid inputs return false/null and never throw.
  • setParent(node, null) detaches to root; self-parenting, missing-parent links, and cycle creation attempts return false.
  • Reparent/detach operations preserve the node's local transform; world transform is recomputed immediately from the new ancestry chain.
  • World transform composition is deterministic by ancestry chain with additive position/rotation and multiplicative scale.
  • Traversal order is deterministic: roots by ascending node ID; each node's children by ascending node ID (depth-first preorder).
  • setLocalTransform updates are visible immediately to getWorldTransform and the same-frame traverse callback stream.
  • Render-state controls are additive and reason-coded (invalid_layer, invalid_layer_mask, invalid_cull_bounds, missing_render_binding, etc.).
  • submitRenderBindings() deterministically filters bound nodes by visibility, camera layer mask, and optional cull bounds, then forwards surviving nodes to aura.draw3d.drawMesh(...) in ascending node ID order.
  • LOD selection is distance-based and deterministic: the selected level is the highest threshold distance <= cameraDistance (stable tie behavior at exact thresholds), with fallback to lower configured levels, optional fallbackMeshHandle, then base mesh.
  • Layer filtering uses a 32-bit camera mask (setCameraLayerMask) and per-node layer indices in [0, 31].
  • Bounds culling is deterministic sphere-vs-AABB using world position/scale and optional per-node cullRadius.
  • Runtime inspector telemetry includes additive submission counters for submitted/cull-filtered nodes (scene3dRuntime.submission.scene3d*) and LOD selection counters/active levels (scene3dRuntime.submission.scene3dLod*).
  • loadGltfScene(path, options?) imports .gltf/.glb scene hierarchy into additive scene3d nodes, binds mesh/material handles when bindRenderNodes !== false, and returns deterministic ordering/content/runtime fingerprints.
  • Native glTF import metadata helpers expose default-scene/root-node definitions (getImportedSceneMetadata) plus imported cameras/lights/animations for direct audit/query flows.
  • getImportedScene(importId) returns the imported-scene snapshot (nodeHandles, primitiveBindings, materials, fingerprints) for deterministic audit/query flows.
  • Native .gltf import resolves relative external buffer files next to the source asset and supports sparse animation accessors; unsupported URI schemes and malformed sparse payloads fail with stable import reason codes.
  • Import reason codes are deterministic and include: invalid_scene_path, invalid_scene_import_options, scene_import_unavailable, material_api_unavailable, scene_graph_unavailable, invalid_layer, invalid_visible_flag, invalid_cull_flag, invalid_cull_radius, scene_import_failed, invalid_scene_payload, scene_material_create_failed, scene_node_create_failed, scene_parent_missing, scene_parent_link_failed, render_binding_unavailable, scene_bind_failed, invalid_import_id, missing_import.
  • Inspector surface includes imported-scene aggregate telemetry at scene3dRuntime.importedScenes with deterministic last-import fingerprints (lastOrderFingerprint, lastSceneFingerprint, lastRuntimeFingerprint).
  • Clip runtime controls are deterministic and reason-coded (invalid_clip_options, invalid_clip_id, missing_clip, invalid_crossfade_duration, etc.).
  • Clip events are dispatched in ascending callback order, then registration order, and are exposed in inspector telemetry (scene3dRuntime.animation.*).
  • Skinning telemetry is additive only (runtime checksums/counters); it does not imply skeleton import/render ownership.
  • Helper is additive only: no scene ownership transfer, no implicit renderer replacement, and no hidden material/mesh resource ownership.
  • Explicit non-goals: no keep-world-on-reparent mode and no render graph/material ownership takeover.

1.5 Animation Timeline + Atlas Addendum

This addendum defines the additive aura.animation timeline helper surface. It complements aura.anim2d by providing deterministic timeline primitives with explicit reason-coded validation failures.

type AnimationResult =
  | {
      ok: true,
      reason: null,
      timelineId?: number,
      time?: number,
      loop?: boolean,
      speed?: number,
      atlasId?: number,
      clipId?: number,
      frameKey?: string | null,
      frameIndex?: number | null,
      frameX?: number,
      frameY?: number,
      frameW?: number,
      frameH?: number,
      duration?: number,
      frameCount?: number,
      clipCount?: number,
      completed?: boolean,
      loops?: number
    }
  | { ok: false, reason:
      | "invalid_options"
      | "invalid_duration"
      | "invalid_time"
      | "invalid_transition_options"
      | "invalid_transition_duration"
      | "invalid_loop_flag"
      | "invalid_speed"
      | "invalid_callback"
      | "invalid_playing_flag"
      | "invalid_timeline_id"
      | "missing_timeline"
      | "invalid_dt"
      | "invalid_atlas_payload"
      | "invalid_atlas_image"
      | "invalid_atlas_frames"
      | "invalid_atlas_frame_key"
      | "invalid_atlas_frame_entry"
      | "invalid_atlas_frame_rect"
      | "invalid_atlas_frame_duration"
      | "unsupported_atlas_frame_flags"
      | "invalid_atlas_clips"
      | "invalid_atlas_clip_key"
      | "invalid_atlas_clip"
      | "invalid_atlas_clip_frames"
      | "invalid_atlas_clip_duration"
      | "invalid_atlas_clip_loop"
      | "missing_atlas_frame"
      | "invalid_atlas_id"
      | "missing_atlas"
      | "invalid_frame_key"
      | "missing_frame"
      | "invalid_clip_options"
      | "invalid_clip_key"
      | "missing_clip"
      | "invalid_clip_duration"
      | "invalid_clip_loop"
      | "invalid_clip_frame_index"
      | "invalid_clip_id"
    };

aura.animation.create(options: {
  duration: number,
  time?: number,
  loop?: boolean,
  speed?: number,
  playing?: boolean
}): AnimationResult

aura.animation.play(timelineId: number): AnimationResult
aura.animation.pause(timelineId: number): AnimationResult
aura.animation.resume(timelineId: number): AnimationResult
aura.animation.seek(timelineId: number, time: number): AnimationResult
aura.animation.transition(timelineId: number, options: {
  duration: number,
  targetTime?: number,
  eventTag?: string
}): AnimationResult
aura.animation.crossfade(timelineId: number, options: {
  duration: number,
  fromTime?: number,
  toTime?: number,
  targetTime?: number,
  eventTag?: string
}): AnimationResult
aura.animation.onComplete(timelineId: number, callback: (state: object) => void, order?: number): AnimationResult
aura.animation.onEvent(timelineId: number, callback: (event: object) => void, order?: number): AnimationResult
aura.animation.setLoop(timelineId: number, loop: boolean): AnimationResult
aura.animation.setSpeed(timelineId: number, speed: number): AnimationResult
aura.animation.update(dt: number): AnimationResult

aura.animation.getState(timelineId: number): {
  timelineId: number,
  duration: number,
  time: number,
  normalizedTime: number,
  playing: boolean,
  loop: boolean,
  speed: number,
  completed: boolean,
  loops: number,
  transition: {
    active: true,
    mode: "transition" | "crossfade",
    startTime: number,
    duration: number,
    elapsed: number,
    targetTime: number,
    blend: number,
    paused: boolean
  } | null
} | null

aura.animation.registerAtlas(payload: {
  image: string,
  frames: Record<string, {
    x?: number,
    y?: number,
    w?: number,
    h?: number,
    frame?: { x: number, y: number, w: number, h: number },
    duration?: number,
    rotated?: boolean,
    trimmed?: boolean
  }>,
  clips?: Record<string, {
    frames: string[],
    frameDuration?: number,
    loop?: boolean
  }>
}): AnimationResult

aura.animation.resolveAtlasFrame(atlasId: number, frameKey: string): AnimationResult

aura.animation.createAtlasClip(options: {
  atlasId: number,
  clipKey?: string,
  frames?: string[],
  frameDuration?: number,
  loop?: boolean,
  playing?: boolean,
  frameIndex?: number
}): AnimationResult

aura.animation.stepAtlasClip(clipId: number, dt: number): AnimationResult

aura.animation.getAtlasClipState(clipId: number): {
  clipId: number,
  atlasId: number,
  clipKey: string | null,
  image: string,
  frameKey: string,
  frameIndex: number,
  frameCount: number,
  frameX: number,
  frameY: number,
  frameW: number,
  frameH: number,
  frameDuration: number,
  elapsedInFrame: number,
  playing: boolean,
  loop: boolean,
  completed: boolean,
  loops: number
} | null

Semantics:

  • Timeline IDs are monotonically increasing positive integers and deterministic by creation order.
  • update(dt) processes timelines in ascending timeline-id order.
  • For non-looping timelines, reaching duration sets completed=true and playing=false.
  • For looping timelines, time wraps by modulo duration; wrap count accumulates in loops.
  • crossfade(...) is an additive mixer-style helper: optional fromTime pins blend start, and toTime (or targetTime) sets blend destination.
  • Callback dispatch order per update(dt) tick is deterministic:
    • timeline ID ascending
    • phase precedence: transition-complete events -> marker events -> loop events -> completion callbacks
    • callback order ascending
    • callback registration order
  • Transition phase event payloads include blendMode ("transition" or "crossfade"). Completion event type is transition_complete for transition(...) and crossfade_complete for crossfade(...).
  • pause freezes active transitions; resume unfreezes them without resetting blend progress.
  • Finite seek during an active transition deterministically cancels the transition and applies the target time.
  • If a looping timeline finishes a transition and still has residual same-tick playback progress, loop events dispatch after marker events within the same deterministic callback queue.
  • crossfade(...) reuses transition validation reason codes (invalid_transition_options, invalid_transition_duration, invalid_time) for deterministic failure handling.
  • Identical timeline setup + identical variable-dt sequences MUST produce identical state snapshots.
  • registerAtlas(...) ingests a constrained atlas/spritesheet metadata shape:
    • frames must be a non-empty object map.
    • each frame entry must include finite x,y,w,h (w/h > 0) directly or inside frame.
    • rotated=true and trimmed=true are explicitly rejected in this stage (unsupported_atlas_frame_flags).
    • optional clips entries must reference existing frame keys only.
  • resolveAtlasFrame(...) returns deterministic source-frame coordinates suitable for aura.draw2d.sprite(..., { frameX, frameY, frameW, frameH }).
  • createAtlasClip(...) + stepAtlasClip(...) provide deterministic frame stepping from ingested atlas metadata; repeated identical dt sequences produce identical frame/loop traces.
  • Invalid operations return stable reason-coded failures via { ok: false, reason: <code> } and never throw.

Migration guidance:

  • Replace manual frame-index interpolation loops with transition(...) plus ordered onEvent hooks.
  • Prefer crossfade(...) when porting three.js-style mixer blend flows (explicit fromTime -> toTime handoff in a single deterministic step).
  • Map previous anim2d state-exit effects to onEvent marker/loop callbacks and map clip-end effects to onComplete.
  • After seek(...), re-issue transition intent explicitly if a blend should continue.
  • For atlas-authored spritesheets, ingest frame metadata once with registerAtlas(...), then use resolveAtlasFrame(...) / stepAtlasClip(...) to keep draw2d frame selection deterministic.

1.6 Tween/Easing Addendum

This addendum defines additive aura.tween helpers for deterministic scalar tween playback with constrained easing and reason-coded validation failures.

Non-blank scaffolded projects also ship higher-level wrappers for common 2D motion and feedback:

  • src/starter-utils/tween-2d.js for authored move/fade/scale/pulse/punch flows
  • src/starter-utils/combat-feedback-2d.js for floating text, hit sparks, hit flashes, and hover/lift feedback

Prefer those scaffold helpers for common 2D gameplay polish. Use raw aura.tween directly when you need lower-level tween ownership.

type TweenResult =
  | { ok: true, reason: null, tweenId?: number, value?: number, cancelled?: boolean, listenerId?: number }
  | { ok: false, reason:
      | "invalid_options"
      | "invalid_duration"
      | "invalid_from_value"
      | "invalid_to_value"
      | "invalid_easing"
      | "invalid_playing_flag"
      | "invalid_tween_id"
      | "missing_tween"
      | "invalid_callback"
      | "invalid_dt"
    };

aura.tween.create(options: {
  from: number,
  to: number,
  duration: number,
  easing?: "linear" | "easeInQuad" | "easeOutQuad" | "easeInOutQuad" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic",
  playing?: boolean
}): TweenResult

aura.tween.pause(tweenId: number): TweenResult
aura.tween.resume(tweenId: number): TweenResult
aura.tween.cancel(tweenId: number): TweenResult
aura.tween.onUpdate(tweenId: number, callback: (event: object) => void, order?: number): TweenResult
aura.tween.onComplete(tweenId: number, callback: (state: object) => void, order?: number): TweenResult
aura.tween.update(dt: number): TweenResult

aura.tween.getState(tweenId: number): {
  tweenId: number,
  duration: number,
  elapsed: number,
  progress: number,
  from: number,
  to: number,
  value: number,
  easing: string,
  playing: boolean,
  completed: boolean,
  cancelled: boolean
} | null

Semantics:

  • Tween IDs are monotonically increasing positive integers and deterministic by creation order.
  • update(dt) processes tweens in ascending tween-id order.
  • Easing is constrained to the listed set; unsupported easing values return invalid_easing.
  • Tween interpolation is deterministic scalar lerp with clamped progress [0, 1]; completion sets completed=true and playing=false.
  • Callback dispatch order per update(dt) tick is deterministic:
    • tween ID ascending
    • phase precedence: update callbacks -> completion callbacks
    • callback order ascending
    • callback registration order
  • pause freezes progress; resume continues from current elapsed/progress state.
  • cancel removes the tween deterministically; subsequent getState for that id returns null.
  • Invalid operations return stable reason-coded failures via { ok: false, reason: <code> } and never throw.

1.7 Particles-lite Addendum

This addendum defines additive aura.particles helpers for deterministic 2D particles-lite orchestration suitable for gameplay polish and constrained effects.

type ParticlesResult =
  | { ok: true, reason: null, emitterId?: number, spawned?: number, particles?: number, drawnParticles?: number, emitterCount?: number, activeEmitters?: number, updatedEmitters?: number, emitted?: number, stopped?: boolean }
  | { ok: false, reason:
      | "invalid_options"
      | "too_many_emitters"
      | "invalid_position"
      | "invalid_count"
      | "invalid_life"
      | "invalid_size"
      | "invalid_speed_range"
      | "invalid_direction"
      | "invalid_spread"
      | "invalid_loop_flag"
      | "invalid_rate"
      | "invalid_color"
      | "invalid_dt"
      | "invalid_emitter_id"
      | "missing_emitter"
      | "invalid_draw_options"
      | "invalid_max_particles"
    };

aura.particles.emit(options: {
  x?: number,
  y?: number,
  count?: number,         // 1..256
  life?: number,          // seconds, > 0
  size?: number,          // radius, > 0
  speedMin?: number,      // > 0
  speedMax?: number,      // >= speedMin
  direction?: number,     // radians
  spread?: number,        // radians, [0, 2Ï€]
  loop?: boolean,
  rate?: number,          // particles/sec, > 0
  color?: { r: number, g: number, b: number, a?: number }
}): ParticlesResult

aura.particles.update(dt: number): ParticlesResult
aura.particles.draw(emitterIdOrOptions?: number | { maxParticles?: number }): ParticlesResult
aura.particles.stop(emitterId: number): ParticlesResult

aura.particles.getState(emitterId: number): {
  emitterId: number,
  active: boolean,
  loop: boolean,
  rate: number,
  particles: number,
  position: { x: number, y: number },
  defaults: {
    life: number,
    size: number,
    speedMin: number,
    speedMax: number,
    direction: number,
    spread: number
  }
} | null

Semantics:

  • Emitter IDs are monotonically increasing positive integers and deterministic by emit order.
  • update(dt) processes emitters in ascending emitter-id order and advances all live particles deterministically.
  • Pool behavior is constrained and deterministic:
    • max emitters: 256
    • max particles per emitter: 2048
    • max loop-spawned particles per update tick per emitter: 512
  • Non-loop emitters self-retire when their particle pool reaches zero.
  • draw(...) delegates particle render submission to aura.draw2d.circleFill(...); camera transforms apply through normal draw2d ownership/call-order behavior.
  • This helper remains a bounded CPU-submitted effects path, not a GPU-instanced particle renderer; large emitters should be treated as capped gameplay polish, not an unbounded throughput surface.
  • Invalid operations return stable reason-coded failures via { ok: false, reason: <code> } and never throw.

1.8 Tilemap Import + Draw/Culling Addendum

This addendum defines additive aura.tilemap helpers for constrained Tiled JSON import and deterministic draw2d submission ordering.

aura.tilemap.import(source: object | string): number
aura.tilemap.unload(mapId: number): boolean

aura.tilemap.getInfo(mapId: number): {
  mapId: number,
  width: number,
  height: number,
  tileWidth: number,
  tileHeight: number,
  layerCount: number,
  objectLayerCount: number,
  objectCount: number,
  mapPropertyCount: number,
  tilesetCount: number
} | null

aura.tilemap.drawLayer(
  mapId: number,
  layer: number | string,
  options?: {
    x?: number,
    y?: number,
    cull?: boolean,
    camera?: { x?: number, y?: number, width?: number, height?: number },
    view?: { x?: number, y?: number, width?: number, height?: number },
    includeHidden?: boolean
  }
): {
  mapId: number,
  layerName: string,
  layerIndex: number,
  consideredTiles: number,
  drawnTiles: number,
  culledTiles: number
} | null

aura.tilemap.draw(
  mapId: number,
  options?: {
    x?: number,
    y?: number,
    cull?: boolean,
    camera?: { x?: number, y?: number, width?: number, height?: number },
    view?: { x?: number, y?: number, width?: number, height?: number },
    includeHidden?: boolean
  }
): {
  mapId: number,
  layerOrder: string[],
  consideredTiles: number,
  drawnTiles: number,
  culledTiles: number,
  layers: Array<{
    mapId: number,
    layerName: string,
    layerIndex: number,
    consideredTiles: number,
    drawnTiles: number,
    culledTiles: number
  }>
} | null

aura.tilemap.setTile(
  mapId: number,
  layer: number | string,
  tile: { x: number, y: number } | number,
  yOrGid?: number,
  gid?: number
): {
  ok: boolean,
  error: string | null,
  reasonCode: string | null,
  mapId: number,
  layerName: string,
  layerIndex: number,
  x: number,
  y: number,
  previousGid: number,
  gid: number,
  changed: boolean,
  mutatedTiles: number,
  changedTiles: number
}

aura.tilemap.setRegion(...)
aura.tilemap.removeRegion(...)
aura.tilemap.replaceRegion(...)

aura.tilemap.setTileCollision(
  mapId: number,
  layer: number | string,
  tileOrRegion: { x: number, y: number, w?: number, h?: number } | number,
  yOrW?: number,
  hOrCollision?: number | boolean,
  collisionOrH?: boolean | number,
  collision?: boolean
): {
  ok: boolean,
  error: string | null,
  reasonCode: string | null,
  mapId: number,
  layerName: string,
  layerIndex: number,
  collision: boolean,
  mutatedTiles: number,
  changedTiles: number
}

aura.tilemap.setLayerFlags(...)
aura.tilemap.setLayerVisibility(...)
aura.tilemap.setLayerCollision(...)

aura.tilemap.queryPoint(...)
aura.tilemap.queryAABB(...)
aura.tilemap.queryRay(...)
aura.tilemap.queryRaycast(...)

aura.tilemap.queryObjects(
  mapId: number,
  options?: {
    layer?: number | string,
    name?: string,
    type?: string,
    className?: string,
    class?: string,
    visible?: boolean,
    includeHidden?: boolean,
    property?: string | { name: string, value?: string | number | boolean | null },
    properties?: Record<string, string | number | boolean | null>
  }
): {
  ok: boolean,
  hit: boolean,
  count: number,
  hits: object[],
  error: string | null,
  reasonCode: string | null
}

aura.tilemap.queryObjectsAtPoint(mapId: number, point: { x: number, y: number } | number, y?: number, options?: object): {
  ok: boolean,
  hit: boolean,
  count: number,
  hits: object[],
  point: { x: number, y: number },
  error: string | null,
  reasonCode: string | null
}

aura.tilemap.queryObjectsInAABB(mapId: number, aabb: { x: number, y: number, w: number, h: number } | number, y?: number, w?: number, h?: number, options?: object): {
  ok: boolean,
  hit: boolean,
  count: number,
  hits: object[],
  range: { x: number, y: number, w: number, h: number },
  error: string | null,
  reasonCode: string | null
}

Supported import schema (constrained Tiled JSON subset):

  • Required map fields: width, height, tilewidth, tileheight, layers, tilesets.
  • Supported layer types: tilelayer, objectgroup (other layer types are ignored by this helper).
  • Layer data must be an uncompressed integer array with length width * height (layer width/height fallback to map width/height when omitted).
  • Object layers support deterministic import of object arrays with constrained fields (id, name, type/class, x, y, width, height, rotation, visible, gid, point, ellipse, polygon, polyline, properties).
  • Object template and text payloads are rejected in this stage.
  • Supported tileset fields: firstgid, image, columns required; tilewidth/tileheight optional (fallback to map tile size); tilecount, margin, spacing optional.
  • Properties are accepted on map/layer/object/tileset as either:
    • object map ({ key: primitive }), or
    • Tiled-style property array ([{ name, value, ... }]).
    • Allowed property value primitives: string, finite number, boolean, null.
  • Unsupported layer compression and non-csv/non-empty encoding deterministically fail import.

Determinism and failure semantics:

  • draw() layer submission order is deterministic and follows normalized layer array order.
  • draw()/drawLayer() only submit tile layers; object layers remain imported metadata and do not affect tile draw ordering.
  • Culling uses axis-aligned tile rectangle vs camera/view rectangle intersection; when no valid rectangle is provided, culling is disabled for that call.
  • Orthogonal maps use a padded visible tile window before fine tile intersection checks so camera-bound draws avoid full-layer scans in the common case.
  • Isometric and staggered maps keep deterministic full-order traversal; throughput expectations are lower there than the orthogonal fast path.
  • Mutation helpers (setTile, setRegion, removeRegion, replaceRegion, setTileCollision, layer-flag setters) are non-throwing and return reason-coded { ok, reasonCode } payloads on invalid input.
  • Tile collision queries respect deterministic per-tile collision overrides set by setTileCollision(...).
  • Object query helpers (queryObjects*) are non-throwing and return deterministic hit ordering: layerIndex, then object id, then source object order.
  • import() throws deterministic Error messages prefixed with aura.tilemap.import: <reason>.
  • getInfo()/draw()/drawLayer() return null for invalid or unloaded map handles; unload() returns false for invalid/missing handles.
  • Helper is additive only: draw submission is delegated to aura.draw2d.sprite(...) and remains subject to normal draw-phase ownership rules.

Dev-only Debug Helpers

aura.debug.log(...values: any[]): void
aura.debug.drawRect(x: number, y: number, w: number, h: number, color?: ColorLike): void
aura.debug.drawCircle(x: number, y: number, radius: number, color?: ColorLike): void
aura.debug.drawText(text: string, x: number, y: number, color?: ColorLike): void
aura.debug.enableInspector(enabled?: boolean): boolean
aura.debug.inspectorStats(): {
  enabled: boolean,
  frameCount: number,
  frame: { fps: number, deltaSeconds: number, elapsedSeconds: number },
  window: { width: number, height: number, pixelRatio: number },
  queues: { draw2dPending: number, debugOverlayPending: number },
  phase2: {
    animation: { active: number, total: number, callbackQueueDepth: number } | null,
    tilemap: { active: number, total: number, callbackQueueDepth: number } | null,
    scene3d: { active: number, total: number, callbackQueueDepth: number } | null,
    particles: { active: number, total: number, callbackQueueDepth: number } | null
  }
}
  • In dev mode (AURA_MODE not set to release), helpers are active.
  • In release mode (AURA_MODE=release), all aura.debug.* helpers are deterministic no-op and never throw.
  • Overlay primitives render after gameplay draw calls, so they appear on top of the frame and do not mutate gameplay draw command state.
  • enableInspector(true) activates a lightweight runtime inspection overlay in draw phase; enableInspector(false) disables and resets inspector frame counters.
  • inspectorStats() returns deterministic frame/window/queue counters suitable for dev-mode runtime diagnostics.
  • phase2 is always present in the inspector stats payload.
  • When the inspector is enabled, phase2.animation, phase2.tilemap, phase2.scene3d, and phase2.particles are deterministic non-negative counter rows when those helper namespaces are available.
  • In default-disabled mode, phase-2 rows may be null to preserve low-overhead baseline behavior.

1.9 Authored App-Shell Addendum

Registry-backed AuraJS projects also ship a JS-first app-shell layer above the low-level namespaces:

  • src/runtime/scene-flow.js
  • src/runtime/screen-shell.js
  • src/starter-utils/triggers.js

These helpers are scaffold/runtime surfaces, not new aura.* namespaces.

Scene Flow

The registry-backed runtime owns deterministic scene-stack helpers for common app-style flow:

  • replace(sceneId, data?)
  • push(sceneId, data?)
  • pop(result?)
  • current()
  • canPop()
  • getState()

Returned state snapshots use schema aurajs.scene-flow.v1 and expose:

  • currentSceneId
  • current
  • stack
  • depth
  • canPop
  • mutationCount

Canonical reason codes:

  • scene_flow_ok
  • scene_flow_invalid_scene_id
  • scene_flow_unknown_scene
  • scene_flow_cannot_pop_root

Screen Shell

The same runtime mounts authored screens through one explicit model:

  • one persistent HUD slot
  • one optional overlay slot
  • one modal stack

The runtime screen-shell surface is:

  • setHud(screenId, data?)
  • clearHud()
  • showOverlay(screenId, data?)
  • clearOverlay()
  • pushModal(screenId, data?)
  • popModal(result?)
  • getState()

Returned state snapshots use schema aurajs.screen-shell.v1 and expose:

  • hud
  • overlay
  • modals
  • modalDepth
  • mutationCount

Canonical reason codes:

  • screen_shell_ok
  • screen_shell_invalid_screen_id
  • screen_shell_unknown_screen
  • screen_shell_empty_modal_stack

Trigger Helpers

Non-blank starters also ship shared trigger helpers for common 2D/3D gameplay events without forcing every game to hand-roll previous-frame overlap state.

The helper layer covers:

  • 2D rect triggers
  • 3D proximity triggers
  • 3D volume triggers
  • persistent trigger-tracker state
  • interaction-prompt state derived from active triggers

Canonical schemas and reason codes:

  • aurajs.trigger-tracker.v1
  • aurajs.interaction-prompt.v1
  • trigger_step_ok
  • trigger_invalid_definition
  • trigger_invalid_subject
  • trigger_interaction_ready
  • trigger_interaction_accepted
  • trigger_interaction_unavailable

Registry-Backed Runtime Context

Registry-backed starter apps expose the app-shell layer to authored scenes through the runtime app context:

  • replaceScene(sceneId, data?)
  • pushScene(sceneId, data?)
  • popScene(result?)
  • canPopScene()
  • setHudScreen(screenId, data?)
  • clearHudScreen()
  • showOverlayScreen(screenId, data?)
  • clearOverlayScreen()
  • pushModalScreen(screenId, data?)
  • popModalScreen(result?)
  • getSceneFlowState()
  • getScreenShellState()
  • inspectProject()

Truthful boundary:

  • This addendum productizes the authored app shell for scene flow, mounted screens, and reusable triggers.
  • It does not replace low-level aura.scene, promise a visual editor, add dialogue/inventory/save systems, or introduce hosted preview/deploy surfaces.

2. Lifecycle Callbacks

Lifecycle callbacks are defined by the game developer on the global aura object and invoked by the Rust host. The host never throws if a lifecycle callback is undefined -- it silently skips the call.

Callback Invocation Order (per frame)

The per-frame order is:

  1. Timer callbacks (in registration order)
  2. Lifecycle events (onResize, onFocus, onBlur) -- fired if SDL2 events occurred
  3. aura.update(dt)
  4. aura.draw()

2.1 aura.setup()

aura.setup = function(): void
Property Value
Called when Once, after the JS entrypoint has executed but before the first frame.
Arguments None.
Return value Ignored.
Async support The host awaits the return value if it is a Promise. This allows await in setup for asset loading.
Error behavior If setup() throws, the error is caught. Dev mode: error overlay displayed, frame loop starts (update/draw are skipped while overlay is active). Release mode: error logged to error.log, crash dialog shown, process exits.
If undefined Silently skipped. The frame loop starts immediately.

2.2 aura.update(dt)

aura.update = function(dt: number): void
Property Value
Called when Once per frame, after timer callbacks and lifecycle events, before draw().
Arguments dt -- time in seconds (float) since the last frame. First frame receives the time since setup() completed. Typical values: 0.016 (60 FPS), 0.033 (30 FPS).
Return value Ignored.
Error behavior Exception caught per-callback. Dev mode: error recorded in overlay, update() skipped on subsequent frames until hot-reload clears the error. Release mode: error logged, crash dialog, exit.
Frame timeout If update() exceeds the frame timeout (default: 5 seconds), a timeout error is raised (cooperative).
If undefined Silently skipped.

2.3 aura.draw()

aura.draw = function(): void
Property Value
Called when Once per frame, after update(). All aura.draw2d.* calls must happen inside this callback.
Arguments None.
Return value Ignored.
Error behavior Same as update(). Dev mode: error overlay replaces the frame. Release mode: log, dialog, exit.
Frame timeout Same as update() (default: 5 seconds).
If undefined Silently skipped. The screen is cleared to the default background color ({r: 0.08, g: 0.08, b: 0.12, a: 1.0}).

2.4 aura.onResize(width, height)

aura.onResize = function(width: number, height: number): void
Property Value
Called when The window is resized by the user or programmatically. Fired before update() on the frame the resize occurs. May fire multiple times per frame if multiple resize events are queued.
Arguments width -- new window width in logical pixels (points). height -- new window height in logical pixels.
Return value Ignored.
Error behavior Exception caught and recorded as a lifecycle error. Dev mode: logged, overlay shown. Release mode: logged, exit.
If undefined Silently skipped. The renderer still reconfigures its surface internally.

2.5 aura.onFocus()

aura.onFocus = function(): void
Property Value
Called when The window gains input focus. Fired after the runtime clears any pending relative mouse delta for the regained-focus frame.
Arguments None.
Return value Ignored.
Error behavior Same as onResize.
Lifecycle semantics Does not automatically unlock the cursor or mutate cursor visibility preference. If the game wants to unlock on focus regain, it must do so explicitly.
If undefined Silently skipped.

2.6 aura.onBlur()

aura.onBlur = function(): void
Property Value
Called when The window loses input focus. Fired after the runtime clears held/pressed transient input state and relative mouse delta for the blur transition.
Arguments None.
Return value Ignored.
Error behavior Same as onResize.
Lifecycle semantics Does not automatically unlock the cursor or mutate cursor visibility preference. Games that want menu-style behavior on blur should unlock explicitly in aura.onBlur().
If undefined Silently skipped.

3. aura.window

Window management namespace. All sizes are in logical pixels (points). On HiDPI displays, physical pixels differ from logical pixels by the pixel ratio.

3.1 aura.window.setTitle(title)

aura.window.setTitle(title: string): void
Property Value
Description Set the window title bar text.
Arguments title -- string.
Return value undefined.
Validation If title is not a string, it is coerced via String(title). If coercion fails or result is empty, a warning is logged and the call is a no-op.
Error behavior If the underlying SDL2 call fails, a warning is logged. Never throws.

3.2 aura.window.setSize(width, height)

aura.window.setSize(width: number, height: number): void
Property Value
Description Resize the window to the given logical dimensions.
Arguments width -- positive integer (clamped to >= 1). height -- positive integer (clamped to >= 1).
Return value undefined.
Validation Non-number arguments: warning logged, call skipped. Values <= 0: clamped to 1 with warning. Non-integer values: truncated via Math.floor.
Error behavior SDL2 failure: warning logged. Never throws.
Side effect Triggers onResize callback on the next frame.

3.3 aura.window.getSize()

aura.window.getSize(): { width: number, height: number }
Property Value
Description Get the current window size in logical pixels.
Arguments None.
Return value Plain object { width: number, height: number }.
Error behavior Never throws. Always returns a valid object.

3.4 aura.window.setFullscreen(enabled)

aura.window.setFullscreen(enabled: boolean): void
Property Value
Description Enter or exit borderless fullscreen (desktop fullscreen mode).
Arguments enabled -- boolean.
Return value undefined.
Validation Non-boolean: coerced via truthiness (!!enabled).
Error behavior SDL2 failure: warning logged. Never throws.
Side effect Triggers onResize callback on the next frame with the new dimensions.

3.5 aura.window.getPixelRatio()

aura.window.getPixelRatio(): number
Property Value
Description Get the ratio of physical pixels to logical pixels. Returns 1.0 on standard displays, 2.0 on Retina/HiDPI.
Arguments None.
Return value number (always >= 1.0).
Error behavior Never throws. Falls back to 1.0 if the window has zero logical width.

3.6 aura.window.getFPS()

aura.window.getFPS(): number
Property Value
Description Get the current frames-per-second, averaged over a rolling window.
Arguments None.
Return value number (>= 0). Returns 0 on the first frame before enough samples exist.
Error behavior Never throws.

3.7 aura.window.setCursorVisible(visible)

aura.window.setCursorVisible(visible: boolean): void
Property Value
Description Request whether the OS cursor should be visible.
Arguments visible -- boolean.
Return value undefined.
Validation Non-boolean: coerced via truthiness (!!visible).
Error behavior Best-effort. Unsupported runtimes may no-op, but the call must never throw.
Lifecycle semantics Cursor visibility preference is tracked independently from cursor lock. Locking may still hide the effective cursor while locked, but it does not overwrite the stored visibility preference. Games that need FPS-style control should explicitly pair setCursorVisible(false) with setCursorLocked(true) rather than relying on one call to imply the other.

3.8 aura.window.setCursorLocked(locked)

aura.window.setCursorLocked(locked: boolean): void
Property Value
Description Request cursor lock/capture for FPS-style relative mouse look.
Arguments locked -- boolean.
Return value undefined.
Validation Non-boolean: coerced via truthiness (!!locked).
Error behavior Best-effort. Unsupported runtimes may no-op or degrade gracefully, but the call must never throw.
Lifecycle semantics Entering or leaving cursor lock clears the current-frame relative mouse delta before game code can observe it. This prevents a phantom first-frame camera jump when lock state changes. On runtimes that also track absolute cursor position, unlocking may resynchronize absolute position after clearing delta. Focus/blur callbacks do not synthesize lock changes; games that want unlock-on-blur must implement that policy explicitly.

4. aura.input

Input polling namespace. All functions return the state at the start of the current frame (after SDL2 event polling). Key and button names are case-insensitive.

Key Names

Canonical key names stored in the runtime are lowercased:

  • Letters: "a" through "z"
  • Digits: "0" through "9"
  • Arrows: "arrowup", "arrowdown", "arrowleft", "arrowright"
  • Modifiers: "shift", "control", "alt", "meta"
  • Special: "space", "enter", "escape", "tab", "backspace", "delete"
  • Function keys: "f1" through "f12"

Accepted aliases are normalized before lookup:

  • Arrows: "up"/"uparrow" -> "arrowup", "down"/"downarrow" -> "arrowdown", "left"/"leftarrow" -> "arrowleft", "right"/"rightarrow" -> "arrowright"
  • Shift: "lshift", "rshift", "leftshift", "rightshift", "shiftleft", "shiftright" -> "shift"
  • Control: "ctrl", "lctrl", "rctrl", "leftctrl", "rightctrl", "controlleft", "controlright" -> "control"
  • Alt: "lalt", "ralt", "leftalt", "rightalt", "altleft", "altright", "option", "loption", "roption", "leftoption", "rightoption" -> "alt"
  • Meta: "cmd", "command", "meta", "lmeta", "rmeta", "super", "win", "windows" -> "meta"

Mouse Buttons

Value Button
0 Left
1 Middle
2 Right

4.1 aura.input.isKeyDown(key)

aura.input.isKeyDown(key: string): boolean
Property Value
Description Returns true if the key is currently held down.
Arguments key -- string key name (case-insensitive).
Return value boolean.
Validation key must be a string. Input is trimmed/lowercased and aliases are normalized. Non-string or unknown key returns false (no warning).
Error behavior Never throws. Invalid input always returns false.

4.2 aura.input.isKeyPressed(key)

aura.input.isKeyPressed(key: string): boolean
Property Value
Description Returns true if the key transitioned from up to down this frame. Only true for a single frame.
Arguments key -- string key name (case-insensitive).
Return value boolean.
Validation Same as isKeyDown. Invalid key returns false.
Error behavior Never throws.

4.3 aura.input.isKeyReleased(key)

aura.input.isKeyReleased(key: string): boolean
Property Value
Description Returns true if the key transitioned from down to up this frame. Only true for a single frame.
Arguments key -- string key name (case-insensitive).
Return value boolean.
Validation Same as isKeyDown. Invalid key returns false.
Error behavior Never throws.

4.3A aura.input.getTextInput()

aura.input.getTextInput(): string
Property Value
Description Returns the sanitized printable text captured during the current frame from native text-input events. This is the canonical text-entry seam for retained authored aura.ui.input(...) widgets.
Arguments None.
Return value string. Returns "" when no text input was produced this frame.
Validation Never throws. Newlines, tabs, and non-printable bytes are not surfaced through this public string.
Lifecycle semantics The returned value is frame-local and is cleared before the next frame begins. Key state (isKeyPressed, isKeyDown) remains the canonical surface for navigation, shortcuts, backspace/delete, and submit/cancel semantics.

4.4 aura.input.getMousePosition()

aura.input.getMousePosition(): { x: number, y: number }
Property Value
Description Get the mouse cursor position in logical pixels relative to the top-left corner of the window.
Arguments None.
Return value Plain object { x: number, y: number }. Coordinates may be negative or exceed window bounds if the cursor is outside the window.
Error behavior Never throws. Always returns a valid object.

4.5 aura.input.getMouseDelta()

aura.input.getMouseDelta(): { x: number, y: number }
Property Value
Description Get the relative mouse motion accumulated for the current frame.
Arguments None.
Return value Plain object { x: number, y: number }. Positive x means motion to the right; positive y means motion downward.
Error behavior Never throws. Always returns a valid object.
Lifecycle semantics Returns { x: 0, y: 0 } if no mouse motion occurred this frame. Entering cursor lock, leaving cursor lock, losing focus, and regaining focus all clear the current-frame delta before the game can observe it, so the first frame after those transitions returns zero unless new motion occurred after the transition. When cursor lock is active, this is the canonical FPS-look primitive.

4.6 aura.input.isMouseDown(button)

aura.input.isMouseDown(button: number): boolean
Property Value
Description Returns true if the given mouse button is currently held down.
Arguments button -- 0 (left), 1 (middle), 2 (right).
Return value boolean.
Validation Non-number or out-of-range button returns false.
Error behavior Never throws.

4.7 aura.input.isMousePressed(button)

aura.input.isMousePressed(button: number): boolean
Property Value
Description Returns true if the given mouse button was pressed this frame.
Arguments button -- 0, 1, or 2.
Return value boolean.
Validation Same as isMouseDown. Invalid button returns false.
Error behavior Never throws.

4.8 aura.input.getMouseWheel()

aura.input.getMouseWheel(): number
Property Value
Description Get the mouse wheel scroll delta for the current frame. Positive = scroll up, negative = scroll down. Resets to 0 each frame.
Arguments None.
Return value number. Typically integer multiples of 1.0 for notched wheels; fractional for smooth trackpads.
Error behavior Never throws. Returns 0 if no scroll occurred.

4.9 aura.input.isGamepadConnected(index)

aura.input.isGamepadConnected(index: number): boolean
Property Value
Description Returns true if a gamepad is connected at the given index.
Arguments index -- zero-based gamepad index (0-3).
Return value boolean.
Validation index must be an integer number in [0, 3]. Fractional values (for example 0.5), numeric strings, non-number values, and out-of-range indices return false.
Error behavior Never throws.

4.10 aura.input.getGamepadAxis(index, axis)

aura.input.getGamepadAxis(index: number, axis: number): number
Property Value
Description Get the value of a gamepad axis.
Arguments index -- gamepad index (0-3). axis -- axis index (0 = left stick X, 1 = left stick Y, 2 = right stick X, 3 = right stick Y, 4 = left trigger, 5 = right trigger).
Return value number in range [-1.0, 1.0] for sticks, [0.0, 1.0] for triggers.
Validation index and axis must be integer numbers. index must be in [0, 3]; axis must be in [0, 5]. Fractional/non-number/out-of-range values return 0. Disconnected gamepad returns 0.
Error behavior Never throws.

4.11 aura.input.isGamepadButtonDown(index, button)

aura.input.isGamepadButtonDown(index: number, button: number): boolean
Property Value
Description Returns true if a gamepad button is currently held down.
Arguments index -- gamepad index (0-3). button -- button index (SDL2 GameController button mapping: 0=A, 1=B, 2=X, 3=Y, 4=Back, 5=Guide, 6=Start, 7=LeftStick, 8=RightStick, 9=LeftShoulder, 10=RightShoulder, 11=DPadUp, 12=DPadDown, 13=DPadLeft, 14=DPadRight).
Return value boolean.
Validation index and button must be integer numbers. index must be in [0, 3]; button must be in [0, 14]. Fractional/non-number/out-of-range values and disconnected gamepads return false.
Error behavior Never throws.

5. aura.draw2d

Immediate-mode 2D drawing namespace. All draw calls except measureText() must be made inside the aura.draw() callback. Calls made outside draw() are no-ops; a warning is logged once per runtime session.

All coordinates are in logical pixels. The coordinate origin (0, 0) is the top-left corner of the window. X increases rightward, Y increases downward.

Color Argument Convention

Wherever a color argument appears, the following types are accepted:

  • { r, g, b, a } -- all fields are floats in [0.0, 1.0]. a defaults to 1.0 if omitted.
  • A named color constant, e.g. aura.Color.RED.
  • Result of aura.rgb(r, g, b) or aura.rgba(r, g, b, a).

Color arguments are non-throwing. If color parsing fails, each API uses its function-level fallback color.

5.1 aura.draw2d.clear(color)

aura.draw2d.clear(color: Color): void
Property Value
Description Clear the entire screen to the given color. Typically the first call in draw().
Arguments color -- Color object.
Return value undefined.
Validation Missing or invalid color: warning logged, default background color used ({r: 0.08, g: 0.08, b: 0.12, a: 1.0}).
Error behavior Never throws. Invalid args: warning logged, call uses default.

5.2 aura.draw2d.rect(x, y, w, h, color)

aura.draw2d.rect(x: number, y: number, w: number, h: number, color: Color): void
Property Value
Description Draw a rectangle outline (stroke only, no fill).
Arguments x, y -- top-left corner position. w, h -- width and height. color -- stroke color.
Return value undefined.
Validation Non-number coordinates: warning logged, call skipped. Negative w or h: drawn as-is (the rect extends in the negative direction). Zero w or h: call skipped (no visible output).
Error behavior Never throws. Invalid args: warning logged, call skipped.
Line width Default stroke width is 1.0 logical pixel.

5.3 aura.draw2d.rectFill(x, y, w, h, color)

aura.draw2d.rectFill(x: number, y: number, w: number, h: number, color: Color): void
Property Value
Description Draw a filled rectangle.
Arguments Same as rect().
Return value undefined.
Validation Same as rect().
Error behavior Never throws. Invalid args: warning logged, call skipped.

5.4 aura.draw2d.circle(x, y, radius, color)

aura.draw2d.circle(x: number, y: number, radius: number, color: Color): void
Property Value
Description Draw a circle outline (stroke only).
Arguments x, y -- center position. radius -- in logical pixels (must be > 0). color -- stroke color.
Return value undefined.
Validation Non-number args: warning, skipped. radius <= 0: warning, skipped.
Error behavior Never throws.
Segment count Implementation detail: the host chooses an appropriate segment count based on the radius for smooth rendering.

5.5 aura.draw2d.circleFill(x, y, radius, color)

aura.draw2d.circleFill(x: number, y: number, radius: number, color: Color): void
Property Value
Description Draw a filled circle.
Arguments Same as circle().
Return value undefined.
Validation Same as circle().
Error behavior Never throws.

5.6 aura.draw2d.line(x1, y1, x2, y2, color, width?)

aura.draw2d.line(x1: number, y1: number, x2: number, y2: number, color: Color, width?: number): void
Property Value
Description Draw a line segment between two points.
Arguments x1, y1 -- start point. x2, y2 -- end point. color -- line color. width -- optional line width in logical pixels (default: 1.0).
Return value undefined.
Validation Non-number coordinates: warning, skipped. width <= 0: clamped to 1.0 with warning. Zero-length line (start == end): drawn as a single point.
Error behavior Never throws.

5.7 aura.draw2d.sprite(image, x, y, options?)

aura.draw2d.sprite(image: Asset, x: number, y: number, options?: SpriteOptions): void

SpriteOptions:

{
    width?: number,    // destination width in logical pixels
    height?: number,   // destination height in logical pixels
    frameX?: number,   // source-frame x in pixels (requires frameW+frameH)
    frameY?: number,   // source-frame y in pixels (requires frameW+frameH)
    frameW?: number,   // source-frame width in pixels
    frameH?: number,   // source-frame height in pixels
    scaleX?: number,   // default: 1.0
    scaleY?: number,   // default: 1.0
    rotation?: number, // radians, clockwise, default: 0.0
    originX?: number,  // 0.0 = left edge, 0.5 = center, 1.0 = right edge, default: 0.0
    originY?: number,  // 0.0 = top edge, 0.5 = center, 1.0 = bottom edge, default: 0.0
    tint?: Color,      // multiplicative tint, default: WHITE (no tint)
    alpha?: number     // opacity override, 0.0 = transparent, 1.0 = opaque, default: 1.0
}
Property Value
Description Draw an image at the given position.
Arguments image -- image reference as a path string (for example "player.png") or object containing path/name. x, y -- position of the sprite origin in logical pixels. options -- optional configuration object.
Return value undefined.
Validation Invalid/empty image handle or invalid asset path (absolute, backslashes, or traversal) logs a warning and skips the call. x/y must be finite numbers or the call is skipped. Unknown option keys are ignored.
Sizing rules If only width or height is provided, the missing side copies the provided value. If neither is provided, renderer fallback size is used.
Frame rules Frame cropping activates only when both frameW and frameH are valid. If either is missing, frame rect is ignored. If frame size is valid and frameX/frameY are missing, they default to 0.
Legacy compatibility If frameX/frameY are omitted and frameW/frameH are provided, frameW/frameH are treated as destination width/height (legacy alias behavior).
Tint/alpha rule tint sets RGBA. alpha multiplies the resulting alpha after tint (clamped to [0,1]).
Error behavior Never throws.

5.8 aura.draw2d.text(str, x, y, options?)

aura.draw2d.text(str: string, x: number, y: number, options?: TextOptions): void

TextOptions:

{
    font?: string | Asset | LoadedFont, // path string, font asset-like object, or loaded font handle
    size?: number,       // font size in logical pixels, default: 16
    color?: Color,       // text color, default: WHITE
    align?: string       // "left" | "center" | "right", default: "left"
}
Property Value
Description Draw a text string at the given position.
Arguments str -- the text to draw. Non-string values are coerced via String(value). x, y -- position of the text baseline (left edge for "left" alignment, center for "center", right edge for "right"). options -- optional configuration.
Return value undefined.
Validation x/y must be finite numbers; invalid coordinates log a warning and skip the call. size is rounded/clamped to >=1. Invalid align falls back to "left". Empty string is allowed and draws nothing.
Font option font accepts a non-empty path string, object with path/name, or loaded handle from aura.assets.loadFont(...) / aura.assets.loadBitmapFont(...). If an object includes kind, only "font" is accepted; other kinds log a warning and fall back to built-in font.
Fallback Missing/invalid/unloadable font path falls back to built-in font with warning (render-time fallback).
Error behavior Never throws.

5.9 aura.draw2d.measureText(str, options?)

aura.draw2d.measureText(str: string, options?: TextMeasureOptions): { width: number, height: number }

TextMeasureOptions:

{
    font?: string | Asset | LoadedFont, // path string, font asset-like object, or loaded font handle
    size?: number    // font size in logical pixels, default: 16
}
Property Value
Description Measure the bounding box of a text string without drawing it. Useful for layout calculations.
Arguments str -- the text to measure. options -- either a number (size) or object with size/font.
Return value { width: number, height: number } in logical pixels.
Validation Empty string returns { width: 0, height: 0 }. Invalid size falls back to default size. Invalid/unloadable dynamic font falls back to built-in font with warning. Loaded bitmap font handles use deterministic bitmap metrics.
Error behavior Never throws.
Note Can be called outside of draw(). This is a pure measurement function.

5.9.1 aura.draw2d render-target composition helpers

aura.draw2d.createRenderTarget(width: number, height: number): {
  ok: boolean,
  reasonCode: string,
  handle?: number,
  width?: number,
  height?: number,
  type?: "renderTarget",
  __renderTarget?: true
}

aura.draw2d.resizeRenderTarget(
  target: number | { handle: number, __renderTarget?: true },
  width: number,
  height: number
): { ok: boolean, reasonCode: string, handle?: number, width?: number, height?: number, resized?: boolean }

aura.draw2d.destroyRenderTarget(
  target: number | { handle: number, __renderTarget?: true }
): { ok: boolean, reasonCode: string, handle?: number, destroyed?: boolean }

aura.draw2d.withRenderTarget(
  target: number | { handle: number, __renderTarget?: true },
  callback: () => void
): { ok: boolean, reasonCode: string, handle?: number, commandCount?: number }
Property Value
Description Create an offscreen draw2d surface, capture immediate-mode draw commands into it, then reuse the captured target as a sprite-like source in later draw calls.
Valid phase createRenderTarget, resizeRenderTarget, and destroyRenderTarget are runtime-safe. withRenderTarget(...) is draw-phase only and captures only the commands queued inside its callback.
Returned handle Successful createRenderTarget(...) returns an object tagged with type: "renderTarget" and __renderTarget: true. That handle may be passed back into resizeRenderTarget, destroyRenderTarget, sprite, tileSprite, and nineSlice.
Resize semantics Resize is deterministic. resized is true only when width or height changed. Invalid handles or non-positive dimensions return stable reason codes.
Capture semantics withRenderTarget(...) records a single offscreen pass and returns the number of draw2d commands captured in that pass. Nested capture is rejected with reasonCode: "nested_render_target_capture".
Reset / lifecycle Render-target GPU resources are renderer-owned and are recreated or released deterministically across resize/reset/hot-reload boundaries.
Limit withRenderTarget(...) remains single-target callback capture. Named compositor graphs and PNG export are available through runCompositorGraph(...) and exportRenderTarget(...); AuraJS does not include an arbitrary retained rendergraph or general readback formats beyond that path.

5.9.2 aura.draw2d clip-rect stack

aura.draw2d.pushClipRect(
  x: number,
  y: number,
  width: number,
  height: number
): { ok: boolean, reasonCode: string }

aura.draw2d.pushClipRect(
  rect: { x?: number, y?: number, width: number, height: number }
): { ok: boolean, reasonCode: string }

aura.draw2d.popClipRect(): { ok: boolean, reasonCode: string }
Property Value
Description Bound subsequent draw2d submissions to a deterministic clip rectangle stack for HUD panes, scroll regions, and offscreen composition.
Push semantics Valid rectangles return { ok:true, reasonCode:"draw2d_clip_pushed" }. Invalid or non-positive rectangles return { ok:false, reasonCode:"invalid_clip_rect" }.
Pop semantics popClipRect() returns { ok:true, reasonCode:"draw2d_clip_popped" } when a clip is active. Underflow returns { ok:false, reasonCode:"draw2d_clip_stack_underflow" }.
Reset The active clip stack is reset at the start of each frame and is isolated per render-target capture callback.
Limit pushClipRect() / popClipRect() remain rectangle-only bounds helpers. Non-rectangular masking now lives in withMask(...), but clip rects do not claim arbitrary path clipping or shader-authored stencil pipelines.

5.9.3 aura.draw2d.tileSprite(image, x, y, width, height, options?)

aura.draw2d.tileSprite(
  image: Asset,
  x: number,
  y: number,
  width: number,
  height: number,
  options?: {
    tileScaleX?: number,
    tileScaleY?: number,
    tileOffsetX?: number,
    tileOffsetY?: number,
    tint?: Color,
    alpha?: number
  }
): void
Property Value
Description Repeat a sprite-like source across the destination rect with deterministic UV stepping. Useful for scrolling backgrounds, fills, and HUD bars.
Accepted sources Path string, sprite asset-like object, or render-target handle from createRenderTarget(...).
Validation Invalid image handle/path logs a warning and skips. x, y, width, and height must be finite; non-positive width/height skip the call.
Error behavior Never throws.

5.9.4 aura.draw2d.nineSlice(image, x, y, width, height, options?)

aura.draw2d.nineSlice(
  image: Asset,
  x: number,
  y: number,
  width: number,
  height: number,
  options: {
    slice?: number,
    left?: number,
    right?: number,
    top?: number,
    bottom?: number,
    tint?: Color,
    alpha?: number
  }
): void
Property Value
Description Draw a scalable nine-slice panel without stretching the corners. Useful for HUD windows, buttons, and framed overlays.
Slice shorthand slice applies the same positive inset to all four edges. Edge-specific overrides (left/right/top/bottom) take precedence when provided.
Accepted sources Path string, sprite asset-like object, or render-target handle from createRenderTarget(...).
Validation Invalid image handle/path logs a warning and skips. width/height must be finite and > 0. Slice sizes must be positive.
Error behavior Never throws.

5.9.5 aura.draw2d masking, compositor graphs, export, and sprite FX

aura.draw2d.withMask(
  source:
    | Asset
    | { handle: number, __renderTarget?: true }
    | { type: "analytic" | "shape", shape: "circle", feather?: number },
  x: number,
  y: number,
  width: number,
  height: number,
  callback: () => void,
  options?: { invert?: boolean }
): {
  ok: boolean,
  reasonCode: string,
  commandCount?: number,
  maskKind?: "source" | "analytic",
  maskShape?: "texture" | "circle",
  maskFeather?: number
}

aura.draw2d.withRenderTargets(
  stages: Array<{
    target: number | { handle: number, __renderTarget?: true },
    draw: () => void
  }>
): { ok: boolean, reasonCode: string, stageCount?: number, commandCount?: number, handles?: number[] }

aura.draw2d.runCompositorGraph(
  graphName: string,
  stages: Array<{
    id: string,
    target: number | { handle: number, __renderTarget?: true },
    draw: () => void
  }>
): {
  ok: boolean,
  reasonCode: string,
  graphName?: string,
  stageCount?: number,
  commandCount?: number,
  stages?: Array<{
    type: "compositorStage",
    __compositorStage?: true,
    graph: string,
    graphName: string,
    stage: string,
    id: string,
    handle: number
  }>
}

aura.draw2d.exportRenderTarget(
  target:
    | number
    | { handle: number, __renderTarget?: true }
    | { type: "compositorStage", graph: string, stage: string },
  path: string
): { ok: boolean, reasonCode: string, handle?: number, path?: string, graphName?: string, stage?: string }

aura.draw2d.spriteFx(
  image:
    | Asset
    | { handle: number, __renderTarget?: true }
    | { type: "compositorStage", graph: string, stage: string },
  x: number,
  y: number,
  options: {
    width?: number,
    height?: number,
    alpha?: number,
    shadow?: {
      offsetX?: number,
      offsetY?: number,
      blur?: number,
      color?: Color,
      alpha?: number
    },
    outline?: {
      thickness?: number,
      color?: Color,
      alpha?: number
    }
  }
): { ok: boolean, reasonCode: string, submittedFxDrawCount?: number }
Property Value
Description withMask(...) captures a callback-owned masked draw pass using a sprite, render target, or analytic mask descriptor on the main draw path. withRenderTargets(...) remains the simple ordered multi-target capture helper. runCompositorGraph(...) exposes named deterministic compositor stages with stage reuse through { type:"compositorStage", graph, stage }. exportRenderTarget(...) queues PNG export for a render target or compositor-stage output. spriteFx(...) adds outline and shadow sprite duplication without exposing a full 2D material/post stack.
Accepted sources withMask(...) accepts a sprite-like asset source, a draw2d render-target handle, or an analytic descriptor with shape:"circle". spriteFx(...) accepts a sprite-like asset source, a draw2d render-target handle, or a compositor-stage descriptor. withRenderTargets(...) accepts only live render-target handles returned from createRenderTarget(...). exportRenderTarget(...) accepts either a live render-target handle or a compositor-stage descriptor.
Mask semantics Valid masked passes return { ok:true, reasonCode:"draw2d_mask_captured" } with deterministic commandCount, maskKind, maskShape, and maskFeather. The analytic path is a feathered circle mask with optional { invert:true }. Mask capture inside an active render-target capture is rejected with reasonCode:"mask_capture_inside_render_target_unsupported". Malformed or unsupported analytic descriptors fail with stable reason codes such as invalid_mask_shape, unsupported_mask_shape, and invalid_mask_feather.
Composition semantics withRenderTargets(...) captures each stage in order and returns { ok:true, reasonCode:"draw2d_render_targets_captured" } with stageCount and total commandCount. runCompositorGraph(...) returns { ok:true, reasonCode:"draw2d_compositor_captured" } with graphName, stageCount, commandCount, and typed stages[] descriptors. Duplicate stage ids, duplicate targets, self-dependencies, and forward references fail atomically with stable reason codes.
Export semantics exportRenderTarget(...) is a deterministic queueing surface for .png output only. Successful calls return { ok:true, reasonCode:"draw2d_render_target_export_queued" } with the resolved handle, path, and optional graphName / stage. Invalid paths, missing targets, or unsupported formats fail with stable reason codes such as invalid_export_path, missing_render_target, missing_compositor_stage, and unsupported_export_format.
Sprite FX semantics spriteFx(...) supports both shadow and outline duplication. shadow accepts offset / blur / color / alpha controls, outline accepts thickness / color / alpha controls, and either or both may be supplied. Missing both effects returns reasonCode:"missing_sprite_fx_effect". Invalid shadow or outline config returns stable reason codes such as invalid_sprite_fx_shadow and invalid_sprite_fx_outline. Successful calls return { ok:true, reasonCode:"draw2d_sprite_fx_queued" } with submittedFxDrawCount.
Runtime evidence aura.debug.inspectorStats() exposes truthful draw2dRuntime.masking, draw2dRuntime.compositor, and draw2dRuntime.fx counters so tests can distinguish queued, executed, rejected, and idle masking/compositor/FX frames.
Limit This surface includes analytic circle masking, named deterministic compositor graphs, PNG export, and outline/shadow spriteFx(). It does not include arbitrary vector/path masks, generic readback formats, a fully general retained rendergraph, or a broader glow/blur/material/post stack beyond the outline/shadow path.

5.9.6 aura.ui immediate-mode layout plus retained containers, selection, and scroll state

aura.ui.beginLayout(options: {
  id: string,
  x?: number,
  y?: number,
  width?: number,
  height?: number,
  direction?: "vertical" | "horizontal",
  gap?: number
}): { ok: boolean, reasonCode: string }

aura.ui.label(id: string, options: { text: string, width?: number, height?: number }): { ok: boolean, reasonCode: string }
aura.ui.button(id: string, options: { label: string, width?: number, height?: number }): { ok: boolean, reasonCode: string }
aura.ui.toggle(id: string, options: { label: string, value?: boolean, width?: number, height?: number }): { ok: boolean, reasonCode: string }
aura.ui.slider(id: string, options: { label: string, min?: number, max?: number, step?: number, value?: number, width?: number, height?: number }): { ok: boolean, reasonCode: string }
aura.ui.input(id: string, options: { value?: string, placeholder?: string, maxLength?: number, width?: number, height?: number }): { ok: boolean, reasonCode: string }

aura.ui.endLayout(): { ok: boolean, reasonCode: string }
aura.ui.moveFocus(direction: "next" | "prev"): { ok: boolean, reasonCode: string }
aura.ui.activate(id?: string): { ok: boolean, reasonCode: string }
aura.ui.adjustValue(idOrDelta: string | number, delta?: number): { ok: boolean, reasonCode: string }
aura.ui.getFocusState(layoutId: string): { focusedId: string | null, focusedIndex: number, reasonCode?: string }
aura.ui.getWidgetState(widgetId: string, layoutId?: string): Record<string, unknown> | null

aura.ui.beginContainer(options: {
  id: string,
  x?: number,
  y?: number,
  width: number,
  height: number,
  direction?: "vertical" | "horizontal",
  gap?: number,
  padding?: number,
  wrap?: boolean,
  scrollX?: number,
  scrollY?: number
}): { ok: boolean, reasonCode: string }

aura.ui.region(id: string, options: {
  width: number,
  height: number,
  x?: number,
  y?: number,
  order?: number,
  focusable?: boolean,
  disabled?: boolean
}): { ok: boolean, reasonCode: string }

aura.ui.selectOption(id: string, options: {
  label?: string,
  text?: string,
  value?: string | number | boolean | null,
  selected?: boolean,
  width: number,
  height: number,
  x?: number,
  y?: number,
  order?: number,
  disabled?: boolean
}): { ok: boolean, reasonCode: string }

aura.ui.endContainer(): { ok: boolean, reasonCode: string }

aura.ui.getContainerState(containerId?: string): Record<string, unknown> | null
aura.ui.getRegionState(regionId: string, containerId?: string): Record<string, unknown> | null
aura.ui.getSelectionState(containerId?: string): Record<string, unknown> | null
aura.ui.getScrollState(containerId?: string): Record<string, unknown> | null
aura.ui.setScroll(containerId: string, x?: number, y?: number): { ok: boolean, reasonCode: string }
aura.ui.scrollBy(containerId: string, dx?: number, dy?: number): { ok: boolean, reasonCode: string }

aura.ui.reset(layoutOrContainerId?: string): { ok: boolean, reasonCode: string }
Property Value
Description Canonical immediate-mode UI helpers for deterministic menu/HUD layout, focus traversal, activation, toggles, and sliders, plus additive retained container/region/select/scroll state keyed by declared ids. The retained surface is renderer-owned state, not a second UI framework.
Lifecycle Layout/widget calls are intended for draw() and are rebuilt each frame. Container/region declarations are also rebuild-per-frame, but AuraJS retains focus, selection, and scroll state across frames by container/region id so menus and HUD panes do not have to fork their own state model.
Focus and container semantics beginLayout(...) / endLayout() establish deterministic widget focus order. beginContainer(...) / region(...) / selectOption(...) / endContainer() establish deterministic region order, retained focus, selection, and scroll clamping for a named container. moveFocus(...), activate(...), adjustValue(...), setScroll(...), and scrollBy(...) return stable reason codes for invalid targets or values.
Supported surface label, button, toggle, and slider remain the immediate-mode widget primitives. region and selectOption are the retained container primitives. getFocusState(...), getWidgetState(...), getContainerState(...), getRegionState(...), getSelectionState(...), and getScrollState(...) expose truthful runtime-owned state for tests and tooling.
Relationship to authored composition The authored view path in 5.9.7 reuses the same deterministic focus, activation, and reset core. button(...) stays the shared interaction primitive across both surfaces.
Reset reset(...) clears retained layout/container state deterministically and returns reasonCode:"ui_focus_reset" when successful. Container resets also clear retained region and scroll state for the targeted container id.
Limit This surface adds retained container, select, and scroll helpers on top of the immediate-mode primitives, and 5.9.7 adds the authored view/theme surface. AuraJS does not include general text-input widgets, a full retained widget tree, or a broader accessibility/layout engine beyond the documented layout/container/authored-view surfaces.

5.9.7 aura.ui authored view composition and theme surface

type UIViewColor =
  | Color
  | string
  | [number, number, number]
  | [number, number, number, number]

type UIViewSize = number | "fill" | "stretch" | "content" | "auto"
type UIViewDirection = "vertical" | "horizontal" | "row" | "column"

aura.ui.setTheme(theme?: {
  gap?: number,
  padding?: number,
  fontSize?: number,
  lineHeight?: number,
  buttonHeight?: number,
  borderWidth?: number,
  textColor?: UIViewColor,
  mutedTextColor?: UIViewColor,
  panelColor?: UIViewColor,
  panelBorderColor?: UIViewColor,
  buttonColor?: UIViewColor,
  buttonHoverColor?: UIViewColor,
  buttonFocusColor?: UIViewColor,
  buttonActiveColor?: UIViewColor,
  buttonTextColor?: UIViewColor
}): { ok: boolean, reasonCode: string }

aura.ui.getTheme(): {
  gap: number,
  padding: number,
  fontSize: number,
  lineHeight: number,
  buttonHeight: number,
  borderWidth: number,
  textColor: Color,
  mutedTextColor: Color,
  panelColor: Color,
  panelBorderColor: Color,
  buttonColor: Color,
  buttonHoverColor: Color,
  buttonFocusColor: Color,
  buttonActiveColor: Color,
  buttonTextColor: Color,
  transparentColor: Color
}

aura.ui.beginView(options: {
  id: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  direction?: UIViewDirection,
  gap?: number,
  padding?: number,
  borderWidth?: number,
  background?: UIViewColor | null,
  borderColor?: UIViewColor | null,
  clip?: boolean
}): { ok: boolean, reasonCode: string }

aura.ui.endView(): { ok: boolean, reasonCode: string }

aura.ui.beginDiv(options?: {
  id?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  direction?: UIViewDirection,
  gap?: number,
  padding?: number,
  borderWidth?: number,
  background?: UIViewColor | null,
  borderColor?: UIViewColor | null,
  clip?: boolean
}): { ok: boolean, reasonCode: string }

aura.ui.endDiv(): { ok: boolean, reasonCode: string }

aura.ui.beginRow(options?: {
  id?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  gap?: number,
  padding?: number,
  borderWidth?: number,
  background?: UIViewColor | null,
  borderColor?: UIViewColor | null,
  clip?: boolean
}): { ok: boolean, reasonCode: string }

aura.ui.endRow(): { ok: boolean, reasonCode: string }

aura.ui.beginColumn(options?: {
  id?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  gap?: number,
  padding?: number,
  borderWidth?: number,
  background?: UIViewColor | null,
  borderColor?: UIViewColor | null,
  clip?: boolean
}): { ok: boolean, reasonCode: string }

aura.ui.endColumn(): { ok: boolean, reasonCode: string }

aura.ui.text(id: string, options?: {
  text?: string,
  label?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  size?: number,
  color?: UIViewColor,
  font?: string
}): { ok: boolean, reasonCode: string }

aura.ui.button(id: string, options?: {
  label?: string,
  text?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  size?: number,
  padding?: number,
  color?: UIViewColor,
  background?: UIViewColor,
  hoverBackground?: UIViewColor,
  focusBackground?: UIViewColor,
  activeBackground?: UIViewColor,
  borderColor?: UIViewColor,
  borderWidth?: number,
  font?: string,
  disabled?: boolean,
  focusable?: boolean
}): { ok: boolean, reasonCode: string, hovered?: boolean, pressed?: boolean }

aura.ui.toggle(id: string, options?: {
  label?: string,
  text?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  size?: number,
  padding?: number,
  color?: UIViewColor,
  background?: UIViewColor,
  hoverBackground?: UIViewColor,
  focusBackground?: UIViewColor,
  activeBackground?: UIViewColor,
  borderColor?: UIViewColor,
  borderWidth?: number,
  font?: string,
  value?: boolean,
  disabled?: boolean,
  focusable?: boolean
}): { ok: boolean, reasonCode: string, hovered?: boolean, pressed?: boolean, value?: boolean }

aura.ui.slider(id: string, options?: {
  label?: string,
  text?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  size?: number,
  padding?: number,
  color?: UIViewColor,
  background?: UIViewColor,
  hoverBackground?: UIViewColor,
  focusBackground?: UIViewColor,
  activeBackground?: UIViewColor,
  borderColor?: UIViewColor,
  borderWidth?: number,
  font?: string,
  min?: number,
  max?: number,
  step?: number,
  value?: number,
  disabled?: boolean,
  focusable?: boolean
}): { ok: boolean, reasonCode: string, hovered?: boolean, pressed?: boolean, value?: number, min?: number, max?: number, step?: number }

aura.ui.input(id: string, options?: {
  label?: string,
  value?: string,
  placeholder?: string,
  x?: number,
  y?: number,
  width?: UIViewSize,
  height?: UIViewSize,
  size?: number,
  padding?: number,
  color?: UIViewColor,
  background?: UIViewColor,
  hoverBackground?: UIViewColor,
  focusBackground?: UIViewColor,
  activeBackground?: UIViewColor,
  borderColor?: UIViewColor,
  borderWidth?: number,
  font?: string,
  maxLength?: number,
  disabled?: boolean,
  focusable?: boolean
}): { ok: boolean, reasonCode: string, hovered?: boolean, pressed?: boolean, value?: string, placeholder?: string }

aura.ui.getViewState(nodeId?: string): {
  nodeId: string,
  rootId: string,
  layoutId: string,
  kind: "view" | "div" | "text" | "button" | "toggle" | "slider" | "input",
  parentId: string | null,
  depth: number,
  x: number,
  y: number,
  width: number,
  height: number,
  direction: "vertical" | "horizontal" | null,
  text: string | null,
  label: string | null,
  hovered: boolean,
  focused: boolean,
  activated: boolean,
  changed: boolean,
  toggled: boolean,
  pressed: boolean,
  clicked: boolean,
  value: boolean | number | string | null,
  placeholder: string | null,
  editing: boolean,
  submitted: boolean,
  caretIndex: number | null,
  min: number | null,
  max: number | null,
  step: number | null,
  disabled: boolean,
  clip: boolean,
  reasonCode: string
} | null
Property Value
Description CSS-like authored composition on top of the existing aura.ui focus/runtime core. It provides a nested box tree with default panel and button chrome so menus, overlays, and tool panes can be authored as views/rows/columns instead of manual rectangle math.
Theme semantics setTheme(...) mutates a runtime-owned authored UI theme and returns reasonCode:"ui_theme_updated" on success. Passing null or omitting the argument resets to defaults. getTheme() returns the active normalized theme snapshot, including resolved Color objects.
Layout semantics beginView(...) starts a root authored tree and internally reuses the deterministic aura.ui focus/layout core. beginDiv(...), beginRow(...), and beginColumn(...) add nested containers beneath the active root. Omitted x / y values use directional flow order plus gap; explicit x / y values pin the node absolutely within the current authored tree. width / height accept fixed numbers plus "fill" / "stretch" or "content" / "auto" sizing.
Supported authored primitives text(...), button(...), toggle(...), slider(...), and input(...) are the authored leaves. label(...) also maps onto the same authored text path when a view tree is active. The authored settings/forms controls reuse the same deterministic widget core as immediate-mode toggle / slider / input, including truthful value, focus, activation, edit, submit, and change state.
State and control getViewState(...) exposes node geometry and interaction state for tooling, tests, and overlay rendering, including authored widget value, changed, toggled, editing, submitted, caretIndex, and range metadata. moveFocus(...), activate(...), adjustValue(...), and reset(...) from 5.9.6 remain the shared control surface; authored views do not fork a second interaction model.
Input semantics aura.ui.input(...) is a retained single-line text field. Durable values still belong to authored state such as appState.session, appState.ui, or scene-owned state; aura.ui owns runtime draft/edit/focus/caret state keyed by stable widget ids. aura.input.getTextInput() is the canonical text-entry source, while Enter/Space activation, Backspace/Delete editing, and Escape cancellation stay on the shared key/control surface.
Rendering semantics endView() paints the authored tree through aura.draw2d using the active theme, panel/background colors, border colors, clip state, and authored button/toggle/slider/input chrome. Pointer targeting can focus authored widgets directly, and authored slider track clicks can change values deterministically. Nested authored composition is intended for draw() and is rebuilt each frame.
Limit This surface includes authored view/div/row/column containers plus text, button, toggle, slider, and single-line input leaves with theme tokens, pointer integration, and truthful node snapshots. AuraJS still does not include multi-line editor widgets, browser-style contenteditable behavior, authored list/grid widgets as first-class runtime nodes, stylesheet files/selectors, or a full browser-style layout engine.

5.10 Transform Stack

aura.draw2d.pushTransform(): void
aura.draw2d.popTransform(): void
aura.draw2d.translate(x: number, y: number): void
aura.draw2d.rotate(angle: number): void
aura.draw2d.scale(sx: number, sy: number): void
Function Description
pushTransform() Save the current transformation matrix onto the stack.
popTransform() Restore the most recently saved transformation matrix.
translate(x, y) Apply a translation offset to subsequent draw calls.
rotate(angle) Apply a rotation in radians (clockwise) to subsequent draw calls.
scale(sx, sy) Apply a scale factor to subsequent draw calls.
Property Value
Return value All return undefined.
Maximum stack depth 64 total stack entries (including the base identity). This means at most 63 nested pushTransform() calls are accepted before overflow.
Overflow behavior At max depth, pushTransform() logs a warning and is a no-op.
Unbalanced pops popTransform() on an empty stack logs a warning and is a no-op.
Defaults translate(x, y) defaults missing args to 0. rotate(angle) defaults missing arg to 0. scale(sx, sy) defaults to 1; if sy is omitted it uses sx.
Error behavior Never throws.
Reset The transform stack is automatically reset to identity at the start of each draw() call.

5.11 aura.camera (Canonical 2D Camera State)

aura.camera.x: number
aura.camera.y: number
aura.camera.zoom: number
aura.camera.rotation: number

aura.camera.getState(): {
  x: number,
  y: number,
  zoom: number,
  rotation: number,
  following: boolean,
  activeEffects: number,
  deadzone: { x: number, y: number, width: number, height: number } | null,
  bounds: { x: number, y: number, width: number, height: number } | null,
  overlay: {
    effectType: "fade" | "flash",
    effectId: number,
    alpha: number,
    color: { r: number, g: number, b: number, a: number }
  } | null
}

aura.camera.follow(
  target: { x: number, y: number } | (() => { x: number, y: number }),
  options?: { lerpX?: number, lerpY?: number, offsetX?: number, offsetY?: number }
): { ok: boolean, reasonCode: string }

aura.camera.stopFollow(): { ok: true, stopped: boolean, reasonCode: string }

aura.camera.setDeadzone(
  deadzone: { x?: number, y?: number, width: number, height: number } | number,
  height?: number
): { ok: boolean, reasonCode: string }
aura.camera.clearDeadzone(): { ok: true, reasonCode: string }

aura.camera.setBounds(
  bounds: { x?: number, y?: number, width: number, height: number } | number,
  y?: number,
  width?: number,
  height?: number
): { ok: boolean, reasonCode: string }
aura.camera.clearBounds(): { ok: true, reasonCode: string }

aura.camera.pan(x: number, y: number, options?: { duration?: number }): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.panTo(x: number, y: number, options?: { duration?: number }): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.zoomTo(zoom: number, options?: { duration?: number }): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.rotateTo(rotation: number, options?: { duration?: number }): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.shake(options?: {
  duration?: number,
  frequency?: number,
  intensity?: number,
  intensityX?: number,
  intensityY?: number
}): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.fade(options?: {
  duration?: number,
  color?: { r: number, g: number, b: number, a?: number } | [number, number, number, number?],
  fromAlpha?: number,
  toAlpha?: number,
  alpha?: number
}): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.flash(options?: {
  duration?: number,
  color?: { r: number, g: number, b: number, a?: number } | [number, number, number, number?],
  alpha?: number
}): { ok: boolean, effectId?: number, reasonCode: string }
aura.camera.clearEffects(): { ok: true, cleared: number, reasonCode: string }

aura.camera.onEffectComplete(
  callback: (event: { type: "effect_complete", effectType: string, effectId: number, reasonCode: string }) => void,
  order?: number
): { ok: boolean, listenerId?: number, reasonCode: string }
aura.camera.offEffectComplete(listenerId: number): boolean

aura.camera.update(dt: number): {
  ok: boolean,
  reasonCode: string,
  x?: number,
  y?: number,
  zoom?: number,
  rotation?: number,
  following?: boolean,
  activeEffects?: number,
  overlay?: {
    effectType: "fade" | "flash",
    effectId: number,
    alpha: number,
    color: { r: number, g: number, b: number, a: number }
  } | null
}
Property Value
Description Global 2D camera state used by runtime draw2d geometry/sprite submission.
Defaults x = 0, y = 0, zoom = 1, rotation = 0.
Pan semantics x/y represent camera position in logical pixels. Positive camera position translates world-space draw calls by -x/-y (camera pans right/down while world appears left/up).
Zoom semantics zoom is a scalar applied to draw2d camera transform. Must remain finite and > 0.
Rotation semantics rotation is radians. Runtime applies -rotation to world-space draw submissions.
Validation Assignments are non-throwing. Non-finite values are ignored. zoom <= 0 is ignored.
Object identity aura.camera is a canonical runtime-owned object and is non-replaceable. Field writes are mutable per the validation rules above.
Call-order contract Camera state is sampled in draw call order and deterministically affects subsequent draw2d geometry/sprite submissions in the same frame.
Follow/deadzone/bounds follow accepts an object target or target callback. Deadzone/bounds configuration is deterministic and non-throwing with reason-coded failure returns.
Effects pan/zoomTo/rotateTo/shake/fade/flash enqueue deterministic camera effects with stable effectId sequencing and reason-coded validation failures.
Overlay semantics fade renders a fullscreen color overlay through the native renderer and persists its target alpha after completion until cleared or replaced. flash renders the same fullscreen overlay path transiently, then yields back to the last persistent fade state (or no overlay).
Effect callbacks onEffectComplete listeners are dispatched in ascending order, then registration order, for each completed effect in ascending effectId order.
Update loop update(dt) is explicit and deterministic; invalid dt returns { ok:false, reasonCode:"invalid_dt" } without throwing.

6. aura.audio

Audio playback namespace. Audio assets must be loaded via aura.assets.load() before playback. The audio engine (miniaudio) runs on a background thread; JS calls enqueue commands.

Audio Handle

aura.audio.play() returns an opaque numeric handle used to control the playing sound. Handles are invalidated when the sound finishes or is stopped. Using an invalid handle is a silent no-op (never throws).

6.1 aura.audio.play(path, options?)

aura.audio.play(path: string, options?: AudioOptions): number

AudioOptions:

{
    volume?: number,  // 0.0 (silent) to 1.0 (full), default: 1.0
    loop?: boolean,   // default: false
    pitch?: number,   // playback speed multiplier, 0.5 = half speed, 2.0 = double, default: 1.0
    bus?: string      // optional mixer bus name; default: "master"
}
Property Value
Description Play an audio asset. The asset must have been previously loaded via aura.assets.load() or will be loaded on-demand.
Arguments path -- asset path string (e.g. "music.ogg", "sfx/jump.wav"). options -- optional playback configuration.
Return value number -- opaque handle for controlling the sound instance. Returns -1 if playback failed.
Validation volume clamped to [0.0, 1.0]. pitch clamped to [0.1, 10.0]. Invalid option values: clamped with warning.
Error behavior Missing asset: throws Error with message "Asset not found: <path>". Invalid path type: throws TypeError.

6.2 aura.audio.stop(handle)

aura.audio.stop(handle: number): void
Property Value
Description Stop a playing sound immediately. The handle is invalidated.
Arguments handle -- audio handle from play().
Return value undefined.
Validation Non-number handle: no-op.
Error behavior Invalid handle: silent no-op. Never throws.

6.3 aura.audio.pause(handle)

aura.audio.pause(handle: number): void
Property Value
Description Pause a playing sound. Can be resumed later with resume().
Arguments handle -- audio handle.
Return value undefined.
Error behavior Invalid handle: silent no-op. Never throws.

6.4 aura.audio.resume(handle)

aura.audio.resume(handle: number): void
Property Value
Description Resume a paused sound from where it was paused.
Arguments handle -- audio handle.
Return value undefined.
Error behavior Invalid handle or non-paused sound: silent no-op. Never throws.

6.5 aura.audio.setVolume(handle, volume)

aura.audio.setVolume(handle: number, volume: number): void
Property Value
Description Change the volume of a playing or paused sound.
Arguments handle -- audio handle. volume -- float in [0.0, 1.0].
Return value undefined.
Validation volume clamped to [0.0, 1.0].
Error behavior Invalid handle: silent no-op. Never throws.

6.6 aura.audio.setMasterVolume(volume)

aura.audio.setMasterVolume(volume: number): void
Property Value
Description Set the global master volume that scales all audio output.
Arguments volume -- float in [0.0, 1.0].
Return value undefined.
Validation Clamped to [0.0, 1.0]. Non-number: warning logged, call skipped.
Error behavior Never throws.

6.7 aura.audio.stopAll()

aura.audio.stopAll(): void
Property Value
Description Stop all currently playing sounds. All handles are invalidated.
Arguments None.
Return value undefined.
Error behavior Never throws.

6.8 aura.audio.setBusVolume(bus, volume)

aura.audio.setBusVolume(bus: string, volume: number): {
  ok: boolean,
  reasonCode: string | null,
  bus?: string,
  volume?: number
}
Property Value
Description Create/update a deterministic mixer bus gain (0..1). Existing tracks routed to that bus are updated immediately.
Failure reason codes invalid_bus, invalid_volume
Error behavior Never throws.

6.9 aura.audio.assignBus(handle, bus)

aura.audio.assignBus(handle: number, bus: string): {
  ok: boolean,
  reasonCode: string | null,
  handle?: number,
  bus?: string,
  effectiveVolume?: number
}
Property Value
Description Move an active track handle to a mixer bus.
Failure reason codes invalid_track_handle, missing_track, invalid_bus
Error behavior Never throws.

6.10 aura.audio.fadeTrack(handle, options)

aura.audio.fadeTrack(handle: number, options: {
  to: number,
  duration: number,
  stopOnComplete?: boolean
}): {
  ok: boolean,
  reasonCode: string | null,
  envelopeId?: number
}
Property Value
Description Schedule deterministic per-track gain envelope progression.
Failure reason codes invalid_track_handle, missing_track, invalid_fade_options, invalid_duration, invalid_volume
Error behavior Never throws.

6.11 aura.audio.fadeBus(bus, options)

aura.audio.fadeBus(bus: string, options: {
  to: number,
  duration: number
}): {
  ok: boolean,
  reasonCode: string | null,
  envelopeId?: number
}
Property Value
Description Schedule deterministic mixer-bus gain envelope progression.
Failure reason codes invalid_bus, missing_bus, invalid_fade_options, invalid_duration, invalid_volume
Error behavior Never throws.

6.12 aura.audio.crossfade(fromHandle, toHandle, options)

aura.audio.crossfade(fromHandle: number, toHandle: number, options: {
  duration: number,
  fromVolume?: number,
  toStartVolume?: number,
  toVolume?: number,
  stopFrom?: boolean
}): {
  ok: boolean,
  reasonCode: string | null,
  fromEnvelopeId?: number,
  toEnvelopeId?: number
}
Property Value
Description Deterministic paired fade-out/fade-in helper over two active tracks.
Failure reason codes invalid_from_track_handle, invalid_to_track_handle, missing_from_track, missing_to_track, invalid_crossfade_options, invalid_duration, invalid_from_volume, invalid_to_start_volume, invalid_to_volume
Error behavior Never throws.

6.13 aura.audio.update(dt)

aura.audio.update(dt: number): {
  ok: boolean,
  reasonCode: string | null,
  advanced?: number,
  completed?: number,
  activeEnvelopes?: number
}
Property Value
Description Advance audio envelopes deterministically by explicit timestep.
Failure reason codes invalid_dt
Error behavior Never throws.

6.14 aura.audio.clearEnvelopes()

aura.audio.clearEnvelopes(): {
  ok: boolean,
  reasonCode: null,
  cleared: number
}
Property Value
Description Cancel all pending fade/crossfade envelopes without changing current gains.
Error behavior Never throws.

6.15 aura.audio.getMixerState()

aura.audio.getMixerState(): {
  buses: Array<{ bus: string, volume: number }>,
  tracks: Array<{ handle: number, bus: string, baseVolume: number, effectiveVolume: number, paused: boolean }>,
  envelopes: Array<{ id: number, kind: "track" | "bus", handle: number | null, bus: string | null, start: number, end: number, duration: number, elapsed: number, stopOnComplete: boolean }>,
  nextEnvelopeId: number
}
Property Value
Description Deterministic mixer snapshot for debug tooling.
Error behavior Never throws.

6.16 aura.audio.play3d(path, options?)

aura.audio.play3d(path: string, options?: AudioOptions & {
  position?: { x: number, y: number, z: number },
  nodeId?: number,
  minDistance?: number,  // default: 1
  maxDistance?: number,  // default: 32, must be > minDistance
  rolloff?: number,      // default: 1, must be > 0
  panStrength?: number   // default: 1, clamped to [0,1]
}): {
  ok: boolean,
  reasonCode: string | null,
  handle?: number,
  fallbackMode?: "stereo_2d",
  spatial?: object
}
Property Value
Description Start playback + register deterministic 3D emitter spatialization.
Entity attachment If nodeId is provided, emitter follows aura.scene3d.getWorldTransform(nodeId) each updateSpatial().
Fallback semantics If native spatial backend is unavailable, playback still starts in stereo fallback and returns { ok:false, reasonCode:"spatialization_unavailable_backend", handle, fallbackMode:"stereo_2d" }.
Deterministic attenuation distance <= minDistance => 1; distance >= maxDistance => 0; otherwise pow(1 - ((distance - minDistance) / (maxDistance - minDistance)), rolloff), clamped to [0,1].
Deterministic pan Uses listener yaw (rotation.y) and emitter/listener horizontal delta. pan = clamp(dot(normalizedHorizontalDelta, listenerRight) * panStrength, -1, 1).
Failure reason codes invalid_spatial_options, invalid_node_id, scene3d_unavailable, missing_scene3d_node, invalid_scene3d_transform, invalid_emitter_transform, invalid_min_distance, invalid_max_distance, invalid_rolloff, invalid_pan_strength, spatial_play_failed, spatialization_unavailable_backend

6.17 aura.audio.setListenerTransform(transform)

aura.audio.setListenerTransform(transform: {
  position?: { x: number, y: number, z: number },
  rotation?: { x: number, y: number, z: number }
}): { ok: boolean, reasonCode: string | null, listener?: object }
Property Value
Description Set manual listener transform and detach scene3d listener attachment.
Failure reason codes invalid_listener_transform, plus propagated updateSpatial reason codes.

6.18 aura.audio.attachListener(nodeId)

aura.audio.attachListener(nodeId: number): { ok: boolean, reasonCode: string | null, listener?: object }
Property Value
Description Attach listener to a scene3d node world transform.
Failure reason codes invalid_node_id, scene3d_unavailable, missing_scene3d_node, invalid_scene3d_transform, plus propagated updateSpatial reason codes.

6.19 aura.audio.detachListener()

aura.audio.detachListener(): { ok: boolean, reasonCode: string | null, listener?: object }
Property Value
Description Detach listener from scene3d node and continue from last resolved transform in manual mode.

6.20 aura.audio.setEmitterTransform(handle, transform)

aura.audio.setEmitterTransform(
  handle: number,
  transform: { x: number, y: number, z: number }
): { ok: boolean, reasonCode: string | null, handle?: number, spatial?: object }
Property Value
Description Set manual emitter transform for an active track handle.
Failure reason codes invalid_track_handle, missing_track, invalid_emitter_transform, plus propagated updateSpatial reason codes.

6.21 aura.audio.attachEmitter(handle, nodeId)

aura.audio.attachEmitter(handle: number, nodeId: number): {
  ok: boolean, reasonCode: string | null, handle?: number, spatial?: object
}
Property Value
Description Attach emitter for an active track handle to a scene3d node world transform.
Failure reason codes invalid_track_handle, missing_track, invalid_node_id, scene3d_unavailable, missing_scene3d_node, invalid_scene3d_transform, plus propagated updateSpatial reason codes.

6.22 aura.audio.detachEmitter(handle)

aura.audio.detachEmitter(handle: number): {
  ok: boolean, reasonCode: string | null, handle?: number, detached?: boolean
}
Property Value
Description Remove 3D emitter routing for a track handle.
Failure reason codes invalid_track_handle, missing_emitter

6.23 aura.audio.updateSpatial()

aura.audio.updateSpatial(): {
  ok: boolean,
  reasonCode: string | null,
  updated?: number,
  fallbackCount?: number,
  missingTrackCount?: number,
  listenerMode?: "manual" | "scene3d",
  fallbackReasonCode?: "spatialization_unavailable_backend" | null
}
Property Value
Description Deterministically recompute emitter gain/pan from current listener + emitter transforms.
Failure reason codes scene3d_unavailable, missing_scene3d_node, invalid_scene3d_transform

6.24 aura.audio.getListenerState()

aura.audio.getListenerState(): {
  ok: boolean,
  reasonCode: string | null,
  supported?: boolean,
  mode?: "manual" | "scene3d",
  attachedNodeId?: number | null,
  position?: object,
  forward?: object,
  up?: object,
  spatial?: object,
  hrtfEnabled?: boolean,
  reverbZone?: object | null
}
Property Value
Description Deterministic listener snapshot for runtime verification and debugging.
Failure reason codes None beyond runtime availability failures.

6.25 aura.audio.getSpatialState(handle?)

aura.audio.getSpatialState(handle?: number): {
  ok: boolean,
  reasonCode: string | null,
  supported?: boolean,
  listener?: object,
  emitters?: object[],
  emitter?: object,
  lastReasonCode?: string,
  lastUpdate?: object
}
Property Value
Description Deterministic spatial snapshot for runtime verification and debugging.
Failure reason codes invalid_track_handle, missing_emitter

6.26 Runtime Inspector Integration

Property Value
aura.audio.__inspectorStats() Returns deterministic counters with active, total, callbackQueueDepth, plus spatial fallback metadata.
aura.debug.inspectorStats().phase2.audio Exposes audio phase-2 counters (active, total, callbackQueueDepth) when inspector is enabled.

6.27 Scene-transition mixer pattern (reference)

// setup
const music = aura.audio.play('audio/music/menu.ogg', { loop: true, volume: 0.8, bus: 'music' });
const ambience = aura.audio.play('audio/ambience/wind.ogg', { loop: true, volume: 0.5, bus: 'ambience' });
aura.audio.setBusVolume('sfx', 1);

// on scene switch
const intro = aura.audio.play('audio/music/level-1.ogg', { loop: true, volume: 0.7, bus: 'music' });
aura.audio.crossfade(music, intro, { duration: 0.8, stopFrom: true, toStartVolume: 0, toVolume: 0.7 });
aura.audio.fadeBus('ambience', { to: 0.35, duration: 0.5 });

// each frame
aura.audio.update(dt);
Property Value
Description Recommended deterministic flow for Phaser-style scene transitions: route tracks to named buses, crossfade music tracks, and tick envelopes once per frame.

7. aura.assets

Asset loading namespace. Assets are resolved from the assets/ directory relative to the project root (dev mode) or from the embedded pak archive (release builds).

Release resolver contract:

  • assetMode: "embed" hydrates resolver data from assets/asset-pack.pak (declared by assets-manifest.json) with deterministic path/range validation at startup.
  • assetMode: "sibling" uses disk resolver semantics against sibling assets/ files.

Format Capability Contract

aura.assets exposes deterministic format capability state per extension:

  • supported -> runtime can load now (reasonCode: "asset_format_supported").
  • deferred -> intentionally deferred by policy (reasonCode: "asset_format_deferred").
  • unsupported -> not in the contract (reasonCode: "asset_format_unsupported").

Capability probes are normalized deterministically:

  • Accepted probe forms: path-like ("sprites/player.png"), extension-only ("webp"), and dotted extension-only (".woff2").
  • Probe suffixes after ? or # are ignored for capability classification ("mesh.glb?rev=1" -> extension glb).
  • Classification is case-insensitive on extension tokens.
Kind Supported now Deferred by policy
Image .png, .jpg, .jpeg, .webp, .gif .bmp, .tga, .tiff
Audio .wav, .ogg, .flac, .mp3 .aac, .m4a, .wma
Font .ttf, .otf, .woff2 .woff
Data/Text/Bytes .json, text exts, .bin/.dat/.raw (none)
Model (via aura.assets.load bytes path) .gltf, .glb (none)

7.1 aura.assets.load(path)

aura.assets.load(path: string): Asset
Property Value
Description Load an asset from the asset directory. Returns an opaque asset handle. The asset is cached after first load; subsequent calls with the same path return the cached handle.
Arguments path -- relative path from the assets/ directory (e.g. "player.png", "sounds/jump.wav"). Forward slashes only.
Return value Asset -- opaque handle object. The type of the underlying data depends on the file extension.
Validation path must be a non-empty string. Path traversal (..) is rejected.
Error behavior Throws Error if: the file does not exist ("Asset not found: <path>"), the format is not loadable (`"Unsupported asset format: [reason:<asset_format_deferred\
When to call Recommended in aura.setup(). Can be called in update() but this may cause frame hitches for large assets.

7.2 aura.assets.exists(path)

aura.assets.exists(path: string): boolean
Property Value
Description Check if an asset exists at the given path without loading it.
Arguments path -- relative asset path.
Return value boolean.
Validation Non-string: returns false.
Error behavior Never throws. Returns false for any invalid input.

7.3 aura.assets.loadJson(path)

aura.assets.loadJson(path: string): any
Property Value
Description Load and parse a JSON file from the asset directory. Convenience wrapper: loads the file as text and parses it with JSON.parse.
Arguments path -- relative path to a .json file.
Return value The parsed JSON value (object, array, string, number, boolean, or null).
Validation Same as load(). Additionally validates that the file extension is .json.
Error behavior Throws Error if the file is not found or cannot be read. Throws SyntaxError if the JSON is malformed.

7.4 aura.assets.loadText(path)

aura.assets.loadText(path: string): string
Property Value
Description Load a text file from the asset directory as a UTF-8 string.
Arguments path -- relative path to a text file.
Return value string -- the file contents.
Validation Same as load().
Error behavior Throws Error if the file is not found or cannot be read. Throws Error if the file contains invalid UTF-8.

7.5 aura.assets.loadFont(path)

aura.assets.loadFont(path: string): FontLoadResult
Property Value
Description Load/register a dynamic font asset and return a reason-coded result payload for deterministic error handling.
Arguments path -- relative path to a .ttf/.otf/.woff2 asset.
Return value FontLoadResult where ok=true includes font handle (kind="font", mode="dynamic", id, path).
Validation Non-string, empty, absolute, backslash, or traversal paths return { ok:false, reason:"invalid_path" }.
Error behavior Never throws for validation/load failures. Fails via reason codes: invalid_path, asset_not_found, asset_format_deferred, invalid_asset_format, invalid_font_asset, asset_load_failed.

7.6 aura.assets.loadBitmapFont(options?)

aura.assets.loadBitmapFont(options?: { cellWidth?: number, cellHeight?: number }): FontLoadResult
Property Value
Description Register/use the runtime bitmap fallback font and return a loaded font handle.
Arguments options is optional. Runtime bitmap cell is fixed at 8x8.
Return value FontLoadResult where ok=true includes font handle (kind="font", mode="bitmap", id, glyphWidth=8, glyphHeight=8).
Validation Non-object options return { ok:false, reason:"invalid_options" }. Non-finite/non-integer cell values or non-8x8 values return { ok:false, reason:"invalid_bitmap_cell" }.
Error behavior Never throws for validation failures. Fails via reason codes.

7.7 aura.assets.getFormatSupport(path)

aura.assets.getFormatSupport(path: string): AssetFormatSupport
Property Value
Description Query deterministic format capability status without performing disk I/O. Results are host-specific where the runtime surface differs by target.
Arguments path -- path-like or extension-only probe string.
Return value AssetFormatSupport payload with status, reasonCode, and normalized extension/kind hints. Native hosts report .mp4 as supported; current browser builds report aura.video formats as unsupported.
Error behavior Never throws. Invalid/non-string input returns { ok:false, status:"unsupported", reasonCode:"invalid_format_probe" }.

8. aura.storage

Local persistence namespace. Data is stored as JSON in a platform-specific directory:

  • macOS: ~/Library/Application Support/<game-id>/save.json
  • Linux: ~/.local/share/<game-id>/save.json
  • Windows: %APPDATA%/<game-id>/save.json

The <game-id> is derived from the project configuration. All values must be JSON-serializable. The storage file is a single JSON object keyed by the user-provided key strings.

8.1 aura.storage.save(key, value)

aura.storage.save(key: string, value: any): void
Property Value
Description Persist a value to local storage under the given key. The value is serialized to JSON and written to disk.
Arguments key -- non-empty string. value -- any JSON-serializable value (objects, arrays, strings, numbers, booleans, null).
Return value undefined.
Validation key must be a non-empty string.
Error behavior Throws TypeError if key is not a string or is empty. Throws TypeError if value contains circular references or non-serializable types (functions, symbols, undefined). Disk write failures: throws Error with a descriptive message.
Atomicity Writes are atomic (write-to-temp-then-rename) to prevent data corruption on crash.

8.2 aura.storage.load(key)

aura.storage.load(key: string): any | null
Property Value
Description Retrieve a previously saved value by key.
Arguments key -- string key name.
Return value The deserialized value, or null if the key does not exist.
Validation Non-string key: returns null.
Error behavior Returns null for missing keys (never throws for missing data). Throws Error only if the storage file exists but is corrupted (malformed JSON).

8.3 aura.storage.delete(key)

aura.storage.delete(key: string): void
Property Value
Description Remove a key-value pair from local storage.
Arguments key -- string key name.
Return value undefined.
Validation Non-string key: no-op.
Error behavior Deleting a non-existent key is a silent no-op. Disk write failures: throws Error.

8.4 aura.storage.keys()

aura.storage.keys(): string[]
Property Value
Description Returns an array of all keys currently in local storage.
Arguments None.
Return value string[]. Empty array if no data is stored.
Error behavior Never throws. Returns [] if the storage file does not exist.

9. aura.math

Math utility namespace. Provides common game math functions. These are pure functions with no side effects.

NaN Policy

Math functions propagate NaN -- they do not clamp or substitute default values. If you pass NaN to lerp, you get NaN back. This matches standard JavaScript Math behavior and avoids hiding bugs.

9.1 aura.math.lerp(a, b, t)

aura.math.lerp(a: number, b: number, t: number): number
Property Value
Description Linear interpolation between a and b by factor t. Returns a + (b - a) * t.
Arguments a -- start value. b -- end value. t -- interpolation factor (typically [0.0, 1.0], but not clamped).
Return value number.
Validation Non-number args: returns NaN.
Error behavior Never throws.

9.2 aura.math.clamp(value, min, max)

aura.math.clamp(value: number, min: number, max: number): number
Property Value
Description Clamp value to the range [min, max].
Arguments value, min, max -- numbers. If min > max, the behavior is min takes precedence (returns min).
Return value number.
Validation Non-number args: returns NaN.
Error behavior Never throws.

9.3 aura.math.random(min?, max?)

aura.math.random(): number
aura.math.random(max: number): number
aura.math.random(min: number, max: number): number
Property Value
Description Generate a pseudo-random number. No arguments: returns [0.0, 1.0). One argument: returns [0, max). Two arguments: returns [min, max).
Arguments Overloaded. See above.
Return value number.
Validation Non-number args: returns NaN. If min >= max (two-arg form): returns min.
Error behavior Never throws.
RNG Uses a seeded PRNG internally. Not cryptographically secure.

9.4 aura.math.distance(x1, y1, x2, y2)

aura.math.distance(x1: number, y1: number, x2: number, y2: number): number
Property Value
Description Euclidean distance between two 2D points. Returns sqrt((x2-x1)^2 + (y2-y1)^2).
Arguments x1, y1 -- first point. x2, y2 -- second point.
Return value number (>= 0).
Validation Non-number args: returns NaN.
Error behavior Never throws.

9.5 aura.math.angle(x1, y1, x2, y2)

aura.math.angle(x1: number, y1: number, x2: number, y2: number): number
Property Value
Description Angle in radians from point (x1, y1) to point (x2, y2). Uses Math.atan2(y2 - y1, x2 - x1). Result is in range [-PI, PI].
Arguments x1, y1 -- origin point. x2, y2 -- target point.
Return value number in [-PI, PI].
Validation Non-number args: returns NaN. Same point: returns 0.
Error behavior Never throws.

9.6 Constants

aura.math.PI: number   // 3.141592653589793
aura.math.TAU: number  // 6.283185307179586 (2 * PI)
Property Value
Description Mathematical constants. Read-only.
Type number.
Mutability Frozen (Object.defineProperty with writable: false). Assigning to these is a silent no-op in sloppy mode, throws in strict mode.

9.7 Vec2 / Vec3 Helpers

See Section 12 for the full aura.vec2.* and aura.vec3.* function lists. These are exposed under aura.math as aliases:

aura.math.vec2 === aura.vec2   // same reference
aura.math.vec3 === aura.vec3   // same reference

10. aura.timer

Timer namespace for scheduling deferred and repeating callbacks. Timer callbacks fire at the start of each frame (before lifecycle events and update()), in the order they were registered.

Timer Handle

Timer functions return opaque numeric handles used to cancel them. Handles are invalidated after the timer fires (for after) or is cancelled.

10.1 aura.timer.after(seconds, callback)

aura.timer.after(seconds: number, callback: function): number
Property Value
Description Schedule a callback to fire once after the specified delay.
Arguments seconds -- delay in seconds (must be > 0). callback -- function to call.
Return value number -- timer handle.
Validation seconds <= 0: clamped to the next frame (effectively 0).
Error behavior Throws TypeError if callback is not a function. Non-number seconds: throws TypeError.
Callback errors If the callback throws, the error is caught and handled per the lifecycle error policy (dev: overlay, release: log and exit). The timer is considered fired regardless.

10.2 aura.timer.every(seconds, callback)

aura.timer.every(seconds: number, callback: function): number
Property Value
Description Schedule a callback to fire repeatedly at the specified interval.
Arguments seconds -- interval in seconds (must be > 0). callback -- function to call. Receives no arguments.
Return value number -- timer handle (use with cancel() to stop).
Validation seconds <= 0: clamped to the minimum interval of one frame.
Error behavior Throws TypeError if callback is not a function. Non-number seconds: throws TypeError.
Callback errors If the callback throws, the error is caught per lifecycle policy. The repeating timer continues firing.

10.3 aura.timer.cancel(handle)

aura.timer.cancel(handle: number): void
Property Value
Description Cancel a pending or repeating timer.
Arguments handle -- timer handle from after() or every().
Return value undefined.
Error behavior Invalid or already-fired handle: silent no-op. Never throws.

10.4 aura.timer.getDelta()

aura.timer.getDelta(): number
Property Value
Description Get the frame delta time in seconds. This returns the same dt value that is passed to aura.update(dt). Convenience for code that needs dt outside the update() callback.
Arguments None.
Return value number -- seconds since last frame. Returns 0 before the first frame.
Error behavior Never throws.

10.5 aura.timer.getTime()

aura.timer.getTime(): number
Property Value
Description Get the total elapsed time in seconds since the game started (since setup() completed).
Arguments None.
Return value number -- total seconds elapsed.
Error behavior Never throws.

11. aura.collision

2D collision detection namespace. All functions are pure -- they test for overlap and return a boolean. They do not modify any state.

Shape Conventions

  • Rect: { x: number, y: number, w: number, h: number } -- x, y is the top-left corner.
  • Circle: { x: number, y: number, radius: number } -- x, y is the center.
  • Point: { x: number, y: number }.

11.1 aura.collision.rectRect(a, b)

aura.collision.rectRect(a: Rect, b: Rect): boolean
Property Value
Description Axis-aligned rectangle vs. rectangle overlap test.
Arguments a, b -- objects with { x, y, w, h } properties.
Return value true if the rectangles overlap (inclusive of edges).
Validation Missing or non-number properties: returns false with warning.
Error behavior Never throws.
Edge case Zero-width or zero-height rects: treated as degenerate, returns false.

11.2 aura.collision.rectPoint(rect, point)

aura.collision.rectPoint(rect: Rect, point: Point): boolean
Property Value
Description Test if a point lies inside a rectangle (inclusive of edges).
Arguments rect -- { x, y, w, h }. point -- { x, y }.
Return value boolean.
Validation Missing or non-number properties: returns false with warning.
Error behavior Never throws.

11.3 aura.collision.circleCircle(a, b)

aura.collision.circleCircle(a: Circle, b: Circle): boolean
Property Value
Description Circle vs. circle overlap test.
Arguments a, b -- objects with { x, y, radius } properties.
Return value true if the circles overlap (distance between centers <= sum of radii).
Validation Missing or non-number properties: returns false with warning. Negative radius: treated as 0.
Error behavior Never throws.

11.4 aura.collision.circlePoint(circle, point)

aura.collision.circlePoint(circle: Circle, point: Point): boolean
Property Value
Description Test if a point lies inside a circle (inclusive of boundary).
Arguments circle -- { x, y, radius }. point -- { x, y }.
Return value boolean.
Validation Same as circleCircle.
Error behavior Never throws.

11.5 aura.collision.circleRect(circle, rect)

aura.collision.circleRect(circle: Circle, rect: Rect): boolean
Property Value
Description Circle vs. axis-aligned rectangle overlap test. Uses the nearest-point-on-rect algorithm.
Arguments circle -- { x, y, radius }. rect -- { x, y, w, h }.
Return value boolean.
Validation Same as other collision functions.
Error behavior Never throws.

11A. aura.collision3d

Pure 3D collision helper namespace. These helpers operate on authored math shapes only; they do not inspect the live scene3d graph or the physics world.

3D Shape Conventions

  • Point / Vec3: { x: number, y: number, z: number }
  • Box: { x: number, y: number, z: number, w: number, h: number, d: number } where x, y, z is the minimum corner
  • Sphere: { x: number, y: number, z: number, radius: number }
  • Plane: { x: number, y: number, z: number, nx: number, ny: number, nz: number } where { x, y, z } is a point on the plane and { nx, ny, nz } is the plane normal
  • Ray: { x: number, y: number, z: number, dx: number, dy: number, dz: number, maxDist?: number } where direction is normalized internally
type CollisionPoint3D = { x: number, y: number, z: number };
type CollisionBox3D = { x: number, y: number, z: number, w: number, h: number, d: number };
type CollisionSphere3D = { x: number, y: number, z: number, radius: number };
type CollisionPlane3D = { x: number, y: number, z: number, nx: number, ny: number, nz: number };
type CollisionRay3D = {
  x: number,
  y: number,
  z: number,
  dx: number,
  dy: number,
  dz: number,
  maxDist?: number
};
type CollisionSweepHit3D = {
  hit: true,
  toi: number,
  point: CollisionPoint3D,
  normal: CollisionPoint3D
};
type CollisionRayHit3D = {
  hit: true,
  t: number,
  point: CollisionPoint3D,
  normal: CollisionPoint3D
};
type CollisionTriangleHit3D = CollisionRayHit3D;
type CollisionContactHit3D = {
  hit: true,
  depth: number,
  point: CollisionPoint3D,
  normal: CollisionPoint3D
};
type CollisionCapsuleHit3D = CollisionContactHit3D;

aura.collision3d.boxBox(a: CollisionBox3D, b: CollisionBox3D): boolean
aura.collision3d.boxBoxContact(a: CollisionBox3D, b: CollisionBox3D): CollisionContactHit3D | false
aura.collision3d.sphereSphere(a: CollisionSphere3D, b: CollisionSphere3D): boolean
aura.collision3d.sphereBox(sphere: CollisionSphere3D, box: CollisionBox3D): boolean
aura.collision3d.sphereBoxContact(sphere: CollisionSphere3D, box: CollisionBox3D): CollisionContactHit3D | false
aura.collision3d.raySphere(ray: CollisionRay3D, sphere: CollisionSphere3D): boolean
aura.collision3d.raySphereHit(ray: CollisionRay3D, sphere: CollisionSphere3D): CollisionRayHit3D | false
aura.collision3d.rayBox(ray: CollisionRay3D, box: CollisionBox3D): boolean
aura.collision3d.rayBoxHit(ray: CollisionRay3D, box: CollisionBox3D): CollisionRayHit3D | false
aura.collision3d.pointBox(point: CollisionPoint3D, box: CollisionBox3D): boolean
aura.collision3d.pointSphere(point: CollisionPoint3D, sphere: CollisionSphere3D): boolean
aura.collision3d.spherePlane(sphere: CollisionSphere3D, plane: CollisionPlane3D): boolean
aura.collision3d.rayPlane(ray: CollisionRay3D, plane: CollisionPlane3D): boolean
aura.collision3d.rayPlaneHit(ray: CollisionRay3D, plane: CollisionPlane3D): CollisionRayHit3D | false
aura.collision3d.rayTriangle(ray: CollisionRay3D, v0: CollisionPoint3D, v1: CollisionPoint3D, v2: CollisionPoint3D): CollisionTriangleHit3D | false
aura.collision3d.capsuleCapsule(
  aStart: CollisionPoint3D,
  aEnd: CollisionPoint3D,
  aRadius: number,
  bStart: CollisionPoint3D,
  bEnd: CollisionPoint3D,
  bRadius: number
): CollisionCapsuleHit3D | false
aura.collision3d.closestPoint(
  shape: CollisionBox3D | CollisionSphere3D,
  point: CollisionPoint3D
): CollisionPoint3D | false
aura.collision3d.sphereCast(
  start: CollisionPoint3D,
  end: CollisionPoint3D,
  radius: number,
  target: CollisionBox3D | CollisionSphere3D
): CollisionSweepHit3D | false
aura.collision3d.movingBoxBox(
  a: CollisionBox3D,
  velocityA: CollisionPoint3D,
  b: CollisionBox3D,
  velocityB: CollisionPoint3D,
  dt: number
): CollisionSweepHit3D | false

Semantics:

  • overlap helpers return boolean
  • the older overlap probes remain available as boxBox(...), sphereBox(...), raySphere(...), rayBox(...), and rayPlane(...), all returning only boolean
  • raySphereHit(...), rayBoxHit(...), rayPlaneHit(...), and rayTriangle(...) return { hit: true, t, point, normal } on hit and false on miss or invalid input
  • sphereBoxContact(...), boxBoxContact(...), and capsuleCapsule(...) return { hit: true, depth, point, normal } on hit and false on miss or invalid input
  • for sphereBoxContact(...) and boxBoxContact(...), normal points from the second argument toward the first
  • closestPoint(...) returns the nearest point on or inside the target solid for supported box and sphere targets, or false on invalid input
  • sphereCast(...) and movingBoxBox(...) return { hit: true, toi, point, normal } on hit, where toi is normalized to [0, 1] over the authored sweep interval
  • collision3d is the pure-math lane; use aura.scene3d.queryRaycast(...) for rendered geometry picking and aura.physics3d.queryRaycast(...) for physics-body queries and filters

12. aura.ecs

Opt-in ECS-lite helper namespace for deterministic system ordering. This helper does not change host loop ownership; users call aura.ecs.run(dt) (typically from aura.update(dt)).

12.1 aura.ecs.createEntity()

aura.ecs.createEntity(): number

Returns a positive integer entity id.

12.2 aura.ecs.removeEntity(entityId)

aura.ecs.removeEntity(entityId: number): boolean

Removes the entity and all component values attached to it. Returns true if removed, otherwise false.

12.3 aura.ecs.addComponent(entityId, componentName, value)

aura.ecs.addComponent(entityId: number, componentName: string, value: unknown): boolean

Associates a component value with an entity. Returns false for invalid entity ids, unknown entities, or invalid component names.

12.4 aura.ecs.getComponent(entityId, componentName)

aura.ecs.getComponent(entityId: number, componentName: string): unknown | undefined

Returns the component value for the entity/component pair, or undefined when missing.

12.5 aura.ecs.system(name, fn, order)

aura.ecs.system(name: string, fn: (dt: number) => void, order?: number): boolean

Registers or replaces a system callback. Systems execute in ascending order, then name tiebreak. Component mutations performed by earlier systems are visible to later systems in the same aura.ecs.run(dt) call.

12.6 aura.ecs.run(dt)

aura.ecs.run(dt: number): void

Executes registered systems in deterministic order. For identical initial ECS state and a fixed dt sequence, repeated runs are deterministic.


13. Color and Vector Utilities

Top-level utilities on the aura global object.

12.1 Color Construction

Colors use the [0.0, 1.0] float range (not [0, 255]).

aura.rgb(r, g, b)

aura.rgb(r: number, g: number, b: number): Color
Property Value
Description Create a color with full opacity.
Arguments r, g, b -- floats in [0.0, 1.0]. Values outside this range are not clamped (allows HDR-like values).
Return value { r: number, g: number, b: number, a: 1.0 }.
Validation Non-number args: throws TypeError.
Object Returns a plain frozen object.

aura.rgba(r, g, b, a)

aura.rgba(r: number, g: number, b: number, a: number): Color
Property Value
Description Create a color with explicit alpha.
Arguments r, g, b, a -- floats in [0.0, 1.0].
Return value { r: number, g: number, b: number, a: number }.
Validation Non-number args: throws TypeError.

12.2 Named Color Constants

aura.Color.RED         // { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }
aura.Color.GREEN       // { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }
aura.Color.BLUE        // { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }
aura.Color.WHITE       // { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }
aura.Color.BLACK       // { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
aura.Color.YELLOW      // { r: 1.0, g: 1.0, b: 0.0, a: 1.0 }
aura.Color.CYAN        // { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }
aura.Color.MAGENTA     // { r: 1.0, g: 0.0, b: 1.0, a: 1.0 }
aura.Color.TRANSPARENT // { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }

All named color constants are frozen objects (immutable). Attempting to modify their properties is a silent no-op in sloppy mode, throws in strict mode.

12.3 Vec2

aura.vec2(x, y)

aura.vec2(x: number, y: number): Vec2
Property Value
Description Create a 2D vector.
Return value { x: number, y: number } -- plain mutable object.
Validation Non-number args: throws TypeError.

Vec2 Static Methods

All Vec2 operations are pure functions on the aura.vec2 namespace. They do not mutate their arguments; they return new objects.

aura.vec2.add(a: Vec2, b: Vec2): Vec2

Returns { x: a.x + b.x, y: a.y + b.y }.

aura.vec2.sub(a: Vec2, b: Vec2): Vec2

Returns { x: a.x - b.x, y: a.y - b.y }.

aura.vec2.scale(v: Vec2, s: number): Vec2

Returns { x: v.x * s, y: v.y * s }.

aura.vec2.dot(a: Vec2, b: Vec2): number

Returns a.x * b.x + a.y * b.y.

aura.vec2.length(v: Vec2): number

Returns sqrt(v.x * v.x + v.y * v.y).

aura.vec2.normalize(v: Vec2): Vec2

Returns a unit-length vector in the same direction. If the input is a zero vector, returns { x: 0, y: 0 } (no division by zero).

aura.vec2.distance(a: Vec2, b: Vec2): number

Returns the Euclidean distance between two points.

aura.vec2.lerp(a: Vec2, b: Vec2, t: number): Vec2

Returns component-wise linear interpolation.

Property Value
Validation (all Vec2 methods) If a or b is missing x/y properties: throws TypeError. Non-number property values: result is NaN (propagation).
Error behavior Throws TypeError for structurally invalid arguments. NaN propagation for numeric edge cases.

12.4 Vec3

aura.vec3(x, y, z)

aura.vec3(x: number, y: number, z: number): Vec3
Property Value
Description Create a 3D vector.
Return value { x: number, y: number, z: number } -- plain mutable object.
Validation Non-number args: throws TypeError.

Vec3 Static Methods

Same pattern as Vec2, with the z component:

aura.vec3.add(a: Vec3, b: Vec3): Vec3
aura.vec3.sub(a: Vec3, b: Vec3): Vec3
aura.vec3.scale(v: Vec3, s: number): Vec3
aura.vec3.dot(a: Vec3, b: Vec3): number
aura.vec3.cross(a: Vec3, b: Vec3): Vec3
aura.vec3.length(v: Vec3): number
aura.vec3.normalize(v: Vec3): Vec3
aura.vec3.distance(a: Vec3, b: Vec3): number
aura.vec3.lerp(a: Vec3, b: Vec3, t: number): Vec3
Function Description
add(a, b) Component-wise addition.
sub(a, b) Component-wise subtraction.
scale(v, s) Scalar multiplication.
dot(a, b) Dot product: a.x*b.x + a.y*b.y + a.z*b.z.
cross(a, b) Cross product. Returns a new Vec3 perpendicular to both inputs.
length(v) Euclidean magnitude.
normalize(v) Unit vector. Zero vector returns { x: 0, y: 0, z: 0 }.
distance(a, b) Euclidean distance.
lerp(a, b, t) Component-wise linear interpolation.
Property Value
Validation (all Vec3 methods) If a or b is missing x/y/z properties: throws TypeError.
Error behavior Same as Vec2.

13. Error Semantics Table

This table summarizes the default error handling strategy for each namespace.

Namespace Invalid Arguments Missing Resource Runtime Failure Exception in Callback
Lifecycle N/A (host-invoked) N/A Exception caught per-callback. Dev: error overlay, frame loop continues (callback skipped). Release: error logged to error.log, crash dialog shown, process exits. Same as runtime failure.
aura.window Warning logged, call skipped or uses default N/A Warning logged, never throws N/A
aura.input Returns false or 0 silently N/A (unknown keys return false) Never throws N/A
aura.draw2d Warning logged, call skipped Invalid asset handle: warning, call skipped Never throws N/A
aura.debug Dev: permissive coercion/defaults. Release: explicit silent no-op. N/A Dev: terminal/overlay helper failures are non-fatal best-effort. Release: no-op. N/A
aura.audio Handle operations: silent no-op. play() with missing asset: throws Missing asset: throws Error Handle operations: silent no-op N/A
aura.assets load/exists/loadJson/loadText: throw/return as documented. loadFont/loadBitmapFont: return reason-coded { ok:false, reason } payloads (non-throw). load/exists/loadJson/loadText: throw/return as documented. loadFont/loadBitmapFont: return reason-coded failure payloads. load/exists/loadJson/loadText: throw for read failures. loadFont/loadBitmapFont: non-throw reason-coded failures. N/A
aura.storage save(): throws TypeError. load(): returns null. load(): returns null Serialization failure: throws TypeError. Disk I/O failure: throws Error N/A
aura.math Returns NaN (propagation, no clamping) N/A Never throws N/A
aura.timer Throws TypeError for non-function callback or non-number delay N/A Callback errors: caught per lifecycle error policy Timer callback errors are caught. after() timers: timer considered fired. every() timers: continue firing.
aura.collision Returns false with warning for missing/non-number properties N/A Never throws N/A
aura.rgb / rgba Throws TypeError for non-number args N/A Never throws N/A
aura.vec2 / vec3 Constructors: throws TypeError. Methods: throws TypeError for missing structural properties. NaN propagation for numeric issues N/A Never throws N/A

Error Hierarchy

  1. Throws -- the function throws a JS exception. The caller can catch it with try/catch.
  2. Warning logged -- a console warning is emitted via console.warn(). The call is a no-op or uses a fallback.
  3. Silent no-op -- no warning, no error. The call does nothing. Used for operations on invalid handles that are expected during normal gameplay (e.g., stopping an already-finished sound).
  4. NaN propagation -- the function returns NaN without any warning. Standard JS numeric behavior.

Dev vs. Release Error Modes

Lifecycle callback errors follow these mode-specific rules:

Behavior Dev Mode (AURA_MODE unset or "dev") Release Mode (AURA_MODE="release")
Lifecycle callback errors Error overlay rendered on game window (dark red background). Callback skipped on subsequent frames. Terminal output with ANSI color. Hot-reload clears errors. Error logged to error.log. Native crash dialog shown. Process exits.
Frame timeout Cooperative check (5s default). Warning overlay. Same as lifecycle error.
console.warn() / console.error() Printed to stderr with [warn] / [error] prefix Same (stderr).
console.log() Printed to stdout Printed to stdout.

14. Reference Examples

These reference examples summarize expected behavior for both valid and invalid inputs.

14.1 Lifecycle

Call Input Expected
aura.update(dt) dt = 0.016 Callback receives 0.016 as a number
aura.update(dt) dt = 0 Callback receives 0 (first frame edge case)
aura.onResize(w, h) w = 1920, h = 1080 Callback receives two numbers
Undefined aura.setup aura.setup not defined No error, frame loop starts normally
Undefined aura.draw aura.draw not defined No error, screen cleared to default color
aura.update throws aura.update = () => { throw new Error("boom"); } Dev: overlay shown, subsequent update calls skipped. Release: log + exit

14.2 aura.window

Call Input Expected
setTitle("My Game") Valid string Window title changes
setTitle("") Empty string Warning logged, no-op
setTitle(42) Non-string Coerced to "42", title set
setTitle(null) Null Coerced to "null", title set
setSize(800, 600) Valid numbers Window resized
setSize(-1, 100) Negative width Clamped to setSize(1, 100) with warning
setSize(0, 0) Zero dimensions Clamped to setSize(1, 1) with warning
setSize("800", 600) String arg Warning logged, call skipped
getSize() No args Returns { width: number, height: number }
getPixelRatio() Standard display Returns 1.0
getPixelRatio() Retina display Returns 2.0
setFullscreen(true) Boolean Enters fullscreen
setFullscreen(1) Truthy non-boolean Coerced to true
setCursorVisible(false) Boolean Cursor visibility preference becomes hidden
setCursorVisible(1) Truthy non-boolean Coerced to hidden (true) / visible (false) via !!value
setCursorLocked(true) Boolean Cursor lock requested, current-frame mouse delta cleared
setCursorLocked(false) Boolean Cursor unlock requested, current-frame mouse delta cleared
getFPS() First frame Returns 0

14.3 aura.input

Call Input Expected
isKeyDown("space") Key is held true
isKeyDown("space") Key is not held false
isKeyDown("Space") Case variation true (case-insensitive)
isKeyDown("up") Arrow alias Same result as isKeyDown("arrowup")
isKeyDown("lshift") Modifier alias Same result as isKeyDown("shift")
isKeyDown("rctrl") Modifier alias Same result as isKeyDown("control")
isKeyDown("option") Modifier alias Same result as isKeyDown("alt")
isKeyDown("command") Modifier alias Same result as isKeyDown("meta")
isKeyDown("nonexistent") Unknown key false
isKeyDown(42) Non-string false
isKeyDown() No args false
isKeyPressed("a") Key just went down this frame true
isKeyPressed("a") Key held from previous frame false
isKeyReleased("a") Key just went up this frame true
getTextInput() No printable text entered this frame ""
getTextInput() Text input event delivered this frame Captured printable string
getMousePosition() Cursor in window { x: 400, y: 300 } (example)
getMousePosition() Cursor outside window { x: -10, y: 800 } (example, negative/overflow OK)
getMouseDelta() No mouse motion this frame { x: 0, y: 0 }
getMouseDelta() Relative mouse motion this frame { x: number, y: number }
getMouseDelta() after lock/focus transition Transition frame with no new motion { x: 0, y: 0 }
isMouseDown(0) Left button held true
isMouseDown(5) Invalid button false
isMouseDown("left") Non-number false
isMouseDown(0.5) Fractional button index false
getMouseWheel() No scroll 0
getMouseWheel() Scroll up Positive number
isGamepadConnected(0) Gamepad plugged in true
isGamepadConnected(3) Gamepad in slot 3 true when connected
isGamepadConnected(4) Out of range false
isGamepadConnected(0.5) Fractional index false
getGamepadAxis(0, 0) Left stick X, centered 0.0
getGamepadAxis(0, 0) Left stick X, full right 1.0
getGamepadAxis(3, 5) Slot 3 right trigger Trigger value (0..1)
getGamepadAxis(1, 0) No gamepad at index 1 0
getGamepadAxis(0, 0.5) Fractional axis index 0
isGamepadButtonDown(0, 0) A button pressed true
isGamepadButtonDown(3, 6) Slot 3 Start button true when pressed
isGamepadButtonDown(0, 99) Invalid button false
isGamepadButtonDown(0, 0.5) Fractional button index false

14.4 aura.draw2d

Call Input Expected
clear(aura.Color.BLACK) Valid color Screen cleared to black
clear() No args Default background used ({r:0.08,g:0.08,b:0.12,a:1})
clear({r:1, g:0, b:0}) Missing a a defaults to 1.0, screen cleared to red
rect(10, 20, 100, 50, aura.Color.RED) Valid args Red rectangle outline drawn
rectFill(10, 20, 100, 50, aura.Color.BLUE) Valid args Filled blue rectangle drawn
circle(100, 100, 50, aura.Color.GREEN) Valid args Green circle outline drawn
line(0, 0, 100, 100, aura.Color.RED, -1) Negative width Width is clamped to positive minimum
sprite("player.png", 100, 200) Valid image path Sprite drawn at (100, 200)
sprite("hero.png", 10, 20, { width: 32, height: 16, scaleX: 2, scaleY: 0.5, rotation: 1.25, originX: 0.5, originY: 0.25, tint: aura.rgba(0.1, 0.2, 0.3, 0.4), alpha: 0.5 }) Full options Sprite rendered with transforms; final alpha is 0.2 (tint alpha 0.4 multiplied by alpha 0.5)
sprite("sheet.png", 9, 7, { width: 32, height: 32, frameX: 16, frameY: 8, frameW: 16, frameH: 16 }) Frame rect options Sprite uses source frame (16,8,16,16)
sprite({ path: "../escape.png" }, 10, 20) Traversal path Warning + skipped
text("Hello", 10, 30) Valid string Text drawn with default font
text("Hi", 10, 30, { size: -5 }) Negative size Size clamped to 1
text("Hi", 10, 30, { align: "middle" }) Invalid align Falls back to "left"
text("Hi", 10, 30, { font: { kind: "image", path: "ui.png" } }) Wrong asset kind Warning + fallback to built-in font
text(42, 10, 30) Non-string coerced "42" drawn
measureText("Hello") Valid string Returns { width: N, height: N }
measureText("") Empty string Returns { width: 0, height: 0 }
measureText("Hello", 24) Numeric size shorthand Same sizing semantics as { size: 24 }
measureText("Hello", { size: 18, font: "missing.ttf" }) Missing font Falls back to built-in font metrics
pushTransform() x80 Exceed stack limit First 63 pushes are accepted; remaining pushes log warning and are no-op
popTransform() on empty stack No matching push Warning, no-op
translate(10, 20) Valid numbers Transform applied
rotate() Missing arg Equivalent to rotating by 0
scale(2) Single arg Uniform scale (sx=2, sy=2)
scale(2, 2) Valid numbers Transform applied
Draw call outside draw() aura.draw2d.rect(...) in update() Call ignored; warning logged once per runtime session

14.5 aura.audio

Call Input Expected
play("music.ogg") Valid path, asset loaded Returns handle number (>= 0)
play("music.ogg", {volume: 0.5, loop: true}) With options Plays at half volume, loops
play("nonexistent.ogg") Missing asset Throws Error("Asset not found: nonexistent.ogg")
play(42) Non-string path Throws TypeError
play("music.ogg", {volume: 5.0}) Volume out of range Clamped to 1.0, warning
play("music.ogg", {pitch: 0.01}) Pitch below minimum Clamped to 0.1, warning
stop(handle) Valid handle Sound stops
stop(handle) Already-stopped handle Silent no-op
stop(-1) Invalid handle Silent no-op
stop("abc") Non-number Silent no-op
pause(handle) Playing sound Sound paused
resume(handle) Paused sound Sound resumes
resume(handle) Non-paused sound Silent no-op
setVolume(handle, 0.3) Valid handle and volume Volume changed
setVolume(handle, 2.0) Volume > 1 Clamped to 1.0
setMasterVolume(0.5) Valid volume Master volume changed
setMasterVolume("loud") Non-number Warning, call skipped
stopAll() Sounds playing All sounds stop, handles invalidated
setBusVolume("music", 0.6) Valid bus + volume Returns { ok:true }, bus gain updated
setBusVolume("", 0.6) Empty bus name Returns { ok:false, reasonCode:"invalid_bus" }
assignBus(handle, "music") Valid active handle Returns { ok:true }, track routed
assignBus(999999, "music") Unknown handle Returns { ok:false, reasonCode:"missing_track" }
fadeTrack(handle, {to:0.2, duration:0.5}) Valid active handle Returns { ok:true, envelopeId }, envelope queued
fadeBus("music", {to:0.3, duration:0.5}) Existing bus Returns { ok:true, envelopeId }, envelope queued
crossfade(h1, h2, {duration:0.5}) Two active handles Returns { ok:true, fromEnvelopeId, toEnvelopeId }
update(0) Non-positive dt Returns { ok:false, reasonCode:"invalid_dt" }
getMixerState() Any Deterministic snapshot of buses/tracks/envelopes

14.6 aura.assets

Call Input Expected
load("player.png") File exists Returns asset handle
load("player.png") Called twice Returns same cached handle
load("missing.png") File not found Throws Error("Asset not found: missing.png")
load("data.xyz") Unsupported extension Throws Error("Unsupported asset format: .xyz")
load("") Empty string Throws Error
load(42) Non-string Throws TypeError
load("../secret.txt") Path traversal Throws Error("Invalid asset path: path traversal not allowed")
exists("player.png") File exists true
exists("nope.png") File does not exist false
exists(42) Non-string false
loadJson("config.json") Valid JSON file Returns parsed object
loadJson("bad.json") Malformed JSON Throws SyntaxError
loadJson("missing.json") File not found Throws Error
loadText("readme.txt") Valid text file Returns string contents
loadText("binary.png") Binary file as text Throws Error (invalid UTF-8)

14.7 aura.storage

Call Input Expected
save("score", 100) Valid key and value Saved to disk
save("data", {x: 1, y: [2, 3]}) Nested object Saved as JSON
save("", 100) Empty key Throws TypeError
save(42, 100) Non-string key Throws TypeError
save("fn", () => {}) Non-serializable value Throws TypeError
save("circ", circularObj) Circular reference Throws TypeError
load("score") Key exists Returns 100
load("nonexistent") Key not found Returns null
load(42) Non-string key Returns null
delete("score") Key exists Key removed
delete("nonexistent") Key not found Silent no-op
keys() Data exists Returns ["score", "data"] (example)
keys() No data Returns []

14.8 aura.math

Call Input Expected
lerp(0, 10, 0.5) Valid args 5
lerp(0, 10, 0) t=0 0
lerp(0, 10, 1) t=1 10
lerp(0, 10, 2) t>1 (extrapolation) 20 (not clamped)
lerp(0, 10, -1) t<0 (extrapolation) -10 (not clamped)
lerp("a", 10, 0.5) Non-number NaN
clamp(5, 0, 10) In range 5
clamp(-5, 0, 10) Below min 0
clamp(15, 0, 10) Above max 10
clamp(5, 10, 0) Inverted range 10 (min takes precedence)
clamp("a", 0, 10) Non-number NaN
random() No args Number in [0.0, 1.0)
random(10) One arg Number in [0, 10)
random(5, 10) Two args Number in [5, 10)
random(10, 5) Inverted range 10
distance(0, 0, 3, 4) 3-4-5 triangle 5
distance(0, 0, 0, 0) Same point 0
angle(0, 0, 1, 0) Rightward 0
angle(0, 0, 0, 1) Downward PI/2 (approx 1.5708)
angle(0, 0, -1, 0) Leftward PI (approx 3.14159)
angle(0, 0, 0, 0) Same point 0
PI Read 3.141592653589793
TAU Read 6.283185307179586
PI = 0 Write attempt Strict mode: throws. Sloppy: silent no-op. Value unchanged.

14.9 aura.timer

Call Input Expected
after(1.0, () => console.log("hi")) Valid delay and callback Returns handle, fires after 1 second
after(0, () => {}) Zero delay Fires on the next frame
after(-1, () => {}) Negative delay Clamped to next frame
after(1.0, "not a function") Non-function callback Throws TypeError
after("abc", () => {}) Non-number delay Throws TypeError
every(0.5, () => {}) Valid interval Fires every 0.5 seconds
every(0.5, null) Non-function Throws TypeError
cancel(handle) Valid pending handle Timer cancelled, callback never fires
cancel(handle) Already-fired handle Silent no-op
cancel(-1) Invalid handle Silent no-op
getDelta() During update Same value as dt arg to update()
getDelta() Before first frame 0
getTime() After 5 seconds Approximately 5.0
getTime() At start 0

14.10 aura.collision

Call Input Expected
rectRect({x:0,y:0,w:10,h:10}, {x:5,y:5,w:10,h:10}) Overlapping true
rectRect({x:0,y:0,w:10,h:10}, {x:20,y:20,w:10,h:10}) Non-overlapping false
rectRect({x:0,y:0,w:10,h:10}, {x:10,y:0,w:10,h:10}) Touching edges true (inclusive)
rectRect({x:0,y:0,w:0,h:10}, {x:0,y:0,w:10,h:10}) Zero-width rect false (degenerate)
rectRect({x:0,y:0}, {x:5,y:5,w:10,h:10}) Missing w/h false, warning
rectRect(null, {x:0,y:0,w:10,h:10}) Null argument false, warning
rectPoint({x:0,y:0,w:10,h:10}, {x:5,y:5}) Inside true
rectPoint({x:0,y:0,w:10,h:10}, {x:15,y:5}) Outside false
rectPoint({x:0,y:0,w:10,h:10}, {x:10,y:10}) On corner true (inclusive)
circleCircle({x:0,y:0,radius:5}, {x:3,y:4,radius:5}) Overlapping (dist=5, sum=10) true
circleCircle({x:0,y:0,radius:5}, {x:100,y:0,radius:5}) Far apart false
circleCircle({x:0,y:0,radius:5}, {x:10,y:0,radius:5}) Touching (dist=10, sum=10) true (inclusive)
circleCircle({x:0,y:0,radius:-5}, {x:0,y:0,radius:5}) Negative radius false (negative treated as 0)
circlePoint({x:0,y:0,radius:5}, {x:3,y:4}) Inside (dist=5) true (inclusive)
circlePoint({x:0,y:0,radius:5}, {x:10,y:0}) Outside false
circleRect({x:5,y:5,radius:3}, {x:0,y:0,w:10,h:10}) Circle inside rect true
circleRect({x:15,y:5,radius:3}, {x:0,y:0,w:10,h:10}) Circle overlapping right edge true
circleRect({x:20,y:5,radius:3}, {x:0,y:0,w:10,h:10}) Circle far right false

14.11 Color and Vector Utilities

Call Input Expected
rgb(1, 0, 0) Valid args { r: 1, g: 0, b: 0, a: 1 }
rgb(0.5, 0.5, 0.5) Mid-gray { r: 0.5, g: 0.5, b: 0.5, a: 1 }
rgb("red", 0, 0) Non-number Throws TypeError
rgba(1, 0, 0, 0.5) With alpha { r: 1, g: 0, b: 0, a: 0.5 }
rgba(1, 0, 0) Missing alpha Throws TypeError (4 args required)
Color.RED Read { r: 1, g: 0, b: 0, a: 1 }
Color.RED.r = 0.5 Mutate frozen object Strict mode: throws. Sloppy: silent no-op. Value unchanged.
Color.TRANSPARENT Read { r: 0, g: 0, b: 0, a: 0 }
vec2(3, 4) Valid args { x: 3, y: 4 }
vec2("a", 4) Non-number Throws TypeError
vec2.add({x:1,y:2}, {x:3,y:4}) Valid vecs { x: 4, y: 6 }
vec2.sub({x:5,y:3}, {x:2,y:1}) Valid vecs { x: 3, y: 2 }
vec2.scale({x:2,y:3}, 2) Valid vec + scalar { x: 4, y: 6 }
vec2.dot({x:1,y:0}, {x:0,y:1}) Perpendicular 0
vec2.dot({x:1,y:0}, {x:1,y:0}) Parallel 1
vec2.length({x:3,y:4}) 3-4-5 triangle 5
vec2.length({x:0,y:0}) Zero vector 0
vec2.normalize({x:3,y:4}) Valid vector { x: 0.6, y: 0.8 }
vec2.normalize({x:0,y:0}) Zero vector { x: 0, y: 0 } (no error)
vec2.distance({x:0,y:0}, {x:3,y:4}) Two points 5
vec2.lerp({x:0,y:0}, {x:10,y:10}, 0.5) Midpoint { x: 5, y: 5 }
vec2.add({x:1}, {x:3,y:4}) Missing y Throws TypeError
vec3(1, 2, 3) Valid args { x: 1, y: 2, z: 3 }
vec3.cross({x:1,y:0,z:0}, {x:0,y:1,z:0}) Standard basis cross { x: 0, y: 0, z: 1 }
vec3.add(null, {x:1,y:2,z:3}) Null input Throws TypeError

14.12 `aura.tilemap` Query Bridge (Preview)

The tilemap query bridge is additive and supports deterministic solid-cell queries over a constrained model:

  • Required model fields: width, height, tileWidth, tileHeight.
  • Solid cells may come from:
    • layer data (layers[*].data) when the layer is marked solid (layer.solid === true, layer.collision === true, layer.properties.solid === true, or layer name is listed in solidLayerNames / solidLayers);
    • pre-normalized solidCells entries ({ x, y, layerIndex?, layerName? }).
  • Query methods:
    • aura.tilemap.queryPoint(modelOrMapId, {x, y}) (or positional args)
    • aura.tilemap.queryAABB(modelOrMapId, {x, y, w, h}) (or positional args)
    • aura.tilemap.queryRay(modelOrMapId, {x, y, dx, dy, maxDistance?})
    • aura.tilemap.queryRaycast(...) alias of queryRay(...)
    • aura.tilemap.queryObjects(mapId, options?)
    • aura.tilemap.queryObjectsAtPoint(mapId, point, options?)
    • aura.tilemap.queryObjectsInAABB(mapId, aabb, options?)
  • Runtime mutation helpers:
    • aura.tilemap.setTile(...)
    • aura.tilemap.setRegion(...), removeRegion(...), replaceRegion(...)
    • aura.tilemap.setTileCollision(...), setLayerFlags(...), setLayerVisibility(...), setLayerCollision(...)
  • Determinism contract:
    • hit ordering is stable: layerIndex ascending, then y, then x, then layerName.
    • object query ordering is stable: layerIndex ascending, then object id, then source object order.
  • Invalid inputs are reason-coded and non-throwing:
    • invalid_map_handle
    • invalid_model
    • invalid_point_args
    • invalid_aabb_args
    • invalid_ray_args
    • invalid_tile_args, tile_out_of_bounds, invalid_tile_collision_args
    • invalid_object_layer_ref, invalid_object_property_filter
    • invalid_object_point_args, invalid_object_aabb_args

Known non-goals (preview bridge):

  • Does not include full Tiled feature coverage (animated tiles, custom object templates/text, rotation/flip rendering semantics are out of scope here).

14.13 `aura.navmesh` Pathfinding Bridge (Preview)

The navmesh bridge provides deterministic graph-based path queries for 3D waypoints:

  • Core methods:
    • aura.navmesh.create(definition)
    • aura.navmesh.getInfo(meshId)
    • aura.navmesh.findPath(meshId, start, end) (also accepts { start, end })
    • aura.navmesh.queryPath(...) alias of findPath(...)
    • aura.navmesh.unload(meshId)
  • definition.nodes is required and must be a non-empty array of nodes with:
    • id (positive integer, optional fallback = 1-based index)
    • position ({ x, y, z }, finite numbers)
    • optional neighbors entries (node ids or { to|id|nodeId, cost? })
  • Optional definition.edges may add explicit directional edges ({ from, to, cost? }).
  • definition.bidirectional defaults to true.

Determinism contract:

  • nearest-node resolution from vector start/end is stable (distance, then nodeId ascending tie-break).
  • A* expansion ordering is stable (f, then g, then nodeId ascending).
  • repeated runs with fixed graph + start/end inputs return byte-stable nodeIds/waypoints.

Reason-coded failures are non-throwing:

  • invalid_navmesh_args, invalid_navmesh_nodes, invalid_navmesh_node, invalid_navmesh_node_id, duplicate_navmesh_node_id
  • invalid_navmesh_node_position, invalid_navmesh_neighbor_ref, invalid_navmesh_edges, invalid_navmesh_edge, invalid_navmesh_edge_from, invalid_navmesh_edge_to, invalid_navmesh_edge_cost
  • invalid_navmesh_handle, invalid_path_args, invalid_start, invalid_end, path_not_found

15. Versioning Policy

15.1 Contract Freeze

This document defines the v1 API surface. Upon acceptance, the v1 contract is frozen. All binding implementations (Rust host, V8 bindings) must conform to this document exactly.

15.2 Semantic Versioning

AuraJS follows semantic versioning for its API surface:

Change Type Version Bump Process
New namespace (e.g. aura.physics) Minor Add to contract document. No existing APIs affected.
New function in existing namespace Minor Add to contract document. No existing APIs affected.
New optional parameter on existing function Minor Add with default value. Existing calls continue to work.
New optional field in options object Minor Add with default value. Existing calls continue to work.
Bug fix to match contract (implementation diverged) Patch Fix implementation to match contract.
Change function signature (parameter types, order, count) Major Requires deprecation cycle (see 15.3).
Change return type Major Requires deprecation cycle.
Change error behavior (e.g. throw -> return null) Major Requires deprecation cycle.
Remove function Major Requires deprecation cycle.
Rename function Major Requires deprecation cycle.

15.3 Deprecation Cycle

Before a breaking change is released in a major version:

  1. Minor release N: The old API is marked deprecated. A console.warn() is emitted on first use per session: "[aura] aura.foo.bar() is deprecated and will be removed in v{N+1}. Use aura.foo.baz() instead.".
  2. Minor release N+1 (optional): Additional deprecation warnings if the migration period is deemed too short.
  3. Major release M: The deprecated API is removed. Code using it receives a clear error: "[aura] aura.foo.bar() was removed in v{M}. Use aura.foo.baz() instead.".

Minimum deprecation window: one minor version before removal.

15.4 Runtime Version Query

aura.VERSION: string  // e.g. "1.0.0"
Property Value
Description The AuraJS runtime version string (semver).
Type string.
Mutability Read-only (frozen).

15.5 API Compatibility Check

aura.API_VERSION: number  // 1 for this contract
Property Value
Description The API contract version number. Integer. Incremented on major version bumps that change the API surface.
Type number.
Mutability Read-only (frozen).

Games can guard against version mismatches:

aura.setup = function() {
    if (aura.API_VERSION !== 1) {
        throw new Error("This game requires AuraJS API v1");
    }
};

Appendix A: Complete API Surface (Alphabetical)

For quick reference, every function and property in the v1 contract:

aura.API_VERSION                          number (read-only)
aura.Color.BLACK                          Color (frozen)
aura.Color.BLUE                           Color (frozen)
aura.Color.CYAN                           Color (frozen)
aura.Color.GREEN                          Color (frozen)
aura.Color.MAGENTA                        Color (frozen)
aura.Color.RED                            Color (frozen)
aura.Color.TRANSPARENT                    Color (frozen)
aura.Color.WHITE                          Color (frozen)
aura.Color.YELLOW                         Color (frozen)
aura.VERSION                              string (read-only)
aura.assets.exists(path)                  boolean
aura.assets.load(path)                    Asset
aura.assets.loadFont(path)                FontLoadResult
aura.assets.loadBitmapFont(options?)      FontLoadResult
aura.assets.getFormatSupport(path)        AssetFormatSupport
aura.assets.loadJson(path)               any
aura.assets.loadText(path)               string
aura.audio.pause(handle)                  void
aura.audio.play(path, options?)           number
aura.audio.resume(handle)                 void
aura.audio.setBusVolume(bus, volume)      { ok, reasonCode, ... }
aura.audio.assignBus(handle, bus)         { ok, reasonCode, ... }
aura.audio.fadeTrack(handle, options)     { ok, reasonCode, envelopeId? }
aura.audio.fadeBus(bus, options)          { ok, reasonCode, envelopeId? }
aura.audio.crossfade(from, to, options)   { ok, reasonCode, ... }
aura.audio.update(dt)                     { ok, reasonCode, ... }
aura.audio.clearEnvelopes()               { ok, reasonCode, cleared }
aura.audio.getMixerState()                { buses, tracks, envelopes, nextEnvelopeId }
aura.audio.setMasterVolume(volume)        void
aura.audio.setVolume(handle, volume)      void
aura.audio.stop(handle)                   void
aura.audio.stopAll()                      void
aura.collision.circleCircle(a, b)         boolean
aura.collision.circlePoint(circle, point) boolean
aura.collision.circleRect(circle, rect)   boolean
aura.collision.rectPoint(rect, point)     boolean
aura.collision.rectRect(a, b)             boolean
aura.collision3d.boxBox(a, b)             boolean
aura.collision3d.boxBoxContact(a, b)      { hit, depth, point, normal } | false
aura.collision3d.capsuleCapsule(...)      { hit, depth, point, normal } | false
aura.collision3d.closestPoint(shape, point) { x, y, z } | false
aura.collision3d.movingBoxBox(...)        { hit, toi, point, normal } | false
aura.collision3d.pointBox(point, box)     boolean
aura.collision3d.pointSphere(point, sphere) boolean
aura.collision3d.rayBox(ray, box)         boolean
aura.collision3d.rayBoxHit(ray, box)      { hit, t, point, normal } | false
aura.collision3d.rayPlane(ray, plane)     boolean
aura.collision3d.rayPlaneHit(ray, plane)  { hit, t, point, normal } | false
aura.collision3d.raySphere(ray, sphere)   boolean
aura.collision3d.raySphereHit(ray, sphere) { hit, t, point, normal } | false
aura.collision3d.rayTriangle(...)         { hit, t, point, normal } | false
aura.collision3d.sphereBox(sphere, box)   boolean
aura.collision3d.sphereBoxContact(...)    { hit, depth, point, normal } | false
aura.collision3d.sphereCast(...)          { hit, toi, point, normal } | false
aura.collision3d.spherePlane(sphere, plane) boolean
aura.collision3d.sphereSphere(a, b)       boolean
aura.draw()                               void (user-defined callback)
aura.draw2d.circle(x, y, radius, color)  void
aura.draw2d.circleFill(x, y, r, color)   void
aura.draw2d.clear(color)                  void
aura.draw2d.line(x1, y1, x2, y2, c, w?)  void
aura.draw2d.measureText(str, options?)    {width, height}
aura.draw2d.createRenderTarget(w, h)      {ok, reasonCode, handle?, width?, height?, type?}
aura.draw2d.resizeRenderTarget(rt, w, h)  {ok, reasonCode, handle?, width?, height?, resized?}
aura.draw2d.destroyRenderTarget(rt)       {ok, reasonCode, handle?, destroyed?}
aura.draw2d.withRenderTarget(rt, cb)      {ok, reasonCode, handle?, commandCount?}
aura.draw2d.pushClipRect(...)             {ok, reasonCode}
aura.draw2d.popClipRect()                 {ok, reasonCode}
aura.draw2d.popTransform()                void
aura.draw2d.pushTransform()               void
aura.draw2d.rect(x, y, w, h, color)      void
aura.draw2d.rectFill(x, y, w, h, color)  void
aura.draw2d.rotate(angle)                 void
aura.draw2d.scale(sx, sy)                 void
aura.draw2d.sprite(image, x, y, opts?)    void
aura.draw2d.tileSprite(image, x, y, w, h, opts?) void
aura.draw2d.nineSlice(image, x, y, w, h, opts?)  void
aura.draw2d.text(str, x, y, options?)     void
aura.draw2d.translate(x, y)              void
aura.input.getGamepadAxis(idx, axis)      number
aura.input.getTextInput()                 string
aura.input.getMouseDelta()                {x, y}
aura.input.getMousePosition()             {x, y}
aura.input.getMouseWheel()                number
aura.input.isGamepadButtonDown(idx, btn)  boolean
aura.input.isGamepadConnected(index)      boolean
aura.input.isKeyDown(key)                 boolean
aura.input.isKeyPressed(key)              boolean
aura.input.isKeyReleased(key)             boolean
aura.input.isMouseDown(button)            boolean
aura.input.isMousePressed(button)         boolean
aura.math.PI                              number (read-only)
aura.math.TAU                             number (read-only)
aura.math.angle(x1, y1, x2, y2)          number
aura.math.clamp(value, min, max)          number
aura.math.distance(x1, y1, x2, y2)       number
aura.math.lerp(a, b, t)                  number
aura.math.random(min?, max?)              number
aura.onBlur()                             void (user-defined callback)
aura.onFocus()                            void (user-defined callback)
aura.onResize(width, height)              void (user-defined callback)
aura.rgb(r, g, b)                         Color
aura.rgba(r, g, b, a)                     Color
aura.setup()                              void (user-defined callback)
aura.storage.delete(key)                  void
aura.storage.keys()                       string[]
aura.storage.load(key)                    any | null
aura.storage.save(key, value)             void
aura.timer.after(seconds, callback)       number
aura.timer.cancel(handle)                 void
aura.timer.every(seconds, callback)       number
aura.timer.getDelta()                     number
aura.timer.getTime()                      number
aura.tilemap.draw(mapId, options?)        TilemapDrawSummary | null
aura.tilemap.drawLayer(mapId, layer, options?) TilemapLayerDrawSummary | null
aura.tilemap.getInfo(mapId)               TilemapInfo | null
aura.tilemap.import(source)               number
aura.tilemap.unload(mapId)                boolean
aura.update(dt)                           void (user-defined callback)
aura.vec2(x, y)                           Vec2
aura.vec2.add(a, b)                       Vec2
aura.vec2.distance(a, b)                  number
aura.vec2.dot(a, b)                       number
aura.vec2.length(v)                       number
aura.vec2.lerp(a, b, t)                   Vec2
aura.vec2.normalize(v)                    Vec2
aura.vec2.scale(v, s)                     Vec2
aura.vec2.sub(a, b)                       Vec2
aura.vec3(x, y, z)                        Vec3
aura.vec3.add(a, b)                       Vec3
aura.vec3.cross(a, b)                     Vec3
aura.vec3.distance(a, b)                  number
aura.vec3.dot(a, b)                       number
aura.vec3.length(v)                       number
aura.vec3.lerp(a, b, t)                   Vec3
aura.vec3.normalize(v)                    Vec3
aura.vec3.scale(v, s)                     Vec3
aura.vec3.sub(a, b)                       Vec3
aura.window.getFPS()                      number
aura.window.getPixelRatio()               number
aura.window.getSize()                     {width, height}
aura.window.setCursorLocked(locked)       void
aura.window.setCursorVisible(visible)     void
aura.window.setFullscreen(enabled)        void
aura.window.setSize(width, height)        void
aura.window.setTitle(title)               void

Total v1 surface: 90 entries (10 namespaces, 6 lifecycle callbacks, 9 color constants, 2 version properties).


Appendix B: Type Definitions (TypeScript notation)

For tooling and documentation purposes. AuraJS does not require TypeScript, but these types serve as a precise specification.

// --- Core types ---

interface Color {
    readonly r: number;  // [0.0, 1.0]
    readonly g: number;  // [0.0, 1.0]
    readonly b: number;  // [0.0, 1.0]
    readonly a: number;  // [0.0, 1.0]
}

interface Vec2 {
    x: number;
    y: number;
}

interface Vec3 {
    x: number;
    y: number;
    z: number;
}

interface Rect {
    x: number;
    y: number;
    w: number;
    h: number;
}

interface Circle {
    x: number;
    y: number;
    radius: number;
}

interface Point {
    x: number;
    y: number;
}

// --- Opaque handles ---

type Asset = object;       // opaque, returned by aura.assets.load()
type LoadedFont = {
    id: number;
    kind: "font";
    mode: "dynamic" | "bitmap";
    path?: string;
    glyphWidth?: number;
    glyphHeight?: number;
};
type FontLoadResult =
    | { ok: true; reason: null; font: LoadedFont }
    | { ok: false; reason: string; font: null };
type AssetFormatSupport =
    | {
        ok: true;
        path: string;
        extension: string | null;
        kindHint: string;
        status: "supported" | "deferred" | "unsupported";
        reasonCode: "asset_format_supported" | "asset_format_deferred" | "asset_format_unsupported";
        supported: boolean;
        deferred: boolean;
      }
    | {
        ok: false;
        status: "unsupported";
        reasonCode: "invalid_format_probe";
        extension: null;
        kindHint: null;
      };
type AudioHandle = number; // opaque, returned by aura.audio.play()
type TimerHandle = number; // opaque, returned by aura.timer.after/every()

// --- Option bags ---

interface SpriteOptions {
    scaleX?: number;    // default: 1.0
    scaleY?: number;    // default: 1.0
    rotation?: number;  // radians, clockwise, default: 0.0
    originX?: number;   // 0.0-1.0, default: 0.0
    originY?: number;   // 0.0-1.0, default: 0.0
    tint?: Color;       // default: WHITE
    alpha?: number;     // 0.0-1.0, default: 1.0
}

interface TextOptions {
    font?: string | Asset | LoadedFont;  // default: built-in monospace
    size?: number;      // default: 16
    color?: Color;      // default: WHITE
    align?: "left" | "center" | "right";  // default: "left"
}

interface TextMeasureOptions {
    font?: string | Asset | LoadedFont;  // default: built-in monospace
    size?: number;      // default: 16
}

interface AudioOptions {
    volume?: number;    // 0.0-1.0, default: 1.0
    loop?: boolean;     // default: false
    pitch?: number;     // 0.1-10.0, default: 1.0
}

End of AuraJS API Contract.

DOCUMENT REFERENCE
docs/api-contract.md
AURAJS
Cmd/Ctrl+K
aurajsgg