Repository navigation
Aura3D 4.1.0
Pre-release
Pre-release
Superseded. This version is deprecated. Upgrade to Aura3D 4.7.12.
A large release: eight reusable kits extracted from the eleven Crayon game ports and the Aura Slam 3D conversion, plus the engine fixes those games exposed. Everything below is in the published @aura3d/* packages unless it is listed under Tooling and examples.
Highlights
- Library kits for games: a published game kit that defaults on at
@aura3d/engine/game, a QA and replay hook kit, an opt-in runtime safety net (?diag=1, black-frame self-heal), a procedural audio synth kit, a production model loader, a GLSL chunk library, seeded scatter planning with static merge, and racket-sport kits (exact ball flight, scoring, athlete rig). - Engine fixes found on real hardware and in production builds: sun shadows no longer darken far more than the caster's footprint, instanced and batched meshes show their textures, empty instance arrays no longer blank the canvas, custom shaders are NaN-safe on Apple GPUs, and repeated
model()nodes share decoded textures. - CLI asset tools:
assets optimize,assets merge-clips, an isolatedassets import-meshy, and ameshy ledgercommand. - Deprecations extended: the ten deprecated
@aura3d/engine/*subpaths and the deprecated root symbols are now planned for removal in 5.0.0, not 4.1.0 (see Deprecations).
Library kits
- New:
game.racketSports().scoring(rules?)(createRacketScoring): a pure game/match/tiebreak scoring state machine for rally sports. The defaults are the Aura Slam tennis rules (4 points win by 2, first to 3 games leading by 2, tiebreak to 7 win by 2 at 3-3);pointsToGame,winBy,gamesToMatch,matchWinByandtiebreakare configurable, so table-tennis style formats work too.awardPoint(state, side)returns a new state plusgameWon/matchWon/tiebreakStarted, andlabel(own, opponent)gives 0/15/30/40/AD. Verified point for point against the original tennis simulation over 2,000 seeded matches. - New:
game.racketSports().ballFlight(config)is an exact, frame-rate independent ball-flight kit for racket sports (x lateral, y along the court, z up): closed-form flight under gravity and drag, exact-time bounce/net/tape/board events, surface rebound and rolling, a shot solver that clears the net, a serve solver and a loose-ball settle. It was extracted from the Aura Slam 3D simulation and reproduces the original game's physics to 1e-12 (docs/api/ball-flight.md). The existingstepBallPhysicsremains as the simple model. - New:
game.racketSports().athleteRig(docs/api/racket-athlete-rig.md): a procedural racket-sport athlete extracted from the Aura Slam port.locomotion()is a pure stride/split-step/slide/planted-stance solver andapplyLimbs()poses leg, arm (two-bone IK) and racket cylinders from root-local points;solveKnee()is the knee helper. Verified bit-identical against the original game's pose code (60,000 cases). The five existing geometry helpers moved to an internal module and are re-exported unchanged. - New:
createBindingStorein the game kit (packages/game): validated, persisted, rebindable input bindings (keyboard primary + optional alternate keys, controller buttons), reserved keys with custom messages, conflict detection, per-device fallback on corrupt or wrong-version storage, andsaved: falseinstead of throwing on write failure. Extracted from the Aura Slam 3D controls store; an equivalence test shows it validates, persists and restores identically when configured with that game's actions. It is exported from@aura3d/game(private until the game kit is published through@aura3d/engine/game). - New: QA and replay kit (
docs/api/qa-kit.md):installQaHooks,createFixedStepLoop(aliasfixedStepLoop),readQaParams,createCountedRngand the hook-name helpers, exported from@aura3d/engine. They replace thewindow.__AURA_<GAME>__hooks and fixed-timestep loops that every Crayon port copied into its ownmain.ts. The Splat Rush port is migrated as the reference. - New: opt-in runtime safety net in
@aura3d/debug(installSafetyNet,resolveQualitySwitches,createBlackFrameWatch,installDiagnosticsPanel). Parses the shared quality URL switches (?post=0 ?shadow=0 ?env=0 ?sky=0 ?aa=0 ?quality=low ?tm=aces ?heal=0 ?diag=1), reloads one rung down a compatibility ladder when the canvas stays black (SAFETY_BLACK_FRAME/SAFETY_COMPAT_MODE), and shows a GPU diagnostics panel (renderer, limits, draw calls, shader compile/link failures, context loss, last errors). Nothing runs unlesssafetyis set; seedocs/api/safety-net.md. - New:
defineModelAssets(manifest, options?)andmodelsEnabledFromUrl()declare a game's GLBs once with the sha256 and same-origin URL the production bridge requires, validate them at definition time (a bad hash or cross-origin URL used to draw nothing silently), return typed refs, and carry the procedural-fallback switch (?models=0, orenabled) as an option. Seedocs/api/model-loader.md. - New: procedural audio synth kit in
@aura3d/audio(also@aura3d/engine/audio).createCueBank({ cues })plays sample-free cues built from oscillator and filtered-noise layers (envelopes, pitch glides, delays, detune) on the page's singleAudioContextManager, with first-gesture unlock, a master mute and key binding (install()), SFX/music buses, a voice budget with priorities, per-cue cooldown/concurrency limits and music ducking.createStepSequenceris a lookahead music scheduler,createToneLoopa parameter-driven bed (engine drone, wind, pad), andmidiToHz,noteFrom,SCALES,scaleSemitonesand a deterministiccreateNoiseSourcecover pitch and noise. It replaces the hand-rolled Web Audio code the ports each carried; the Splat Rush port now uses it. Seedocs/api/audio-synth.md. - New: recorded takes in the synth kit (
createSampleSlices,SampleSlices; docs indocs/api/audio-synth.md). Plays one slice of a sprite sheet per call on the cue bank's buses, never repeating the previous take, through an optional low-pass, gain and stereo panner, with a procedural fallback until the sheet decodes.CuePlayParams.datacarries a free-form payload to a cue's layer function. Extracted from Aura Slam'stennis-audio.js. - New: author GLSL chunk library for
material.custom(docs/api/glsl-chunks.md).#include <a3d_math>(saturate, safe divide/sqrt/acos/normalize, remap,a3dFinite),<a3d_noise>(sin-free hashes, value and simplex noise, fbm, ridged),<a3d_sky>,<a3d_fog>(linear, exp2, analytic height fog),<a3d_triplanar>,<a3d_toon>, plus<colorspace_decode>and the new<colorspace_encode>. All are GLSL ES 3.00,a3d-prefixed, NaN-safe andpow-free; a materialpatchorregisterShaderChunkstill overrides them. Verified on the mock device and on a real WebGL2 context (every chunk links; known-value probes render exact colours). They are GLSL only, so a material that includes them has no WGSL twin. - New: scatter planning and static merge (
scatterAlongPath,scatterInArea,splitScatterByDistance,scatterTransforms,createScatterRandom,mergeStatic,mergeStaticByKey; docs indocs/api/scatter-merge.md). Seeded, filter-stable placement along paths or over areas (exclusion zones, minimum spacing, height and slope filters, radius LOD bands) that feedsinstances.*, and a transform-and-tint baker that turns many static parts into one geometry per material. Extracted from the hand-written placement and merge code in the Crayon ports; the Cloud Runner mesa layout is reproduced exactly for its seed intests/unit/scatter.
Engine fixes
- Fix:
material.customshaders are NaN-safe on real GPUs. GLSL leavespow(x, y)undefined for a negative base; software GL evaluates it, but Apple/Metal GPUs return NaN, which blending writes into the HDR target and bloom smears over the whole frame (a black scene on an Apple M4 Max in one port). Custom shaders whose source usespownow get a totala3dPow(pow(max(abs(x), 1e-30), y), applied through apowmacro), and the output stage and the bloom downsample drop non-finite samples (a3dFinite). A negative base now yieldspow(abs(x), y)instead of NaN. Not yet confirmed on the reporting machine. - Fix: the game kit imported the deprecated
@aura3d/engine/contractssubpath, so every game using@aura3d/engine/gameprinted a deprecation warning that said removal in 4.1.0; it now imports from the@aura3d/engineroot.
Fix: directional (sun) shadows no longer darken far more than the caster's footprint. The forward path samples one cascade per render item, and a large receiver such as a ground plane has fragments whose light-space depth lies beyond that cascade's fitted depth range; the manual depth compare treated them as shadowed against the cleared depth of 1.0, so one small caster shadowed a whole half of the ground (about 80,000 of 230,400 pixels at fov 15; about 500 after). The receiver depth is now clamped to [0, 1] in every directional sampler (generated forward chunk, legacy core library, packagedpbr-direct, WebGPU forward), like a hardware compare sampler.tests/browser/shadow-footprint.spec.tsandexamples/shadow-lab/reproduce and guard it. Projects that switched sun shadows off because every receiver rendered fully shadowed (for example the Ninja Runner port) can switch them back on. - Fix:
instances.meshandbatched.meshnow honour the material's texture maps. The batched/instanced handler only built the scalar PBR material, sotexture,normalMap,emissiveMapand the other map slots (URLs, asset refs andtexture.*handles) were dropped and the surface rendered white; ordinary primitives got them from the post-mount textured upgrade, which batched nodes never joined. The handler now runs the same upgrade once per node and swaps in theTexturedPBRMaterialwhen it resolves (the scalar material until then); dispose releases it. Also: a multi-geometrybatched.meshdrew nothing unlessqualityRebuild.flagsenabledtiers.batching, because the packed multi-draw route's marker items need that submit path; without the flag it now uses per-geometry instancing (fallbackReason: "tiers-batching-off"). Atexture.datahandle used as a base-colour map must declarecolorSpace: "srgb"(binding validation rejects linear base-colour textures). - Fix:
createGamefrom@aura3d/engine/gamereturned the headless stub unless the caller passedqualityRebuild: { flags: ["game"] }. That entry point is itself the opt-in to the real game kit, so thegamelane now defaults on there;-game,none, the?a3d-qr=-gameURL andA3D_QR_GAME=0env still turn it off. The rootcreateGamein@aura3d/enginestays the headless stub and now logsCREATE_GAME_STUBonce, pointing to@aura3d/engine/game. The game kit imports from the@aura3d/engineroot instead of the deprecated@aura3d/engine/contractssubpath (removal is planned for 5.0.0). The kit was already published as the@aura3d/engine/gamesubpath of the root tarball (no new package); aracing-starterbuild against the published 4.0.8 tarball was used as the check. Docs:docs/api/game-kit.md. - Fix: an empty instanced node no longer blanks the canvas.
instances.<primitive>andinstances.modelwith zero transforms used to throw at build time, and a zero-length instance array threwINSTANCING_CONTRACTinsideForwardPass(before sorting), which stopped the frame. Zero instances is now valid: the compiler skips the node and reportsINSTANCES_EMPTY,batched.mesh/instances.meshwith no live members emit nothing, andForwardPassdrops zero-length instanced items. A geometrydrawRangeof count 0 (three'ssetDrawRange(0, 0)) draws nothing instead of throwing. - Fix: empty
points/linesno longer throw. Zero positions builds and compiles to nothing and reportsGEOMETRY_EMPTY; a one-point polyline is still an authoring error. - Perf: repeated
model()nodes of one GLB share decoded textures.createTypedGLBActornow takes a ref-countedGLTFSharedTextureCacheper (asset url/id/sha256, decode options) when none is passed, so N nodes decode and upload each glTF image once instead of N times (20 actors of a 3-image GLB: 60 decodes before, 3 after). Textures are freed when the last actor disposes;shareTextures: falseopts out andcloneSkinnedkeeps its own cache. Geometry buffers are still per node. - Fix: example and app entry modules survive production bundles with plain imports. The root
sideEffectsallowlist now covers./examples/**,./apps/**and./templates/**, so the eleven port wrappers dropped their__AURA_PORT_MODULES__workaround. - Fix: the cue bank's mute key did nothing unless an
onMuteChangecallback was passed.install()evaluatedoptions.onMuteChange?.(this.toggleMute()), which skips the toggle when the callback is absent. It now toggles first and then notifies.
Behaviour changes
- Directional shadows cover less ground. After the shadow depth fix scenes with a
shadow: truesun look different: the large dark areas on ground planes shrink to the casters' footprints. Check scenes that were tuned around the old darkening. createGamefrom@aura3d/engine/gamenow has the game lane on by default; the rootcreateGamestays a headless stub and logsCREATE_GAME_STUB. Opt out with-game,none,?a3d-qr=-gameorA3D_QR_GAME=0.- Fix:
handle.setUniformon a model node no longer throws. Without a custom material it is a once-per-uniformMODEL_SETUNIFORM_NOOPwarning; withmaterial.customit writes as before. - Fix:
scene({ children })no longer returns an empty scene.scene(init)acceptschildren,background,camera,timelineanddiagnostics; any other key or a non-object throwsinvalid-scene-options. - A multi-geometry
batched.meshwithoutqualityRebuild.flagsenablingtiers.batchingnow falls back to per-geometry instancing (fallbackReason: "tiers-batching-off") instead of drawing nothing. Base-colour maps on instanced nodes needcolorSpace: "srgb". material.customcolour output is linear light that the output stage encodes (and the default ACES operator tones down); use#include <colorspace_decode>withtoneMapping: "none"to get the authored colours. The default is unchanged.
CLI
- New: CLI asset tools (
docs/api/cli-asset-tools.md).aura3d assets optimize --file in.glb --out out.glb --role hero|vehicle|character|prop|background(role vertex and texture budgets, float32 geometry kept),aura3d assets merge-clips --base rig --out file.glb name=clip ...(merge Meshy animate clips into one rig by node name; a foreign skeleton is rejected),aura3d assets import-meshy <dir> --into <assets-dir> --prefix <game> --name <role>(isolated admission in a temporary project; it is the default inside the Aura3D monorepo, so sharedaura.assets.json,src/aura-assets.ts,public/aura-assets/*andaura-evidence/*are never written;--shared-manifestopts out), andaura3d meshy ledger status|reserve|settle|reconcile|list(per-game and global credit caps under a lock; a rate-limited or timed-out call staysunconfirmedand keeps counting instead of being recorded as a failure). Themeshy-cliandaura3d-assetsskills point to the new commands.
Deprecations
- The ten deprecated subpaths
@aura3d/engine/{contracts, media-node, apps, rendering, rendering/production-runtime, rendering/webgpu, production-runtime, advanced-runtime, assets/browser, workflows/production}and the deprecated root symbols inpackages/engine/src/public/index.tskeep working in 4.1.0. Their announced removal moves from 4.1.0 to 5.0.0: the warning text,removeIninaura.exports.jsonand the@deprecatednotes now say 5.0.0. Replacements are listed indocs/migration/4.0-to-4.1.md.
Tooling and examples (not published packages)
- Aura Slam 3D native port (
examples/aura-slam/, wrapperapps/showcase-aura-slam/, not routed): a typed, differential-tested simulation (physics, strokes, rules, AI, swipe, input), pose and rig, renderer, audio and UI shell. The live route is not swapped: Auto mode does not yet match the original's autoplay, the live-game replay gate and matched-tick render parity are not met, desktop draw calls (about 261) are above the 150 budget, and the tour, tournament and online screens are deferred. - Meshy art pass over the eleven Crayon ports (about 4.2k credits at last count by the helper ledger, which undercounts rate-limited calls) and the look-spec extractor under
tools/crayon-capture/. - Apex Overdrive (
showcase-roadster-rushand-v2) runs on a native Aura3D simulation; the 1.1 MB third-partyengine.jsbundle is gone. - The 15 index games dropped their blanket post, compiler and fog pins and share a black-frame self-heal; every port page supports
?diag=1. Strict live-port smoke specs, a hi-dpi spec and the port page generator were added. - Docs:
docs/whats-new-4.1.md,docs/migration/4.0-to-4.1.md,docs/workflows/game-conversion-playbook.md,docs/aura-slam/ARCHITECTURE.md, and the topic pages underdocs/api/.
Verified vs not verified
- Verified: unit and differential tests (see each kit's tests), typecheck, build, public-API contract, surface diff, export and import checks, and remote runs on software WebGL2 (SwiftShader) in headless and headed Chromium.
- Verified on real hardware for some ports only: an Apple M4 Max (ANGLE/Metal, WebGL2) was used by the owner for Drift & Deliver, Silt & Signal and Last Train via
?diag=1. The NaN fix for the black Drift scene and the new Silt rings are not yet confirmed there. - Not verified: any hardware-GPU run of the full port set (blocked on cloud GPU quota), real-headset WebXR, and performance on weak GPUs (several ports use 130 to 1,400 draw calls).
- The Crayon ports are not indistinguishable from the originals. Under the blind model-judge protocol (a model, not a human; seed 20261023) the judge still separated every port from its original (original identified in 35 of 48 trials for HEATSINK, the best result, 39 of 48 for Roadster Hours and 47 or 48 of 48 for most others; for Splat Rush the result reversed to 8 of 48 because the original's cards overflow at the test size); structural metrics improved but Phase B of
docs/workflows/threejs-parity-gaps.mdis unmet.