@aura3d/cli 5.0.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/data/aura.exports.json +20 -0
  2. package/data/root-export-dispositions.json +20 -0
  3. package/dist/commands/registry.d.ts +1 -0
  4. package/dist/commands/registry.d.ts.map +1 -1
  5. package/dist/commands/registry.js +1 -0
  6. package/dist/commands/registry.js.map +1 -1
  7. package/dist/commands/verify/index.d.ts +2 -0
  8. package/dist/commands/verify/index.d.ts.map +1 -0
  9. package/dist/commands/verify/index.js +11 -0
  10. package/dist/commands/verify/index.js.map +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/look-hdri-check.d.ts +7 -0
  15. package/dist/look-hdri-check.d.ts.map +1 -0
  16. package/dist/look-hdri-check.js +88 -0
  17. package/dist/look-hdri-check.js.map +1 -0
  18. package/dist/physics-backend-check.d.ts +8 -0
  19. package/dist/physics-backend-check.d.ts.map +1 -0
  20. package/dist/physics-backend-check.js +44 -0
  21. package/dist/physics-backend-check.js.map +1 -0
  22. package/dist/verify/cdp.d.ts +69 -0
  23. package/dist/verify/cdp.d.ts.map +1 -0
  24. package/dist/verify/cdp.js +223 -0
  25. package/dist/verify/cdp.js.map +1 -0
  26. package/dist/verify/codes.d.ts +17 -0
  27. package/dist/verify/codes.d.ts.map +1 -0
  28. package/dist/verify/codes.js +64 -0
  29. package/dist/verify/codes.js.map +1 -0
  30. package/dist/verify/command.d.ts +12 -0
  31. package/dist/verify/command.d.ts.map +1 -0
  32. package/dist/verify/command.js +101 -0
  33. package/dist/verify/command.js.map +1 -0
  34. package/dist/verify/frame.d.ts +46 -0
  35. package/dist/verify/frame.d.ts.map +1 -0
  36. package/dist/verify/frame.js +190 -0
  37. package/dist/verify/frame.js.map +1 -0
  38. package/dist/verify/index.d.ts +7 -0
  39. package/dist/verify/index.d.ts.map +1 -0
  40. package/dist/verify/index.js +7 -0
  41. package/dist/verify/index.js.map +1 -0
  42. package/dist/verify/lint.d.ts +43 -0
  43. package/dist/verify/lint.d.ts.map +1 -0
  44. package/dist/verify/lint.js +548 -0
  45. package/dist/verify/lint.js.map +1 -0
  46. package/dist/verify/run.d.ts +90 -0
  47. package/dist/verify/run.d.ts.map +1 -0
  48. package/dist/verify/run.js +317 -0
  49. package/dist/verify/run.js.map +1 -0
  50. package/package.json +6 -6
  51. package/skills/agent-docs/api-signatures.md +89 -23
  52. package/skills/agent-docs/recipes.md +94 -1
  53. package/skills/agent-docs/threejs-to-aura3d.md +7 -5
  54. package/skills/agent-files/AGENTS.md +1 -1
  55. package/skills/aura3d-materials-environments/SKILL.md +5 -5
  56. package/skills/aura3d-materials-environments/references/metal-glass.md +32 -0
  57. package/skills/aura3d-materials-environments/references/visual-helpers.md +20 -0
  58. package/skills/aura3d-performance/SKILL.md +5 -0
  59. package/skills/llms.txt +159 -30
  60. package/skills/manifest.json +4 -0
@@ -24,7 +24,7 @@ const app = createAuraApp("#app", { scene: scene({ background: "#101418" }) });
24
24
 
25
25
  | Call | Returns | Options |
26
26
  |---|---|---|
27
- | `createAuraApp(target: AuraAppTarget, options: AuraCreateAppOptions)` | `AuraApp` | options: frameMode?: "sync" \| "async" \| undefi…; scene: AuraSceneBuilder \| AuraSc…; worker?: { readonly createWorker: …; physics?: { readonly layers?: AuraC…; diagnostics?: boolean \| AuraDiagnostics…; renderer?: AuraCreateAppRendererOpti…; performanceQuality?: AuraPerformanceQuality \| …; quality?: AuraQualityMode \| undefin…; preserveDrawingBuffer?: boolean \| undefined; autoStart?: boolean \| undefined; resize?: boolean \| undefined; pixelRatio?: number \| { readonly max?:…; qualityRebuild?: { readonly flags?: import…; lightin… |
27
+ | `createAuraApp(target: AuraAppTarget, options: AuraCreateAppOptions)` | `AuraApp` | options: frameMode?: "sync" \| "async" \| undefi…; scene: AuraSceneBuilder \| AuraSc…; worker?: { readonly createWorker: …; physics?: { readonly layers?: AuraC…; diagnostics?: boolean \| AuraDiagnostics…; renderer?: AuraCreateAppRendererOpti…; performanceQuality?: AuraPerformanceQuality \| …; quality?: AuraQualityMode \| undefin…; preserveDrawingBuffer?: boolean \| undefined; autoStart?: boolean \| undefined; resize?: boolean \| undefined; routeState?: boolean \| undefined; pixelRatio?: number \| { readonly max?:…; qualityRebuild?: {… |
28
28
  | `app.onFrame(callback: AuraFrameCallback)` | `() => void` | |
29
29
  | `app.ready()` | `Promise<void>` | |
30
30
  | `app.nodes` | `AuraRuntimeNodeRegistry` | |
@@ -58,9 +58,27 @@ const stage = scene({ background: "#101418" }).add(primitives.box({ name: "crate
58
58
  | `scene().physics(next: AuraPhysicsWorldController)` | `AuraSceneBuilder` | |
59
59
  | `scene().diagnostics(enabled?: boolean)` | `AuraSceneBuilder` | |
60
60
 
61
+ ## `camera`
62
+
63
+ `camera.frame(subject | subjects | bounds, { fill, angle, azimuth, elevation, padding, fov, aspect })` returns an orbit camera spec placed so the subject fills `fill` (default 0.6) of the limiting frame axis; `camera.frameScene(scene)` frames a whole scene (ground planes ignored); `camera.framePose(...)` returns the pose for `app.camera.setPose` on resize; `camera.worldBounds(subject)` returns the `{ min, max }` box it frames. Solved for `aspect` (default 16/9).
64
+
65
+ ```ts
66
+ void camera.frame(primitives.box({ name: "subject" }), { fill: 0.6 });
67
+ ```
68
+
69
+ | Call | Returns | Options |
70
+ |---|---|---|
71
+ | `camera.perspective(options?: Omit<AuraCameraSpec, "mode">)` | `AuraCameraSpec` | options: position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; up?: AuraVec3 \| undefined; roll?: number \| undefined; offset?: AuraVec3 \| undefined; targetOffset?: AuraVec3 \| undefined; offsetMode?: "scene" \| "target-yaw" \| …; fov?: number \| undefined; focalLength?: number \| undefined; filmGauge?: number \| undefined; filmOffset?: number \| undefined; viewOffset?: { readonly fullWidth: num…; depth?: { readonly mode?: "standa…; near?: number \| undefined; far?: number \| undefined; layers?: number \| undefined; distance… |
72
+ | `camera.orbit(options?: Omit<AuraCameraSpec, "mode">)` | `AuraCameraSpec` | same as `camera.perspective` |
73
+ | `camera.frame(subject: AuraFrameSubject \| AuraFrameBounds \| readonly AuraFrameSubject[], options?: AuraCameraFrameOptions)` | `AuraCameraSpec` | options: fill?: number \| undefined; angle?: AuraFrameAngle \| undefined; azimuth?: number \| undefined; elevation?: number \| undefined; padding?: number \| undefined; fov?: number \| undefined; aspect?: number \| undefined; minDistance?: number \| undefined; exclude?: ((node: AuraSceneNode) =>… |
74
+ | `camera.frameScene(subject: AuraFrameSubject \| readonly AuraFrameSubject[], options?: AuraCameraFrameOptions)` | `AuraCameraSpec` | same as `camera.frame` |
75
+ | `camera.worldBounds(subject: AuraFrameSubject \| readonly AuraFrameSubject[], options?: AuraCameraFrameOptions)` | `AuraFrameBounds` | same as `camera.frame` |
76
+ | `camera.framePose(subject: AuraFrameSubject \| AuraFrameBounds \| readonly AuraFrameSubject[], options?: AuraCameraFrameOptions)` | `AuraFramePose` | same as `camera.frame` |
77
+ | `camera.frameAsset(asset: AuraAssetRef<"model">, options?: AuraCameraFrameAssetOptions)` | `AuraCameraSpec` | asset: type: "model"; format: string; formatMetadata?: FormatMetadata \| undefined; url: string; hash?: string \| undefined; sha256?: string \| undefined; integrity?: AssetIntegrity \| undefined; request?: AssetRequestOptions \| und…; bounds?: AuraVec3 \| undefined; sizeBytes?: number \| undefined; optional?: boolean \| undefined; metadata?: AuraAssetMetadata \| undef…; variants?: AuraAssetVariants \| undef…; requiredDecoders?: readonly AuraAssetRequire…; lods?: readonly AuraAssetLodLeve…; colliderUrl?: string \| undefined; budget?… |
78
+
61
79
  ## `looks`
62
80
 
63
- Genre look presets: environment, key light, fog and grade in one unit. Spread `looks.appOptions(id)` into `createAuraApp` options.
81
+ Genre look presets: environment, key light, fog and grade in one unit. Spread `looks.appOptions(id)` into `createAuraApp` options. HDRI-backed presets load `/hdri/<file>.hdr` from `public/hdri/`; a missing file degrades to a procedural probe, logs one `LOOK_HDRI_MISSING` console error and lists it in `app.diagnostics().warnings`. `looks.heroStage({ target, radius, background })` is studio IBL + key/fill/rim + a floor that fades into `background`; `looks.heroRig` is the lights alone; `looks.fogForExtent({ extent })` sizes fog by distance. A scene with NO look, light, environment or effect gets the hero stage automatically at mount (any authored look/light/environment/effect, or a scene kit, turns it off; `?a3d-cold-start-look=0` forces it off).
64
82
 
65
83
  ```ts
66
84
  void looks.preset("product-studio");
@@ -68,7 +86,11 @@ void looks.preset("product-studio");
68
86
 
69
87
  | Call | Returns | Options |
70
88
  |---|---|---|
71
- | `looks.preset(id: AuraLookId, overrides: AuraLookOverrides \| undefined)` (+1 overloads) | `AuraNodeBuilder<AuraGroupNode>` | overrides: sun?: { readonly azimuthDeg?: n…; exposureEv?: number \| undefined; fogDensityScale?: number \| undefined; accent?: string \| undefined; background?: string \| undefined; hdri?: AuraHdriId \| AuraAssetRef…; environmentIntensity?: number \| undefined; environmentRotationDeg?: number \| undefined; key?: { readonly color?: AuraCo…; rim?: false \| { readonly color?…; fog?: { readonly color?: AuraCo…; grade?: { readonly contrast?: num…; post?: AuraPostPresetId \| { read… |
89
+ | `looks.heroRig(options?: AuraHeroRigOptions)` | `AuraNodeBuilder<AuraGroupNode>` | options: target?: AuraVec3 \| undefined; radius?: number \| undefined; intensity?: number \| undefined; keyColor?: string \| undefined; fillColor?: string \| undefined; rimColor?: string \| undefined; shadow?: boolean \| undefined; name?: string \| undefined |
90
+ | `looks.heroStage(options?: AuraHeroStageOptions)` | `AuraNodeBuilder<AuraGroupNode>` | options: environment?: "none" \| "studio" \| "neut…; environmentIntensity?: number \| undefined; background?: string \| undefined; floor?: false \| { readonly color?…; target?: AuraVec3 \| undefined; radius?: number \| undefined; intensity?: number \| undefined; keyColor?: string \| undefined; fillColor?: string \| undefined; rimColor?: string \| undefined; shadow?: boolean \| undefined; name?: string \| undefined |
91
+ | `looks.fogForExtent(options: AuraFogForExtentOptions)` | `AuraNodeBuilder<AuraEffectNode>` | options: extent: number; color?: string \| undefined; haze?: number \| undefined; mode?: "exp" \| "exp2" \| undefined; name?: string \| undefined |
92
+ | `looks.fogDensityForExtent(extent: number, haze?: number, mode?: "exp" \| "exp2")` | `number` | |
93
+ | `looks.preset(id: AuraLookId, overrides: AuraLookOverrides \| undefined)` (+1 overloads) | `AuraNodeBuilder<AuraGroupNode>` | overrides: sun?: { readonly azimuthDeg?: n…; exposureEv?: number \| undefined; fogDensityScale?: number \| undefined; accent?: string \| undefined; background?: string \| undefined; hdri?: AuraAssetRef<"texture"> \|…; environmentIntensity?: number \| undefined; environmentRotationDeg?: number \| undefined; key?: { readonly color?: AuraCo…; rim?: false \| { readonly color?…; fog?: { readonly color?: AuraCo…; grade?: { readonly contrast?: num…; post?: AuraPostPresetId \| { read… |
72
94
  | `looks.nodes(id: AuraLookId, overrides: AuraLookOverrides \| undefined)` | `readonly AuraNodeBuilder<AuraSceneNode>[]` | same as `looks.preset` |
73
95
  | `looks.appOptions(id: AuraLookId, options: AuraLookBuildOptions \| undefined)` | `Pick<AuraCreateAppOptions, "pixelRatio" \| "renderer">` | options: expansion?: "auto" \| "v0" \| "v1" \| un…; flags?: QrFlags \| undefined |
74
96
  | `looks.list()` | `readonly AuraLookId[]` | |
@@ -77,7 +99,7 @@ void looks.preset("product-studio");
77
99
 
78
100
  ## `material`
79
101
 
80
- Colours are CSS strings. Metals need an environment.
102
+ Colours are CSS strings (`#rgb`, `#rrggbbaa`, `rgb()`, `hsl()`, `oklch()`, names); an invalid value throws `COLOR_INVALID` naming the option. Metals need an environment.
81
103
 
82
104
  ```ts
83
105
  void material.pbr({ color: "#c8ccd4", metallic: 0.9, roughness: 0.38 });
@@ -85,18 +107,21 @@ void material.pbr({ color: "#c8ccd4", metallic: 0.9, roughness: 0.38 });
85
107
 
86
108
  | Call | Returns | Options |
87
109
  |---|---|---|
88
- | `material.custom(spec: AuraCustomMaterialSpec)` | `AuraMaterialSpec` | spec: glsl: { readonly vertex?: strin…; wgsl?: { readonly vertex?: strin…; uniforms?: Readonly<Record<string, A…; uniformAliases?: Readonly<Record<string, s…; defines?: Readonly<Record<string, s…; transparent?: boolean \| undefined; depthWrite?: boolean \| undefined; depthTest?: boolean \| undefined; side?: "front" \| "back" \| "doubl…; blending?: "custom" \| "normal" \| "ad…; fog?: boolean \| undefined; patch?: Readonly<Record<string, s…; receiveShadow?: boolean \| undefined; castShadow?: boolean \| undefined |
110
+ | `material.custom(spec: AuraCustomMaterialSpec)` | `AuraMaterialSpec` | spec: glsl: { readonly vertex?: strin…; wgsl?: { readonly vertex?: strin…; uniforms?: Readonly<Record<string, A…; uniformAliases?: Readonly<Record<string, s…; defines?: Readonly<Record<string, s…; transparent?: boolean \| undefined; depthWrite?: boolean \| undefined; depthTest?: boolean \| undefined; side?: "front" \| "back" \| "doubl…; blending?: "additive" \| "multiply" \|…; fog?: boolean \| undefined; patch?: Readonly<Record<string, s…; receiveShadow?: boolean \| undefined; castShadow?: boolean \| undefined |
89
111
  | `material.toon(options?: AuraToonMaterialOptions)` | `AuraMaterialSpec` | options: color?: string \| undefined; steps?: number \| undefined; ramp?: AuraAssetRef<"texture", s…; shadowColor?: string \| undefined; rim?: AuraMaterialRimSpec \| und… |
112
+ | `material.strata(options?: AuraStrataMaterialOptions)` | `AuraMaterialSpec` | options: bands?: number \| undefined; colors?: readonly string[] \| undef…; heightRange?: readonly [number, number]…; noise?: number \| undefined; noiseScale?: number \| undefined; seam?: number \| undefined; variation?: number \| undefined; slope?: AuraStrataSlopeOptions \| …; roughness?: number \| undefined; seed?: number \| undefined; side?: "front" \| "back" \| "doubl… |
113
+ | `material.caustics(options?: AuraCausticsMaterialOptions)` | `AuraMaterialSpec` | options: color?: string \| undefined; causticColor?: string \| undefined; strength?: number \| undefined; scale?: number \| undefined; speed?: number \| undefined; surfaceY?: number \| undefined; depthFade?: number \| undefined; roughness?: number \| undefined; side?: "front" \| "back" \| "doubl… |
114
+ | `material.lightBeam(options?: AuraLightBeamMaterialOptions)` | `AuraMaterialSpec` | options: color?: string \| undefined; intensity?: number \| undefined; length?: number \| undefined; falloff?: number \| undefined; edgeSoftness?: number \| undefined; noise?: number \| undefined; noiseScale?: number \| undefined; noiseSpeed?: number \| undefined |
90
115
  | `material.matcap(options: AuraMatcapMaterialOptions)` | `AuraMaterialSpec` | options: map: AuraAssetRef<"texture"> \|…; color?: string \| undefined; opacity?: number \| undefined; transparent?: boolean \| undefined; side?: "front" \| "back" \| "doubl… |
91
116
  | `material.depth(options?: AuraDepthPreviewMaterialOptions)` | `AuraMaterialSpec` | options: near?: number \| undefined; far?: number \| undefined; side?: "front" \| "back" \| "doubl… |
92
117
  | `material.normal(options?: AuraNormalMaterialOptions)` | `AuraMaterialSpec` | options: side?: "front" \| "back" \| "doubl… |
93
118
  | `material.distance(options?: AuraDistanceMaterialOptions)` | `AuraMaterialSpec` | options: referencePosition?: AuraVec3 \| undefined; near?: number \| undefined; far?: number \| undefined; side?: "front" \| "back" \| "doubl… |
94
119
  | `material.shadow(options?: AuraShadowMaterialOptions)` | `AuraMaterialSpec` | options: color?: string \| undefined; opacity?: number \| undefined; side?: "front" \| "back" \| "doubl…; light?: "directional" \| "point" \|… |
95
120
  | `material.node(options: TslMaterialNodeOptions \| ShaderGraphMaterialOptions)` | `AuraMaterialSpec` | |
96
- | `material.pbr(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
121
+ | `material.pbr(rawOptions?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
97
122
  | `material.physical(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
98
123
  | `material.emissive(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
99
- | `material.metal(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
124
+ | `material.metal(rawOptions?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
100
125
  | `material.rubber(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
101
126
  | `material.glass(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
102
127
  | `material.clearcoat(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
@@ -109,6 +134,8 @@ void material.pbr({ color: "#c8ccd4", metallic: 0.9, roughness: 0.38 });
109
134
  | `material.brushedMetal(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
110
135
  | `material.frostedGlass(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
111
136
  | `material.clearGlass(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
137
+ | `material.studioMetal(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
138
+ | `material.studioGlass(options?: AuraMaterialSpec & { readonly tint?: AuraColor; })` | `AuraMaterialSpec` | |
112
139
  | `material.blackRubber(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
113
140
  | `material.matteClay(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
114
141
  | `material.ceramic(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
@@ -121,7 +148,7 @@ void material.pbr({ color: "#c8ccd4", metallic: 0.9, roughness: 0.38 });
121
148
  | `material.carPaint(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
122
149
  | `material.planet(preset?: AuraSolarPlanetMaterialPreset, options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
123
150
  | `material.atmosphere(options?: AuraMaterialSpec)` | `AuraMaterialSpec` | |
124
- | `material.proceduralTexture(texture: AuraProceduralTextureKind, options?: Partial<Omit<AuraProceduralTextureSpec, "texture" \| "kind">>)` | `AuraProceduralTextureSpec` | options: scale?: number \| undefined; strength?: number \| undefined; contrast?: number \| undefined; direction?: AuraVec3 \| undefined; colorA?: string \| undefined; colorB?: string \| undefined |
151
+ | `material.proceduralTexture(texture: AuraProceduralTextureKind, options?: Partial<Omit<AuraProceduralTextureSpec, "texture" \| "kind">>)` | `AuraProceduralTextureSpec` | options: strength?: number \| undefined; contrast?: number \| undefined; scale?: number \| undefined; direction?: AuraVec3 \| undefined; colorA?: string \| undefined; colorB?: string \| undefined |
125
152
  | `material.proceduralTextures` | `value` | |
126
153
  | `material.parameters(name: string, spec: AuraMaterialSpec)` | `AuraEditableMaterialParameters` | |
127
154
  | `material.fromParameters(parameters: AuraEditableMaterialParameters)` | `AuraMaterialSpec` | parameters: kind: "aura-material-parameters"; name: string; material: AuraMaterialSpec; roughness: number; metallic: number; metalness: number; transmission: number; clearcoat: number; clearcoatRoughness: number; thickness: number; ior: number; sheen: number; iridescence: number; anisotropy: number; envMapIntensity: number; emissiveIntensity: number |
@@ -143,11 +170,11 @@ void lights.directional({ position: [4, 6, 3], target: [0, 0, 0], intensity: 3,
143
170
  |---|---|---|
144
171
  | `lights.lensFlare(options?: AuraLensFlareOptions)` | `AuraNodeBuilder<AuraSceneNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; light?: string \| undefined; elements?: readonly AuraLensFlareEle…; occlusion?: AuraLensFlareOcclusion \| …; edgeFade?: number \| undefined; intensity?: number \| undefined; runtime?: AuraRuntimeNodeSpec \| und… |
145
172
  | `lights.ambient(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined |
146
- | `lights.hemisphere(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; skyColor?: string \| undefined; groundColor?: string \| undefined; intensity?: number \| undefined; position?: AuraVec3 \| undefined |
173
+ | `lights.hemisphere(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; skyColor?: string \| undefined; groundColor?: string \| undefined; intensity?: number \| undefined; position?: AuraVec3 \| undefined; color?: string \| undefined |
147
174
  | `lights.lightProbe(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; sh?: readonly number[] \| undef…; skyColor?: string \| undefined; groundColor?: string \| undefined; intensity?: number \| undefined; position?: AuraVec3 \| undefined |
148
- | `lights.directional(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; intensity?: number \| undefined; color?: string \| undefined; shadow?: boolean \| AuraDirectional… |
149
- | `lights.point(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; intensity?: number \| undefined; power?: number \| undefined; distance?: number \| undefined; decay?: number \| undefined; color?: string \| undefined; shadow?: boolean \| AuraLocalShadow…; photometricProfile?: AuraPhotometricProfile \| … |
150
- | `lights.spot(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; angle?: number \| undefined; penumbra?: number \| undefined; distance?: number \| undefined; decay?: number \| undefined; intensity?: number \| undefined; power?: number \| undefined; color?: string \| undefined; shadow?: boolean \| AuraLocalShadow…; photometricProfile?: AuraPhotometricProfile \| …; projectedImage?: AuraProjectedLightImage \|… |
175
+ | `lights.directional(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; intensity?: number \| undefined; color?: string \| undefined; shadow?: boolean \| AuraDirectional…; castShadow?: boolean \| undefined |
176
+ | `lights.point(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; intensity?: number \| undefined; power?: number \| undefined; distance?: number \| undefined; decay?: number \| undefined; color?: string \| undefined; shadow?: boolean \| AuraLocalShadow…; castShadow?: boolean \| undefined; photometricProfile?: AuraPhotometricProfile \| … |
177
+ | `lights.spot(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; angle?: number \| undefined; penumbra?: number \| undefined; distance?: number \| undefined; decay?: number \| undefined; intensity?: number \| undefined; power?: number \| undefined; color?: string \| undefined; shadow?: boolean \| AuraLocalShadow…; castShadow?: boolean \| undefined; photometricProfile?: AuraPhotometricProfile \| …; projectedImage?: AuraProjectedLightImage \|… |
151
178
  | `lights.studio(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: intensity?: number \| undefined |
152
179
  | `lights.rect(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; target?: AuraVec3 \| undefined; intensity?: number \| undefined; color?: string \| undefined; width?: number \| undefined; height?: number \| undefined; twoSided?: boolean \| undefined |
153
180
  | `lights.rectArea(options?: {…})` | `AuraNodeBuilder<AuraLightNode>` | same as `lights.rect` |
@@ -165,23 +192,23 @@ void environments.neutral({ intensity: 0.8, background: false });
165
192
 
166
193
  | Call | Returns | Options |
167
194
  |---|---|---|
168
- | `environments.outdoor(options?: {…})` | `WorldEnvBuilderOut` | options: texture?: AuraAssetRef<"texture", s…; name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined; reflectionTexture?: AuraAssetRef<"texture", s…; rotation?: number \| undefined; biome?: OutdoorBiome \| undefined |
169
- | `environments.room(options?: {…})` | `WorldEnvBuilderOut` | options: texture?: AuraAssetRef<"texture", s…; name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined; reflectionTexture?: AuraAssetRef<"texture", s…; rotation?: number \| undefined; colorTemperatureK?: number \| undefined |
170
- | `environments.space(options?: Partial<Omit<AuraEnvironmentNode, "environment" \| "kind">>)` | `WorldEnvBuilderOut` | options: texture?: AuraAssetRef<"texture", s…; name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined; reflectionTexture?: AuraAssetRef<"texture", s…; rotation?: number \| undefined |
195
+ | `environments.outdoor(options?: {…})` | `WorldEnvBuilderOut` | options: texture?: AuraAssetRef<"texture", s…; name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined; rotation?: number \| undefined; reflectionTexture?: AuraAssetRef<"texture", s…; biome?: OutdoorBiome \| undefined |
196
+ | `environments.room(options?: {…})` | `WorldEnvBuilderOut` | options: texture?: AuraAssetRef<"texture", s…; name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined; rotation?: number \| undefined; reflectionTexture?: AuraAssetRef<"texture", s…; colorTemperatureK?: number \| undefined |
197
+ | `environments.space(options?: Partial<Omit<AuraEnvironmentNode, "environment" \| "kind">>)` | `WorldEnvBuilderOut` | options: texture?: AuraAssetRef<"texture", s…; name?: string \| undefined; intensity?: number \| undefined; color?: string \| undefined; rotation?: number \| undefined; reflectionTexture?: AuraAssetRef<"texture", s… |
171
198
  | `environments.underwater(options?: Partial<Omit<AuraEnvironmentNode, "environment" \| "kind">>)` | `WorldEnvBuilderOut` | same as `environments.space` |
172
199
  | `environments.preset(name: AuraEnvironmentPresetName, options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | options: name?: string \| undefined; intensity?: number \| undefined; diffuseIntensity?: number \| undefined; specularIntensity?: number \| undefined; rotation?: number \| undefined; background?: boolean \| AuraEnvironment…; color?: string \| undefined |
173
200
  | `environments.neutral(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
174
201
  | `environments.none(options?: {…})` | `AuraNodeBuilder<AuraEnvironmentNode>` | options: name?: string \| undefined |
175
- | `environments.capture(options?: {…})` | `AuraNodeBuilder<AuraEnvironmentNode>` | options: name?: string \| undefined; intensity?: number \| undefined; diffuseIntensity?: number \| undefined; specularIntensity?: number \| undefined; rotation?: number \| undefined; background?: boolean \| AuraEnvironment…; color?: string \| undefined; position?: AuraVec3 \| undefined; resolution?: 64 \| 128 \| 256 \| undefined; update?: "once" \| "on-demand" \| { …; include?: "all" \| "sky-only" \| unde… |
202
+ | `environments.capture(options?: {…})` | `AuraNodeBuilder<AuraEnvironmentNode>` | options: name?: string \| undefined; intensity?: number \| undefined; diffuseIntensity?: number \| undefined; specularIntensity?: number \| undefined; rotation?: number \| undefined; background?: boolean \| AuraEnvironment…; color?: string \| undefined; position?: AuraVec3 \| undefined; resolution?: 64 \| 128 \| 256 \| undefined; update?: "on-demand" \| "once" \| { …; include?: "all" \| "sky-only" \| unde… |
176
203
  | `environments.hdri(options: {…})` | `AuraNodeBuilder<AuraEnvironmentNode>` | options: name?: string \| undefined; intensity?: number \| undefined; diffuseIntensity?: number \| undefined; specularIntensity?: number \| undefined; rotation?: number \| undefined; background?: boolean \| AuraEnvironment…; color?: string \| undefined; texture: AuraAssetRef<"texture">; reflectionTexture?: AuraAssetRef<"texture"> \|… |
177
- | `environments.studio(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
204
+ | `environments.studio(options?: AuraStudioEnvironmentOptions)` | `AuraNodeBuilder<AuraEnvironmentNode>` | options: preset?: "studio" \| "softbox" \| un…; name?: string \| undefined; intensity?: number \| undefined; diffuseIntensity?: number \| undefined; specularIntensity?: number \| undefined; rotation?: number \| undefined; background?: boolean \| AuraEnvironment…; color?: string \| undefined |
178
205
  | `environments.materialLab(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
179
206
  | `environments.productHero(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
180
207
  | `environments.nightCinematic(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
181
208
  | `environments.metalStudio(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
182
209
  | `environments.glassStudio(options?: AuraEnvironmentOptionsV2)` | `AuraNodeBuilder<AuraEnvironmentNode>` | same as `environments.preset` |
183
210
  | `environments.presets()` | `readonly AuraEnvironmentMapPreset[]` | |
184
- | `environments.forMaterial(materialClass: "metal" \| "glass" \| "studio" \| "product")` | `AuraNodeBuilder<AuraEnvironmentNode>` | |
211
+ | `environments.forMaterial(materialClass: "product" \| "studio" \| "metal" \| "glass")` | `AuraNodeBuilder<AuraEnvironmentNode>` | |
185
212
 
186
213
  ## `sky`
187
214
 
@@ -193,8 +220,8 @@ void sky.preetham({ sun: { elevationDeg: 35, azimuthDeg: 120 } });
193
220
 
194
221
  | Call | Returns | Options |
195
222
  |---|---|---|
196
- | `sky.preetham(options: AuraSkyPreethamOptions)` | `AuraNodeBuilder<AuraSceneNode>` | options: moon?: unknown; groundColor?: string \| undefined; sun: AuraSkySunSpec; turbidity?: number \| undefined; rayleigh?: number \| undefined; mieCoefficient?: number \| undefined; mieDirectionalG?: number \| undefined; exposure?: number \| undefined; stars?: AuraSkyStarsOptions \| und…; clouds?: AuraSkyCloudsOptions \| un…; name?: string \| undefined; captureEnvironment?: boolean \| undefined; affectsFog?: boolean \| undefined |
197
- | `sky.gradient(options: AuraSkyGradientOptions)` | `AuraNodeBuilder<AuraSceneNode>` | options: moon?: unknown; intensity?: number \| undefined; clouds?: AuraSkyCloudsOptions \| un…; sun?: AuraSkySunSpec \| undefined; zenith: string; horizon: string; ground?: string \| undefined; exponent?: number \| undefined; horizonGlow?: number \| undefined; bands?: readonly AuraSkyBand[] \| …; stars?: AuraSkyStarsOptions \| und…; name?: string \| undefined; captureEnvironment?: boolean \| undefined; affectsFog?: boolean \| undefined |
223
+ | `sky.preetham(options: AuraSkyPreethamOptions)` | `AuraNodeBuilder<AuraSceneNode>` | options: exposure?: number \| undefined; moon?: unknown; groundColor?: string \| undefined; sun: AuraSkySunSpec; turbidity?: number \| undefined; rayleigh?: number \| undefined; mieCoefficient?: number \| undefined; mieDirectionalG?: number \| undefined; stars?: AuraSkyStarsOptions \| und…; clouds?: AuraSkyCloudsOptions \| un…; name?: string \| undefined; captureEnvironment?: boolean \| undefined; affectsFog?: boolean \| undefined |
224
+ | `sky.gradient(options: AuraSkyGradientOptions)` | `AuraNodeBuilder<AuraSceneNode>` | options: intensity?: number \| undefined; moon?: unknown; clouds?: AuraSkyCloudsOptions \| un…; sun?: AuraSkySunSpec \| undefined; zenith: string; horizon: string; ground?: string \| undefined; exponent?: number \| undefined; horizonGlow?: number \| undefined; bands?: readonly AuraSkyBand[] \| …; stars?: AuraSkyStarsOptions \| und…; name?: string \| undefined; captureEnvironment?: boolean \| undefined; affectsFog?: boolean \| undefined |
198
225
  | `sky.hdri(options: {…})` | `AuraNodeBuilder<AuraSceneNode>` | options: texture: AuraAssetRef<"texture">; intensity?: number \| undefined; rotation?: number \| undefined; blurriness?: number \| undefined; name?: string \| undefined; captureEnvironment?: boolean \| undefined; affectsFog?: boolean \| undefined |
199
226
  | `sky.dayNight(options?: {…})` | `{ readonly nodes: readonly AuraSceneNode[]; readonly background: stri…` | options: hour?: number \| undefined; seed?: number \| undefined; starCount?: number \| undefined; cloudCellCount?: number \| undefined; starLimit?: number \| undefined; cloudLimit?: number \| undefined; model?: "preetham" \| "gradient" \|… |
200
227
 
@@ -210,7 +237,7 @@ void primitives.cylinder({ name: "plinth", size: [2, 0.9, 2], material: material
210
237
  |---|---|---|
211
238
  | `primitives.box(options: AuraPrimitiveOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | options: name?: string \| undefined; material?: AuraMaterialSpec \| undefi…; size?: number \| AuraVec3 \| undef…; castShadow?: boolean \| undefined; receiveShadow?: boolean \| undefined; physics?: AuraNodePhysicsSpec \| und…; instances?: readonly AuraTransformSpe…; instanceColors?: readonly string[] \| undef…; geometry?: AuraCustomGeometrySpec \| …; text3D?: Omit<AuraText3DGeometry, …; lod?: AuraRootLodSpec \| undefin…; params?: AuraPrimitiveParams \| und…; position?: AuraVec3 \| undefined; pivot?: AuraVec3 \| undefined; rotation?: Aur… |
212
239
  | `primitives.sphere(options: AuraPrimitiveOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | same as `primitives.box` |
213
- | `primitives.plane(options: AuraPrimitiveOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | same as `primitives.box` |
240
+ | `primitives.plane(options: AuraPlaneOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | options: position?: AuraVec3 \| undefined; layers?: number \| undefined; physics?: AuraNodePhysicsSpec \| und…; name?: string \| undefined; pivot?: AuraVec3 \| undefined; rotation?: AuraVec3 \| undefined; scale?: number \| AuraVec3 \| undef…; lookAt?: AuraVec3 \| undefined; rotationOrder?: AuraEulerOrder \| undefined; quaternion?: AuraQuat \| undefined; renderOrder?: number \| undefined; frustumCulled?: boolean \| undefined; userData?: Record<string, unknown> \|…; castShadow?: boolean \| undefined; material?: AuraMaterialSpec \| undefi…; … |
214
241
  | `primitives.cylinder(options: AuraPrimitiveOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | same as `primitives.box` |
215
242
  | `primitives.capsule(options: AuraPrimitiveOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | same as `primitives.box` |
216
243
  | `primitives.torus(options: AuraPrimitiveOptions \| undefined)` | `AuraNodeBuilder<AuraPrimitiveNode>` | same as `primitives.box` |
@@ -235,6 +262,8 @@ void effects.bloom({ intensity: 0.7, threshold: 0.75, radius: 0.55 });
235
262
 
236
263
  | Call | Returns | Options |
237
264
  |---|---|---|
265
+ | `effects.godRays(options?: AuraGodRaysOptions)` | `AuraNodeBuilder<AuraEffectNode>` | options: name?: string \| undefined; source?: AuraVec3 \| "sun" \| AuraGo…; intensity?: number \| undefined; decay?: number \| undefined; samples?: 64 \| 32 \| 48 \| undefined; color?: string \| undefined; density?: number \| undefined; medium?: number \| undefined |
266
+ | `effects.volumetricBeam(options?: AuraVolumetricBeamOptions)` | `AuraNodeBuilder<AuraPrimitiveNode>` | options: name?: string \| undefined; position?: AuraVec3 \| undefined; direction?: AuraVec3 \| undefined; length?: number \| undefined; endRadius?: number \| undefined; color?: string \| undefined; intensity?: number \| undefined; falloff?: number \| undefined; edgeSoftness?: number \| undefined; noise?: number \| undefined; noiseScale?: number \| undefined; noiseSpeed?: number \| undefined; radialSegments?: number \| undefined |
238
267
  | `effects.contactShadows(options?: AuraContactShadowOptions)` | `AuraNodeBuilder<AuraEffectNode>` | options: length?: number \| undefined; thickness?: number \| undefined; steps?: 8 \| 12 \| 16 \| undefined; intensity?: number \| undefined; lights?: "sun" \| "shadowed" \| unde… |
239
268
  | `effects.toon(options?: AuraToonOptions)` | `AuraCustomPostPass` | options: name?: string \| undefined; bands?: number \| undefined; bandMix?: number \| undefined; outline?: number \| undefined; ink?: readonly [number, number,…; edgeStart?: number \| undefined; edgeEnd?: number \| undefined; size?: readonly [number, number]…; order?: AuraPostPassOrder \| undef…; inkSource?: AuraToonInkSource \| undef…; fadeDistance?: number \| undefined; inkColor?: string \| undefined |
240
269
  | `effects.pixelation(options?: AuraPixelationOptions)` | `AuraNodeBuilder<AuraEffectNode>` | options: size?: number \| undefined; name?: string \| undefined |
@@ -255,7 +284,7 @@ void effects.bloom({ intensity: 0.7, threshold: 0.75, radius: 0.55 });
255
284
  | `effects.vignette(options?: PostV3EffectOptions)` | `AuraNodeBuilder<AuraEffectNode>` | |
256
285
  | `effects.filmGrain(options?: PostV3EffectOptions)` | `AuraNodeBuilder<AuraEffectNode>` | |
257
286
  | `effects.chromaticAberration(options?: PostV3EffectOptions)` | `AuraNodeBuilder<AuraEffectNode>` | |
258
- | `effects.shaderPass(options: AuraShaderPassOptions)` | `AuraCustomPostPass` | options: name?: string \| undefined; glsl?: string \| undefined; wgsl?: string \| undefined; fragment?: { readonly glsl?: string;…; uniforms?: Readonly<Record<string, n…; inputs?: readonly ("normal" \| "col…; insertAt?: PostInsertAt \| undefined; order?: AuraPostPassOrder \| undef…; enabled?: boolean \| undefined |
287
+ | `effects.shaderPass(options: AuraShaderPassOptions)` | `AuraCustomPostPass` | options: name?: string \| undefined; glsl?: string \| undefined; wgsl?: string \| undefined; fragment?: { readonly glsl?: string;…; uniforms?: Readonly<Record<string, n…; inputs?: readonly ("depth" \| "colo…; insertAt?: PostInsertAt \| undefined; order?: AuraPostPassOrder \| undef…; enabled?: boolean \| undefined |
259
288
  | `effects.fog(options?: FogNodeOptions)` | `AuraNodeBuilder<AuraEffectNode>` | |
260
289
  | `effects.cinematicBloom(options?: Omit<AuraEffectNode, "kind" \| "effect">)` | `AuraNodeBuilder<AuraEffectNode>` | |
261
290
  | `effects.volumetricFog(options?: AuraVolumetricFogOptions)` | `AuraNodeBuilder<AuraEffectNode>` | |
@@ -305,6 +334,31 @@ void instances.cone({ name: "pines", transforms: [{ position: [0, 1, 0], scale:
305
334
  | `instances.custom(spec: AuraCustomGeometrySpec, options: Omit<AuraInstancedPrimitiveOptions, "geometry">)` | `AuraNodeBuilder<AuraPrimitiveNode>` | spec: kind: "aura-custom-geometry"; positions: AuraVec3Attribute; normals?: AuraVec3Attribute \| undef…; uvs?: AuraVec2Attribute \| undef…; uv1s?: AuraVec2Attribute \| undef…; colors?: readonly string[] \| reado…; indices: AuraIndexAttribute; attributes?: Readonly<Record<string, A…; bounds?: { readonly min: AuraRootV…; groups?: readonly AuraGeometryGrou…; drawRange?: AuraGeometryDrawRangeSpec…; morphTargets?: readonly AuraGeometryMorp…; topology?: "triangles" \| "lines" \| "…; dynamic?: boolean \| undefined |
306
335
  | `instances.model(asset: TAsset, options: {…})` (+1 overloads) | `AuraNodeBuilder<AuraGroupNode>` | options: name?: string \| undefined; material?: AuraMaterialSpec \| undefi…; castShadow?: boolean \| undefined; receiveShadow?: boolean \| undefined; visible?: boolean \| undefined; role?: AuraModelRole \| undefined; scaleMode?: AuraModelScaleMode \| unde…; targetHeight?: number \| undefined; targetMaxDimension?: number \| undefined; targetLength?: number \| undefined; physics?: AuraNodePhysicsSpec \| und…; hiddenNodeNames?: readonly string[] \| undef…; materialOverrides?: readonly AuraModelMateria…; variant?: string \| undefined; lod?… |
307
336
 
337
+ ## `points`
338
+
339
+ Static cloud. `Float32Array` positions + packed `Uint8Array`/`Uint32Array`/`Float32Array` colors stay typed (no per-point objects, no color parsing). Live clouds: `points.stream` + `pointStreamOf(handle)` (docs/agents/streaming-buffers.md).
340
+
341
+ ```ts
342
+ void points({ positions: new Float32Array([0, 0, 0, 1, 1, 1]), colors: new Uint8Array(8).fill(255), size: 2 });
343
+ ```
344
+
345
+ | Call | Returns | Options |
346
+ |---|---|---|
347
+ | `points(options: AuraPointsOptions)` | `AuraNodeBuilder<AuraPointsNode>` | options: points?: AuraLinePositionsInput \| …; positions?: AuraLinePositionsInput \| …; colors?: readonly string[] \| Float…; size?: number \| undefined; sizeAttenuation?: boolean \| undefined; map?: AuraMaterialTextureInput …; alphaTest?: number \| undefined; transparent?: boolean \| undefined; material?: AuraMaterialSpec \| undefi…; name?: string \| undefined; visible?: boolean \| undefined; runtime?: AuraRuntimeNodeSpec \| und…; position?: AuraVec3 \| undefined; rotation?: AuraVec3 \| undefined; scale?: number \| AuraVec3 \| undef… |
348
+ | `points.stream(spec: AuraPointStreamSpec)` | `AuraNodeBuilder<AuraPointsNode>` | spec: capacity: number; colors?: AuraPointStreamColorForma…; colorSpace?: "linear" \| "srgb" \| undef…; size?: number \| undefined; sizeAttenuation?: boolean \| undefined; map?: AuraMaterialTextureInput …; alphaTest?: number \| undefined; transparent?: boolean \| undefined; material?: AuraMaterialSpec \| undefi…; bounds?: { readonly min: AuraVec3;…; name?: string \| undefined; visible?: boolean \| undefined; runtime: AuraRuntimeNodeSpec; position?: AuraVec3 \| undefined; rotation?: AuraVec3 \| undefined; scale?: number \| AuraVec3 … |
349
+
350
+ ## `pointStreamOf`
351
+
352
+ Narrows `app.nodes.require(id)` of a `points.stream({ capacity, colors?, runtime: { id } })` node to `{ write, lease, setDrawCount, drawCount, capacity, stats }`; typed in-place updates, one dirty-range upload.
353
+
354
+ ```ts
355
+ void pointStreamOf;
356
+ ```
357
+
358
+ | Call | Returns | Options |
359
+ |---|---|---|
360
+ | `pointStreamOf(handle: AuraRuntimeNodeHandle \| undefined)` | `AuraPointStreamHandle` | |
361
+
308
362
  ## `geometry`
309
363
 
310
364
  `define` builds the spec; mount it with `geometry.custom(spec, { material })`.
@@ -351,7 +405,7 @@ void interactions.orbit({ target: "crate" });
351
405
 
352
406
  ## `physics`
353
407
 
354
- Rapier-backed worlds, bodies and colliders; see `docs/api/` physics pages before use.
408
+ Rapier-backed worlds, bodies and colliders; see `docs/api/` physics pages before use. Needs `npm install @dimforge/rapier3d-compat@0.20.0` (optional peer; once installed, every app that imports the engine loads its 1.08 MB gzip chunk at startup unless it is built with `aura3d()` from `@aura3d/assets/vite`, which leaves Rapier out of apps that never mention physics, or `aura3d({ physics: "lazy" })`, which downloads it on first use (then `await app.physics.ready?.()` before imperative calls); see `docs/concepts/physics.md`): without it you get the typed `PHYSICS_BACKEND_MISSING` error (`error.code`, `error.installCommand`) in `diagnostics().errors`, on the canvas, from `app.physics`, from `preloadPhysics()`, and from `aura3d doctor`. Working example: recipe 16 (`recipes-src/physics-basics`).
355
409
 
356
410
  ```ts
357
411
  void physics.box;
@@ -397,6 +451,18 @@ void AnimationController;
397
451
  | `AnimationController#listClips()` | `readonly AuraRegisteredAnimationClip<TClipId, TEvent>[]` | |
398
452
  | `AnimationController#dispose()` | `void` | |
399
453
 
454
+ ## `createSpatialPicker`
455
+
456
+ CPU picking for `points` (nearest to the ray within a pixel or world tolerance) and instanced nodes (per-instance box or sphere). `picker.pick({ camera, ndc }, { viewport, pixelRadius })`; `picker.invalidate(name)` after writing a position or matrix buffer in place. `bindSpatialPicking` adds hover and click.
457
+
458
+ ```ts
459
+ const picker = createSpatialPicker({ nodes: [] });
460
+ ```
461
+
462
+ | Call | Returns | Options |
463
+ |---|---|---|
464
+ | `createSpatialPicker(sourcesOf: SpatialPickerSources \| (() => SpatialPickerSources))` | `SpatialPicker` | |
465
+
400
466
  ## `createFixedStepLoop`
401
467
 
402
468
  Deterministic simulation loop for games.
@@ -1,12 +1,13 @@
1
1
  # Recipes: copy a working scene, then adapt it
2
2
 
3
- Coming from three.js? Read [`threejs-to-aura3d.md`](threejs-to-aura3d.md) first; signatures are in [`api-signatures.md`](api-signatures.md). Recipes 8 to 15 build complete scenes with no admitted models. Start here when the task looks like one of these. Each recipe is a complete `src/main.ts` body that compiled against the engine types (`tsc`, strict) and uses only non-deprecated root exports. Copy it, change names and numbers, run `npm run build`. Do not read `node_modules/@aura3d/engine/dist` to discover the API first: that is the slowest path (in the cold-start trial, 8 of 15 runs spent all 40 turns reading `.d.ts` and `dist` files without writing a scene, while the game-loop runs, which had a complete sample to copy, finished in 11 to 17 turns).
3
+ Coming from three.js? Read [`threejs-to-aura3d.md`](threejs-to-aura3d.md) first; signatures are in [`api-signatures.md`](api-signatures.md). Recipes 8 to 15 build complete scenes with no admitted models; recipe 16 covers physics. Start here when the task looks like one of these. Each recipe is a complete `src/main.ts` body that compiled against the engine types (`tsc`, strict) and uses only non-deprecated root exports. Copy it, change names and numbers, run `npm run build`. Do not read `node_modules/@aura3d/engine/dist` to discover the API first: that is the slowest path (in the cold-start trial, 8 of 15 runs spent all 40 turns reading `.d.ts` and `dist` files without writing a scene, while the game-loop runs, which had a complete sample to copy, finished in 11 to 17 turns).
4
4
 
5
5
  Page needs a `<div id="app">` (or a `<canvas id="app">`); `createAuraApp("#app", { scene })` mounts into it. Background, camera, lights and fog are ordinary nodes or scene options. Animate inside `app.onFrame((frame) => ...)` using `frame.dt`. Await `app.ready()` when you need the first frame. With a HUD or other elements beside the canvas, give the canvas a wrapper of definite size (`<div id="stage" style="width:640px;height:360px"><canvas id="app"></canvas></div>`), or pass `resize: false` and a `width`/`height` on the canvas; the app sizes itself to its parent, and an auto-height parent that also holds a HUD can make a canvas grow until it draws nothing on 4.1.3 and earlier (see troubleshooting, black frame checklist).
6
6
 
7
7
  Rules that every recipe follows:
8
8
 
9
9
  - Orbit control: `interactions.orbit({ target: "<node name>" })`. It takes a node name, nothing else.
10
+ - Frame the subject with `camera.frame(subject, { fill: 0.6 })` (a node or array of nodes, not a hand-placed `camera.perspective`; pass `aspect` if the canvas is not 16:9).
10
11
  - Metals need something to reflect: add `environments.neutral(...)` (or a look preset) or a metallic material renders dark.
11
12
  - Instancing: `instances.cone/cylinder/sphere/box({ transforms, colors })` draws one draw call per shape; shapes are unit-sized, so `scale` sets the size.
12
13
  - A deformed surface needs vertices: build a grid with `geometry.define` and mount it with `geometry.custom(...)`.
@@ -719,6 +720,98 @@ const app = createAuraApp("#app", { scene: stage });
719
720
  await app.ready();
720
721
  ```
721
722
 
723
+ ## 16. Physics: bodies, a stacked wall, a swinging rope, collision events, fixed step
724
+
725
+ - Goal: `.physics({...})` bodies on nodes (static ground, a 5 x 4 crate wall), a ball on a `ball-socket` rope from a gantry pivot that swings into the wall, `app.physics.onCollision`, and a fixed 1/60 step so the result depends on the step count only. `?steps=140` freezes the simulation after 140 steps for captures.
726
+ - Install first: `npm install @dimforge/rapier3d-compat@0.20.0`. Rapier is an optional peer, so it is only in your build if you install it; once installed, every app that imports `@aura3d/engine` downloads and initialises it at startup, even one that never calls physics (a 2.85 MB chunk, 1.08 MB gzip, measured; the `aura3d-no-tla` condition does not avoid it). Build with `aura3d()` from `@aura3d/assets/vite` and Vite leaves Rapier out of any app whose code never mentions physics; `aura3d({ physics: "lazy" })` keeps it but downloads it on first use, in which case add `await app.physics.ready?.()` before the imperative `app.physics.createBody` / `createJoint` calls below (`docs/concepts/physics.md`). Without it the app does not crash silently: `diagnostics().errors` and the canvas show `PHYSICS_BACKEND_MISSING` with this install command, `app.physics` throws the same typed error, and `aura3d doctor` flags the project.
727
+ - Final frame: the ball has hit the wall at the base of a dark gantry and the crates are tumbling into a heap on green ground.
728
+ - Replaces in three.js: a Rapier/cannon setup plus a manual body-to-mesh sync loop (here `physics: { bindNodes: true }` plus `.runtime({ id })` on each node does the sync).
729
+ - Values: gravity `[0, -9.81, 0]`, step 1/60 with at most 5 catch-up steps, ball mass 6 radius 0.55, crates mass 1, friction 0.7.
730
+ - Capture: `recipes-src/physics-basics/frame.png`; source: `recipes-src/physics-basics/main.ts`.
731
+
732
+ ```ts
733
+ import { camera, createAuraApp, lights, material, primitives, scene, type AuraNodeBuilder, type AuraPrimitiveNode } from "@aura3d/engine";
734
+
735
+ // Rigid bodies on scene nodes: a static ground, a stacked wall of crates, a ball on a ball-socket rope that swings
736
+ // into the wall (the wrecking ball), collision events, and a deterministic fixed step. Needs the Rapier backend
737
+ // (`npm install @dimforge/rapier3d-compat@0.20.0`); without it the app reports PHYSICS_BACKEND_MISSING in
738
+ // diagnostics().errors and on the canvas. ?steps=N freezes the simulation after N fixed steps for a capture.
739
+ const STEP = 1 / 60; // fixed step: the result depends on the step count, never on the display rate
740
+ const MAX_CATCH_UP = 5;
741
+ const freezeAt = new URLSearchParams(location.search).get("steps");
742
+
743
+ const CRATE = 0.5;
744
+ const COLORS = ["#c98b4b", "#b8763a", "#d9a066", "#a86a32"];
745
+ const crates: AuraNodeBuilder<AuraPrimitiveNode>[] = [];
746
+ for (let row = 0; row < 5; row += 1) {
747
+ for (let col = 0; col < 4; col += 1) {
748
+ crates.push(
749
+ primitives.box({ name: `crate-${row}-${col}`, size: CRATE, material: material.pbr({ color: COLORS[(row + col) % COLORS.length]!, roughness: 0.8 }) })
750
+ .position(0.2 + (row % 2) * 0.02, CRATE / 2 + row * CRATE, (col - 1.5) * CRATE)
751
+ .runtime({ id: `crate-${row}-${col}` })
752
+ .physics({ type: "dynamic", shape: "box", halfExtents: [CRATE / 2, CRATE / 2, CRATE / 2], mass: 1, friction: 0.7, restitution: 0.05 })
753
+ );
754
+ }
755
+ }
756
+
757
+ const PIVOT: [number, number, number] = [-1.2, 6.2, 0]; // rope anchor; the ball starts 5 m away on the arc and falls into the wall
758
+ const BALL_START: [number, number, number] = [-4.2, 2.2, 0];
759
+
760
+ const app = createAuraApp("#app", {
761
+ physics: { gravity: [0, -9.81, 0], bindNodes: true, seed: 7 },
762
+ scene: scene({ background: "#8fb4d8" })
763
+ .add(lights.hemisphere({ skyColor: "#cfe2f6", groundColor: "#4a5a3a", intensity: 1.2 }))
764
+ .add(lights.directional({ name: "sun", position: [6, 9, 5], target: [0, 0.5, 0], intensity: 4, color: "#fff0d8", shadow: true }))
765
+ .add(
766
+ primitives.box({ name: "ground", size: [30, 0.5, 14], material: material.pbr({ color: "#6f8a50", roughness: 1 }) })
767
+ .position(0, -0.25, 0)
768
+ .runtime({ id: "ground" })
769
+ .physics({ type: "static", shape: "box", halfExtents: [15, 0.25, 7], friction: 0.9 })
770
+ )
771
+ .add(primitives.box({ name: "gantry", size: [8, 0.25, 0.4], material: material.pbr({ color: "#394457", metallic: 0.6, roughness: 0.4 }) }).position(PIVOT[0], PIVOT[1] + 0.125, 0))
772
+ .add(primitives.box({ name: "post-left", size: [0.3, PIVOT[1], 0.4], material: material.pbr({ color: "#394457", metallic: 0.6, roughness: 0.4 }) }).position(PIVOT[0] - 3.85, PIVOT[1] / 2, 0))
773
+ .add(primitives.box({ name: "post-right", size: [0.3, PIVOT[1], 0.4], material: material.pbr({ color: "#394457", metallic: 0.6, roughness: 0.4 }) }).position(PIVOT[0] + 3.85, PIVOT[1] / 2, 0))
774
+ .addMany(crates)
775
+ .add(
776
+ primitives.sphere({ name: "ball", size: 1.1, material: material.pbr({ color: "#d94b3d", metallic: 0.4, roughness: 0.35 }) })
777
+ .position(...BALL_START)
778
+ .runtime({ id: "ball" })
779
+ .physics({ type: "dynamic", shape: "sphere", radius: 0.55, mass: 6, restitution: 0.1, friction: 0.5 })
780
+ )
781
+ .camera(camera.perspective({ position: [4, 3.6, 13], target: [-0.8, 2.7, 0], fov: 46, near: 0.1, far: 200 }))
782
+ });
783
+
784
+ // The anchor is a body-less pivot: a static body with no node, and the rope is a ball-socket joint through the
785
+ // world-space anchor point above the ball (anchors are world space, bodies keep their local frames).
786
+ app.physics.createBody({ name: "pivot", type: "static", shape: "sphere", radius: 0.05, position: PIVOT, sensor: true });
787
+ app.physics.createJoint({ kind: "ball-socket", bodyA: "pivot", bodyB: "ball", anchor: PIVOT });
788
+
789
+ // Collision events: count solid hits and remember the hardest one. The page object is what the render gate reads.
790
+ const stats = { steps: 0, solidHits: 0, hardestHit: 0, firstCrateHitStep: -1 };
791
+ app.physics.onCollision((event) => {
792
+ if (event.phase !== "begin" || event.sensor) return;
793
+ stats.solidHits += 1;
794
+ stats.hardestHit = Math.max(stats.hardestHit, event.relativeSpeed);
795
+ const names = [event.nodeA, event.nodeB];
796
+ if (stats.firstCrateHitStep < 0 && names.includes("ball") && names.some((name) => name?.startsWith("crate-"))) stats.firstCrateHitStep = stats.steps;
797
+ });
798
+
799
+ const frozen = freezeAt === null ? undefined : Math.max(0, Math.floor(Number(freezeAt)));
800
+ let accumulator = 0;
801
+ const stepOnce = (): void => { app.physics.step(STEP); stats.steps += 1; };
802
+ if (frozen !== undefined) for (let i = 0; i < frozen; i += 1) stepOnce();
803
+ app.onFrame((frame) => {
804
+ if (frozen !== undefined) return;
805
+ accumulator = Math.min(accumulator + frame.dt, STEP * MAX_CATCH_UP);
806
+ while (accumulator >= STEP) { stepOnce(); accumulator -= STEP; }
807
+ });
808
+
809
+ const positions = (): Record<string, readonly number[]> =>
810
+ Object.fromEntries(["ball", "crate-0-0", "crate-4-2"].map((name) => [name, app.physics.bodies.require(name).position().map((value) => Math.round(value * 1e4) / 1e4)]));
811
+ (window as unknown as { __PHYSICS_RECIPE__: unknown }).__PHYSICS_RECIPE__ = { stats, positions, diagnostics: () => app.diagnostics().errors };
812
+ await app.ready();
813
+ ```
814
+
722
815
  ## Type traps (strict `tsc` errors agents hit on a first draft)
723
816
 
724
817
  | Error | Cause | Fix |
@@ -5,13 +5,13 @@ Read this first. Aura3D is a standalone engine with its own renderer: you descri
5
5
  | If you would write (three.js) | Write (Aura3D) | Difference that bites |
6
6
  | --- | --- | --- |
7
7
  | `Scene` + `PerspectiveCamera` + `WebGLRenderer` + `setAnimationLoop` | `scene({ background }).camera(camera.perspective({ position, target, fov }))` then `createAuraApp("#app", { scene })`; per frame `app.onFrame((f) => ...)` with `f.dt`; `await app.ready()` | No renderer, resize or loop code. Mount target is a `<canvas>` or a sized wrapper. |
8
- | `new Mesh(geometry, material)` | `primitives.box({ name, size: [w, h, d], material: material.pbr({ color }) })` (also `sphere`, `plane`, `cylinder`, `capsule`, `cone`, `torus`, `torusKnot`, `lathe`, `extrude`, `tube`) | Primitives are unit shapes scaled by `size`. `sphere` size is the **diameter**; `plane` is `[w, 1, d]` and lies in XZ. Unknown option keys are reported, not ignored. |
9
- | `mesh.position.set(...)`, `rotation`, `scale` | Build time: `.position(x, y, z).rotate(x, y, z).scale(2)`. Runtime: `.runtime({ id, mutable: true })`, then `app.nodes.require(id).setPosition(x, y, z)`, `.setRotation`, `.setScale` | A node you move later needs `runtime({ id, mutable: true })`. Handles have `set*`, not `.position.x`. Vectors are tuples, not `Vector3`. |
8
+ | `new Mesh(geometry, material)` | `primitives.box({ name, size: [w, h, d], material: material.pbr({ color }) })` (also `sphere`, `plane`, `cylinder`, `capsule`, `cone`, `torus`, `torusKnot`, `lathe`, `extrude`, `tube`) | Primitives are unit shapes scaled by `size`. `sphere` size is the **diameter**; `plane` is `[w, 1, d]` and lies in XZ (`size: [w, d]` is accepted and means the same). Unknown option keys are reported, not ignored. |
9
+ | `mesh.position.set(...)`, `rotation`, `scale` | Build time: `.position(x, y, z).rotate(x, y, z).scale(2)`. Runtime: `.runtime({ id, mutable: true })`, then `app.nodes.require(id).setPosition(x, y, z)`, `.setRotation`, `.setScale` | A node you move later needs `runtime({ id, mutable: true })`. Handles have `set*`, not `.position.x`. Vectors are tuples, not `Vector3`. Builder `.rotation(x, y, z)` is accepted as `.rotate(x, y, z)`. |
10
10
  | `Group` / `Object3D` hierarchy, bones as groups | `group("arm", [children], { position }).runtime({ id, mutable: true })` | Children use local transforms; a group's origin is its pivot. |
11
11
  | `BufferGeometry` + `setAttribute` + `setIndex` | `geometry.custom(geometry.define({ positions, normals, uvs, colors, indices, bounds }), { name, material })` | Typed arrays in; `bounds` required; `colors` are per-vertex RGBA. `geometry.dynamic` for geometry you rewrite. |
12
- | `MeshStandardMaterial` | `material.pbr({ color, roughness, metallic, emissive, opacity })` | Also `physical` (clearcoat), `emissive`, `toon`, `matcap`. Colour is a CSS string. |
12
+ | `MeshStandardMaterial` | `material.pbr({ color, roughness, metallic, emissive, opacity })` | Also `physical` (clearcoat), `emissive`, `toon`, `matcap`. Colour is a CSS string. `side: "double"` means `doubleSided: true`; `transparent`, `wireframe`, `flatShading` have no equivalent and are reported (see [`guessable-api.md`](guessable-api.md)). |
13
13
  | `ShaderMaterial` | `material.custom({ glsl: { vertex, fragment }, uniforms: { t: { type: "f32", value: 0 } } })`; `handle.setUniform("t", v)` | GLSL 3.00 (`in`/`out`, write `outColor`). Prelude names: `position`, `normal`, `uv`, `modelViewProjectionMatrix`, `modelMatrix`, `cameraPosition`, `uTime` (wall-clock seconds; advances only while the app redraws every frame: give the node `.runtime({ id, mutable: true })` or register `app.onFrame(...)`, else a static scene draws once and `uTime` looks frozen). Node/TSL: `material.node`. |
14
- | `DirectionalLight` + `castShadow` | `lights.directional({ position, target, intensity, shadow: true })` | `shadow: true` on the light; meshes take `castShadow` / `receiveShadow`. Also `point`, `spot`, `hemisphere`, `ambient`, `rect`. |
14
+ | `DirectionalLight` + `castShadow` | `lights.directional({ position, target, intensity, shadow: true })` | `shadow: true` on the light (`castShadow: true` is accepted as the same); meshes take `castShadow` / `receiveShadow`. Also `point`, `spot`, `hemisphere`, `ambient`, `rect`. |
15
15
  | `scene.environment = pmrem` | `environments.neutral({ intensity, background: false })` (also `studio`, `outdoor`, `hdri`, `preset`) | Metals render dark without an environment. `looks.preset(id)` bundles environment, key light, fog and grade. |
16
16
  | `scene.background = sky shader`, `Sky` | `sky.preetham({ sun: { elevationDeg, azimuthDeg } })`, `sky.gradient`, `sky.dayNight` | Move the sun live with `app.atmosphere.setSky({ sun })`. |
17
17
  | `scene.fog = new FogExp2` | `effects.fog({ color, density })` | A scene node, not a property. |
@@ -25,6 +25,8 @@ Read this first. Aura3D is a standalone engine with its own renderer: you descri
25
25
  | `geometry.dispose()`, `renderer.dispose()` | `app.dispose()` | One call disposes everything the app made. |
26
26
  | `Vector3`, `Clock` | `[x, y, z]` tuples at option sites; `Clock` from `@aura3d/engine`; `Vector3` from `@aura3d/math/three` | Mutable three.js-shaped math lives only in `@aura3d/math/three`. |
27
27
 
28
- Not here: `GLTFLoader` (use a typed asset, `model(assets.x)`; see [`asset-workflow.md`](asset-workflow.md)), `renderer.toneMapping` (use the look preset's `output`), custom render targets and `EffectComposer` chains (no equivalent; check `docs/migration/from-threejs.md`).
28
+ Guessed names: every three.js spelling that models actually tried, with the decision for each (alias, reported with a suggestion, or documented), is in [`guessable-api.md`](guessable-api.md). Unknown builder options are reported once with a "did you mean" suggestion (`OPTION_IGNORED`; strict mode throws).
29
+
30
+ Not here: `GLTFLoader` (use a typed asset, `model(assets.x)`; see [`asset-workflow.md`](asset-workflow.md)), `renderer.toneMapping` (use the look preset's `output`, or `output: { toneMapping: "aces" }`; `"aces-filmic"` is not a name), custom render targets and `EffectComposer` chains (no equivalent; check `docs/migration/from-threejs.md`).
29
31
 
30
32
  Porting an existing file: `npx @aura3d/cli@latest migrate three "src/**/*.ts"` lists what maps, what is approximate and what has no equivalent; it is a worklist, not a drop-in conversion. Full mapping with verdicts: [`docs/migration/from-threejs.md`](../migration/from-threejs.md).
@@ -1,3 +1,3 @@
1
1
  # Aura3D agent instructions
2
2
 
3
- Read ./llms.txt first. Read the `aura3d-art-direction` skill. Every scene gets a look. Run the look-dev loop (`aura3d look capture` → judge → iterate) before calling a scene done.
3
+ Read ./llms.txt first: its first screen holds typechecked, GPU-rendered patterns (scene skeleton, instancing, custom shader, procedural geometry, rigged models, game loop, physics, particles) to copy and adapt; do not open `node_modules` or `dist` for APIs. Read the `aura3d-art-direction` skill. Every scene gets a look. Run the look-dev loop (`aura3d look capture` → judge → iterate) before calling a scene done.
@@ -53,16 +53,16 @@ Step 1, presets (always try first):
53
53
  `environments.productHero`, `environments.nightCinematic`,
54
54
  `environments.metalStudio`, `environments.glassStudio`, or
55
55
  `environments.forMaterial("metal" | "glass" | "product" | "studio")`.
56
+ Metal and glass that must read: [metal-glass](references/metal-glass.md) (`material.studioMetal`, `material.studioGlass`, softbox).
56
57
  3. Outdoor: `sky.dayNight({ hour })` returns `nodes`, `background`, and
57
58
  `dayFactor`; `weather.precipitation({ type: "rain" | "snow" })` and
58
59
  `weather.wetGround(...)`; `water.surface({ preset: "calm" | "moderate" |
59
60
  "rough" | "storm" })`. Add their `nodes` with `scene().addMany(...)` and use
60
- the returned `background`.
61
+ the returned `background`. Strata, caustics, god rays, beams: [visual helpers](references/visual-helpers.md).
61
62
  4. Choosing values: start from `material.presets()` and the named builders
62
- above (car paint is `material.clearcoatPaint`, thin glass is
63
- `material.glass`, brushed metal is `material.brushedMetal`); the in-repo
64
- preset library (`GAME_READY_MATERIAL_PRESETS` in
65
- `packages/engine/src/devtools/materials/MaterialValidation.ts`) is a source
63
+ above (car paint is `material.clearcoatPaint`, thin glass `material.glass`,
64
+ brushed metal `material.brushedMetal`); `GAME_READY_MATERIAL_PRESETS` in
65
+ `packages/engine/src/devtools/materials/MaterialValidation.ts` is a source
66
66
  reference for values, not an import.
67
67
 
68
68
  Step 2, a licensed local file (Poly Haven or another CC0 / CC-BY source you
@@ -0,0 +1,32 @@
1
+ # Metal and glass that must read
2
+
3
+ Use `material.studioMetal({ color, roughness })` and `material.studioGlass({ tint, ior, thickness })` for product shots,
4
+ trophies, spheres and anything where chrome, steel, gold or clear glass is the subject. They carry sensible reflection
5
+ settings (full metalness, no clearcoat, `envMapIntensity` 1 for metal and 1.25 for glass, absorption tint and
6
+ `thickness` for glass), wrap `material.metal` and `material.glass`, and every option still overrides.
7
+
8
+ Why they exist: chrome and glass only show what they reflect or refract. A near-uniform probe (`environments.neutral()`,
9
+ plain `environments.studio()`) plus a pale backdrop leaves both invisible, and raising `envMapIntensity` only brightens
10
+ the same flat reflection. Clear glass additionally needs physical transmission, which is off unless
11
+ `A3D_QR_MATERIALS_TRANSMISSION` is on; without it the glass draws as an opaque white ball and the console prints
12
+ `TRANSMISSION_DEGRADED`.
13
+
14
+ What the helpers do automatically:
15
+
16
+ - A scene that uses either helper and authors no `environments.*` node or look binds the `softbox` environment itself.
17
+ An authored environment or look always wins.
18
+ - `createAuraApp` turns physical transmission on for a scene that uses `material.studioGlass`. A source that sets the
19
+ flag explicitly (`?a3d-qr=-materials.transmission`, `qualityRebuild.flags`) wins. It is decided from the scene given
20
+ to `createAuraApp`; a later `setScene` does not re-decide it.
21
+
22
+ To choose it explicitly: `environments.studio({ preset: "softbox" })`, or `environments.preset("softbox")` (background on).
23
+ `environments.studio()` and `environments.neutral()` without the option render exactly as before.
24
+
25
+ The `softbox` probe is a structured studio: an overhead softbox, a tall key softbox, two strip lights, a windowed fill
26
+ with mullions and two dark flags over a gradient room, at the neutral probe's mean brightness so diffuse lighting
27
+ matches. It is embedded in the package (about 230 KB of base64 in its own lazy chunk), so no `/aura-environments` fetch
28
+ and no copied file; `public/aura-environments/softbox.*` (about 0.5 MiB) ships the same bytes. Regenerate with
29
+ `node_modules/.bin/tsx tools/environments/bake-softbox.ts`.
30
+
31
+ Glass shows the opaque scene behind it, so put it in front of a mid-tone or graded backdrop, not plain white. The helpers
32
+ cannot add a backdrop for you.
@@ -0,0 +1,20 @@
1
+ # Visual helpers: strata, caustics, god rays, light beams
2
+
3
+ Judge each in a real-GPU capture, not from the declaration.
4
+
5
+ - `material.strata({ bands, colors, heightRange, noise, noiseScale, seam, variation, slope, roughness, seed })`: height-banded
6
+ rock lit by scene lights and the environment. `slope: { angle, blend, flatColor }` gives flat ground its own colour and bands only
7
+ steep faces, so a canyon wall separates from its floor. Apply it to a `geometry.custom` heightfield (recipe 13) or any mesh. Static: it
8
+ never forces redraws.
9
+ - `material.caustics({ color, causticColor, strength, scale, speed, surfaceY, depthFade, roughness })`: a lit floor with moving
10
+ caustic light projected straight down in world X/Z. It reads `uTime`, so it animates only while the app draws frames: a
11
+ scene with a `uTime` custom shader is a declared time consumer on builds that carry `timeConsumers`; elsewhere add
12
+ `app.onFrame(() => {})` or a looping timeline.
13
+ - `effects.godRays({ source, intensity, decay, samples, color, density, medium })`: screen-space shafts (post stage S4, WebGL2
14
+ v2 chain). `source` is `"sun"` (the strongest directional light), a world point or a light-like `{ position }` / `{ direction }`.
15
+ Shafts need emitters: visible sky or a bright object near the light, and the light in front of the camera. `samples` is capped by the
16
+ quality tier, and the stage is off on the Low tier. Where the backend cannot sample depth the depth-aware chain throws
17
+ `DEPTH_POSTPROCESS_UNSUPPORTED`, as for every depth-aware post effect.
18
+ - `effects.volumetricBeam({ position, direction, length, endRadius, color, intensity, falloff, edgeSoftness, noise })`: an open
19
+ additive cone mesh (a primitive node) with a soft silhouette and drifting dust noise; two beams make a lighthouse.
20
+ `material.lightBeam(options)` is the same shader for your own cone.
@@ -69,6 +69,11 @@ manual quality override invalidates every measurement that follows.
69
69
  `batched.mesh` apply texture maps since 4.1; older builds drew them white.
70
70
  An empty instance array is a no-op with an `INSTANCES_EMPTY` diagnostic, not a
71
71
  crash, so plan for zero placements instead of special-casing them.
72
+ Live data at 10-20 Hz (sensor point clouds, per-frame poses): do not rebuild the scene or tuple
73
+ arrays. Declare `points.stream({ capacity, colors: "u8x4n", runtime: { id } })`, then
74
+ `pointStreamOf(app.nodes.require(id)).write(Float32Array, { colors })`; dense instance writes go
75
+ through `handle.setInstanceMatrices` (primitives and, since 5.1, `instances.model`). See
76
+ `docs/agents/streaming-buffers.md`; its numbers are measurements, not claims.
72
77
  4. Scattered props (trees, rocks, lamps, houses): plan placements with
73
78
  `scatterAlongPath` or `scatterInArea` (seeded, exclusion zones, `minDistance`,
74
79
  slope and height filters), split them by distance with `splitScatterByDistance`