
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.mdpath.
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.
Use one of these narrower exact-reference pages before reading this file end-to-end:
This core contract is canonical for JS-visible signatures.
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:
aura.API_VERSION === 1 as a read-only property.aura.VERSION (semver string) as a read-only property.aura.math.PI and aura.math.TAU are read-only constants.aura.API_VERSION per Section 15.Boundary drift notes:
aura.draw3d, aura.camera3d, aura.light, aura.mesh, aura.material, aura.input.mouse, aura.input.gamepad, aura.input.actionMap.aura.draw2d.image/push/pop, aura.storage.get/setaura.animation.__inspectorStats, aura.scene3d.__inspectorStats, aura.tween.__inspectorStatsaura.physics.__*, aura.net.__*Core contract scope boundary:
aura.animation (timeline primitives for play/pause/seek/loop/speed with reason-coded validation failures).aura.tween (single-axis tween/easing primitives with deterministic IDs/update ordering and reason-coded validation failures).aura.particles (constrained particles-lite emit/update/draw/stop helper with deterministic emitter IDs and pool behavior).aura.draw3d, aura.camera3d, aura.light, aura.mesh, aura.material).aura.scene3d (deterministic parent/child transform composition plus clip/skinning runtime helpers; does not own rendering or scene submission).aura.scene (deterministic scene create/start/pause/resume/update transitions with reason-coded responses).aura.multiplayer (host/client session lifecycle, state sync, and deterministic callback dispatch).physics, net, multiplayer) are contract-defined only when enabled via modules.* (see Sections 1.1 and 1.2).This document is primarily for JS API signatures, but v1 build behavior is pinned here to prevent docs/help drift:
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.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.--asset-mode embed in v1.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/*.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.assets-manifest.json or build-manifest.json; web-build-manifest.json is the browser source of truth.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).| 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:
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:
modules.steam, modules: ["steam"], or AURA_MODULE_STEAM), the runtime emits deterministic deprecation guidance with reason code legacy_modules_steam_ignored.physics, network, and multiplayer.aura.multiplayer.* is available only when modules.multiplayer=true.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).[], 0, null, or false) and host-only mutators (kick, setPlayerData, setState) return false unless the local runtime is hosting.--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.--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.aura surface.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).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.stepCount: fixed sub-steps executed in the last scheduler integration.accumulatorCarry: leftover dt (seconds) carried into the next integration.Determinism contract:
stepCount, accumulatorCarry, and body transforms.This addendum defines the public joint/constraint surface for the optional 3D physics runtime.
Availability:
aura.physics3d.* is available only when modules.physics=true.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:
revolute, ball,
prismatic, and fixed.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.type must be revolute|ball|prismatic|fixed.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.false.getState() / state return null.Truthful limits:
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.modules.* = true hint).Physics callback contract (Body.onCollide(callback)):
otherBodyId (number).a: + static body id)b: + dynamic body id)Networking callback contract (Connection.onDisconnect(callback)):
onDisconnect listeners fire exactly once per connection close transition.close()/disconnect() call returns true and triggers disconnect callback dispatch.false and MUST NOT re-dispatch disconnect callbacks.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:
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.order, then registration order.getState returns a deterministic snapshot suitable for gameplay polling and tooling.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:
false/null and never throw.setParent(node, null) detaches to root; self-parenting, missing-parent links, and cycle creation attempts return false.setLocalTransform updates are visible immediately to getWorldTransform and the same-frame traverse callback stream.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.distance <= cameraDistance (stable tie behavior at exact thresholds), with fallback to lower configured levels, optional fallbackMeshHandle, then base mesh.setCameraLayerMask) and per-node layer indices in [0, 31].cullRadius.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.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..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.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.scene3dRuntime.importedScenes with deterministic last-import fingerprints (lastOrderFingerprint, lastSceneFingerprint, lastRuntimeFingerprint).invalid_clip_options, invalid_clip_id, missing_clip, invalid_crossfade_duration, etc.).order, then registration order, and are exposed in inspector telemetry (scene3dRuntime.animation.*).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:
update(dt) processes timelines in ascending timeline-id order.duration sets completed=true and playing=false.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.update(dt) tick is deterministic:order ascendingblendMode ("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.seek during an active transition deterministically cancels the transition and applies the target time.crossfade(...) reuses transition validation reason codes (invalid_transition_options, invalid_transition_duration, invalid_time) for deterministic failure handling.dt sequences MUST produce identical state snapshots.registerAtlas(...) ingests a constrained atlas/spritesheet metadata shape:frames must be a non-empty object map.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).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.{ ok: false, reason: <code> } and never throw.Migration guidance:
transition(...) plus ordered onEvent hooks.crossfade(...) when porting three.js-style mixer blend flows (explicit fromTime -> toTime handoff in a single deterministic step).onEvent marker/loop callbacks and map clip-end effects to onComplete.seek(...), re-issue transition intent explicitly if a blend should continue.registerAtlas(...), then use resolveAtlasFrame(...) / stepAtlasClip(...) to keep draw2d frame selection deterministic.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 flowssrc/starter-utils/combat-feedback-2d.js for floating text, hit sparks,
hit flashes, and hover/lift feedbackPrefer 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:
update(dt) processes tweens in ascending tween-id order.invalid_easing.[0, 1]; completion sets completed=true and playing=false.update(dt) tick is deterministic:order ascendingpause freezes progress; resume continues from current elapsed/progress state.cancel removes the tween deterministically; subsequent getState for that id returns null.{ ok: false, reason: <code> } and never throw.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:
update(dt) processes emitters in ascending emitter-id order and advances all live particles deterministically.2562048512draw(...) delegates particle render submission to aura.draw2d.circleFill(...); camera transforms apply through normal draw2d ownership/call-order behavior.{ ok: false, reason: <code> } and never throw.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):
width, height, tilewidth, tileheight, layers, tilesets.tilelayer, objectgroup (other layer types are ignored by this helper).width * height (layer width/height fallback to map width/height when omitted).id, name, type/class, x, y, width, height, rotation, visible, gid, point, ellipse, polygon, polyline, properties).template and text payloads are rejected in this stage.firstgid, image, columns required; tilewidth/tileheight optional (fallback to map tile size); tilecount, margin, spacing optional.{ key: primitive }), or[{ name, value, ... }]).string, finite number, boolean, null.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.camera/view rectangle intersection; when no valid rectangle is provided, culling is disabled for that call.setTile, setRegion, removeRegion, replaceRegion, setTileCollision, layer-flag setters) are non-throwing and return reason-coded { ok, reasonCode } payloads on invalid input.setTileCollision(...).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.aura.draw2d.sprite(...) and remains subject to normal draw-phase ownership rules.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
}
}
AURA_MODE not set to release), helpers are active.AURA_MODE=release), all aura.debug.* helpers are deterministic no-op and never throw.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.phase2.animation, phase2.tilemap, phase2.scene3d, and phase2.particles are deterministic non-negative counter rows when those helper namespaces are available.null to preserve low-overhead baseline behavior.Registry-backed AuraJS projects also ship a JS-first app-shell layer above the low-level namespaces:
src/runtime/scene-flow.jssrc/runtime/screen-shell.jssrc/starter-utils/triggers.jsThese helpers are scaffold/runtime surfaces, not new aura.* namespaces.
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:
currentSceneIdcurrentstackdepthcanPopmutationCountCanonical reason codes:
scene_flow_okscene_flow_invalid_scene_idscene_flow_unknown_scenescene_flow_cannot_pop_rootThe same runtime mounts authored screens through one explicit model:
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:
hudoverlaymodalsmodalDepthmutationCountCanonical reason codes:
screen_shell_okscreen_shell_invalid_screen_idscreen_shell_unknown_screenscreen_shell_empty_modal_stackNon-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:
Canonical schemas and reason codes:
aurajs.trigger-tracker.v1aurajs.interaction-prompt.v1trigger_step_oktrigger_invalid_definitiontrigger_invalid_subjecttrigger_interaction_readytrigger_interaction_acceptedtrigger_interaction_unavailableRegistry-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:
aura.scene, promise a visual editor, add
dialogue/inventory/save systems, or introduce hosted preview/deploy surfaces.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.
The per-frame order is:
onResize, onFocus, onBlur) -- fired if SDL2 events occurredaura.update(dt)aura.draw()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. |
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. |
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}). |
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. |
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. |
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. |
Window management namespace. All sizes are in logical pixels (points). On HiDPI displays, physical pixels differ from logical pixels by the pixel ratio.
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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.
Canonical key names stored in the runtime are lowercased:
"a" through "z""0" through "9""arrowup", "arrowdown", "arrowleft", "arrowright""shift", "control", "alt", "meta""space", "enter", "escape", "tab", "backspace", "delete""f1" through "f12"Accepted aliases are normalized before lookup:
"up"/"uparrow" -> "arrowup", "down"/"downarrow" -> "arrowdown", "left"/"leftarrow" -> "arrowleft", "right"/"rightarrow" -> "arrowright""lshift", "rshift", "leftshift", "rightshift", "shiftleft", "shiftright" -> "shift""ctrl", "lctrl", "rctrl", "leftctrl", "rightctrl", "controlleft", "controlright" -> "control""lalt", "ralt", "leftalt", "rightalt", "altleft", "altright", "option", "loption", "roption", "leftoption", "rightoption" -> "alt""cmd", "command", "meta", "lmeta", "rmeta", "super", "win", "windows" -> "meta"| Value | Button |
|---|---|
0 |
Left |
1 |
Middle |
2 |
Right |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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.
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.aura.Color.RED.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.
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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.
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).
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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. |
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 |
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. |
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. |
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. |
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. |
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. |
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 |
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 |
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. |
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 |
| 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. |
// 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. |
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.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:
"sprites/player.png"), extension-only ("webp"), and dotted extension-only (".woff2").? or # are ignored for capability classification ("mesh.glb?rev=1" -> extension glb).| 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) |
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: |
| When to call | Recommended in aura.setup(). Can be called in update() but this may cause frame hitches for large assets. |
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. |
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. |
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. |
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. |
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. |
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" }. |
Local persistence namespace. Data is stored as JSON in a platform-specific directory:
~/Library/Application Support/<game-id>/save.json~/.local/share/<game-id>/save.json%APPDATA%/<game-id>/save.jsonThe <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.
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. |
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). |
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. |
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. |
Math utility namespace. Provides common game math functions. These are pure functions with no side effects.
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.
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. |
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. |
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. |
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. |
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. |
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. |
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
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 functions return opaque numeric handles used to cancel them. Handles are invalidated after the timer fires (for after) or is cancelled.
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. |
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. |
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. |
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. |
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. |
2D collision detection namespace. All functions are pure -- they test for overlap and return a boolean. They do not modify any state.
{ x: number, y: number, w: number, h: number } -- x, y is the top-left corner.{ x: number, y: number, radius: number } -- x, y is the center.{ x: number, y: number }.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. |
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. |
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. |
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. |
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. |
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.
{ x: number, y: number, z: number }{ x: number, y: number, z: number, w: number, h: number, d: number }
where x, y, z is the minimum corner{ x: number, y: number, z: number, radius: number }{ 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{ x: number, y: number, z: number, dx: number, dy: number, dz: number, maxDist?: number }
where direction is normalized internallytype 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:
booleanboxBox(...), sphereBox(...),
raySphere(...), rayBox(...), and rayPlane(...), all returning only
booleanraySphereHit(...), rayBoxHit(...), rayPlaneHit(...), and
rayTriangle(...) return { hit: true, t, point, normal } on hit and
false on miss or invalid inputsphereBoxContact(...), boxBoxContact(...), and capsuleCapsule(...)
return { hit: true, depth, point, normal } on hit and false on miss or
invalid inputsphereBoxContact(...) and boxBoxContact(...), normal points from
the second argument toward the firstclosestPoint(...) returns the nearest point on or inside the target solid
for supported box and sphere targets, or false on invalid inputsphereCast(...) and movingBoxBox(...) return { hit: true, toi, point, normal }
on hit, where toi is normalized to [0, 1] over the authored sweep
intervalcollision3d is the pure-math lane; use aura.scene3d.queryRaycast(...)
for rendered geometry picking and aura.physics3d.queryRaycast(...) for
physics-body queries and filtersOpt-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)).
aura.ecs.createEntity(): number
Returns a positive integer entity id.
aura.ecs.removeEntity(entityId: number): boolean
Removes the entity and all component values attached to it. Returns true if removed, otherwise false.
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.
aura.ecs.getComponent(entityId: number, componentName: string): unknown | undefined
Returns the component value for the entity/component pair, or undefined when missing.
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.
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.
Top-level utilities on the aura global object.
Colors use the [0.0, 1.0] float range (not [0, 255]).
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: 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. |
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.
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. |
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. |
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. |
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. |
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 |
console.warn(). The call is a no-op or uses a fallback.NaN without any warning. Standard JS numeric behavior.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. |
These reference examples summarize expected behavior for both valid and invalid inputs.
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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) |
| 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 [] |
| 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. |
| 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 |
| 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 |
| 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 |
The tilemap query bridge is additive and supports deterministic solid-cell queries over a constrained model:
width, height, tileWidth, tileHeight.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);solidCells entries ({ x, y, layerIndex?, layerName? }).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?)aura.tilemap.setTile(...)aura.tilemap.setRegion(...), removeRegion(...), replaceRegion(...)aura.tilemap.setTileCollision(...), setLayerFlags(...), setLayerVisibility(...), setLayerCollision(...)layerIndex ascending, then y, then x, then layerName.layerIndex ascending, then object id, then source object order.invalid_map_handleinvalid_modelinvalid_point_argsinvalid_aabb_argsinvalid_ray_argsinvalid_tile_args, tile_out_of_bounds, invalid_tile_collision_argsinvalid_object_layer_ref, invalid_object_property_filterinvalid_object_point_args, invalid_object_aabb_argsKnown non-goals (preview bridge):
The navmesh bridge provides deterministic graph-based path queries for 3D waypoints:
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)neighbors entries (node ids or { to|id|nodeId, cost? })definition.edges may add explicit directional edges ({ from, to, cost? }).definition.bidirectional defaults to true.Determinism contract:
nodeId ascending tie-break).f, then g, then nodeId ascending).nodeIds/waypoints.Reason-coded failures are non-throwing:
invalid_navmesh_args, invalid_navmesh_nodes, invalid_navmesh_node, invalid_navmesh_node_id, duplicate_navmesh_node_idinvalid_navmesh_node_position, invalid_navmesh_neighbor_ref, invalid_navmesh_edges, invalid_navmesh_edge, invalid_navmesh_edge_from, invalid_navmesh_edge_to, invalid_navmesh_edge_costinvalid_navmesh_handle, invalid_path_args, invalid_start, invalid_end, path_not_foundThis 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.
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. |
Before a breaking change is released in a major version:
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."."[aura] aura.foo.bar() was removed in v{M}. Use aura.foo.baz() instead.".Minimum deprecation window: one minor version before removal.
aura.VERSION: string // e.g. "1.0.0"
| Property | Value |
|---|---|
| Description | The AuraJS runtime version string (semver). |
| Type | string. |
| Mutability | Read-only (frozen). |
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");
}
};
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).
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.