rayzee 11.2.1 → 11.4.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 (76) hide show
  1. package/README.md +91 -34
  2. package/dist/ArchiveImporter-Cgyx5AF7.js +7860 -0
  3. package/dist/BidirectionalIntegrator-BKF-cwH2.js +954 -0
  4. package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
  5. package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-C3Q2XqbF.js} +4 -3
  6. package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-DV5TtURP.js} +11 -5
  7. package/dist/{PathTracer-D6nbknj5.js → PathTracer-D3MvEaZK.js} +4384 -4618
  8. package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-CY4jiz6a.js} +2 -2
  9. package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-D23m6NqG.js} +486 -391
  10. package/dist/SolarLimb-bZE6y63S.js +762 -0
  11. package/dist/{ToneMapGPU-BOjpul7A.js → ToneMapGPU-CTDBvmmS.js} +31 -22
  12. package/dist/addons/archives.es.js +1 -1
  13. package/dist/addons/bidirectional.es.js +1 -1
  14. package/dist/addons/physical-sky.es.js +1 -1
  15. package/dist/rayzee-core.es.js +7 -7
  16. package/dist/rayzee.es.js +1327 -838
  17. package/dist/rayzee.umd.js +146 -134
  18. package/dist/sceneCachePolicy-AKOEu2lf.js +1602 -0
  19. package/package.json +1 -1
  20. package/src/EngineDefaults.js +2 -2
  21. package/src/Passes/AIUpscaler.js +2 -0
  22. package/src/PathTracerApp.js +126 -19
  23. package/src/Pipeline/CompletionTracker.js +7 -3
  24. package/src/Processor/ArchiveImporter.js +173 -94
  25. package/src/Processor/ArchiveReader.js +20 -23
  26. package/src/Processor/AssetLoader.js +48 -1
  27. package/src/Processor/BVHRefitter.js +109 -13
  28. package/src/Processor/BufferLayout.js +115 -0
  29. package/src/Processor/ChunkedRecords.js +3 -2
  30. package/src/Processor/EmissiveTriangleBuilder.js +6 -6
  31. package/src/Processor/GeometryExtractor.js +189 -79
  32. package/src/Processor/HostMemory.js +39 -9
  33. package/src/Processor/InstanceTable.js +714 -42
  34. package/src/Processor/PBRT/PBRTCurves.js +224 -6
  35. package/src/Processor/PBRT/PBRTMaterials.js +3 -0
  36. package/src/Processor/PBRT/PBRTSceneBuilder.js +82 -25
  37. package/src/Processor/PackedRayBuffer.js +5 -2
  38. package/src/Processor/SceneProcessor.js +747 -141
  39. package/src/Processor/TLASBuilder.js +32 -17
  40. package/src/Processor/ToneMapCPU.js +16 -4
  41. package/src/Processor/ToneMapGPU.js +30 -14
  42. package/src/Processor/USD/USDLayer.js +1149 -0
  43. package/src/Processor/USD/USDScene.js +1655 -0
  44. package/src/Processor/USD/USDStage.js +682 -0
  45. package/src/Processor/USD/USDText.js +674 -0
  46. package/src/Processor/USD/index.js +138 -0
  47. package/src/Processor/Workers/BVHRefitWorker.js +3 -3
  48. package/src/RayzeeRenderer.js +221 -33
  49. package/src/RenderSettings.js +4 -2
  50. package/src/Stages/AutoExposure.js +484 -494
  51. package/src/Stages/Compositor.js +27 -6
  52. package/src/Stages/LocalExposure.js +546 -0
  53. package/src/Stages/NormalDepth.js +7 -12
  54. package/src/Stages/PathTracer.js +18 -21
  55. package/src/Stages/PathTracerStage.js +282 -103
  56. package/src/Storage/GeometrySpill.js +46 -14
  57. package/src/Storage/SceneGraphCodec.js +17 -3
  58. package/src/Storage/SpillStore.js +9 -0
  59. package/src/TSL/BVHTraversal.js +183 -7
  60. package/src/TSL/Common.js +57 -13
  61. package/src/TSL/ShadeKernel.js +3 -2
  62. package/src/TSL/TextureSampling.js +11 -8
  63. package/src/TSL/patches.js +84 -0
  64. package/src/index.js +1 -0
  65. package/src/integrators/BidirectionalIntegrator.js +20 -2
  66. package/src/managers/DenoisingManager.js +15 -19
  67. package/src/managers/InteractionManager.js +19 -6
  68. package/src/managers/MaterialDataManager.js +22 -5
  69. package/src/managers/UniformManager.js +3 -0
  70. package/src/managers/VideoRenderManager.js +30 -4
  71. package/src/neural/NeuralRetouch.js +1 -0
  72. package/src/neural/NeuralSuperRes.js +6 -1
  73. package/dist/ArchiveImporter-D_x863yu.js +0 -5198
  74. package/dist/BidirectionalIntegrator-DmkxxEOR.js +0 -946
  75. package/dist/SolarLimb-CPaeZVwz.js +0 -721
  76. package/dist/sceneCachePolicy-C4-4qwt6.js +0 -485
package/README.md CHANGED
@@ -328,7 +328,7 @@ const engine = new PathTracerApp(canvas, options?)
328
328
  | `options.maxSceneBytes` | `number` | Raise or lower the CPU memory ceiling a scene may need before the engine refuses it (default 9,216 MB). See [Memory monitoring](#memory-monitoring). |
329
329
  | `options.hostMemoryGB` | `number` | The host's memory, for runtimes without Chrome's `navigator.deviceMemory` (which then read as 4 GB and cap the render reserve at 2048). Sizes the reserve and the path pool. |
330
330
  | `options.storage` | `false \| 'auto' \| StorageManager` | On-disk storage (default: `configureAssets( { storage } )`; off under `strict` unless you set it there or here). A manager you pass stays yours to dispose. See [On-disk storage](#on-disk-storage-opfs). |
331
- | `options.memorySpill` | `boolean` | Experimental, default `false`: build a large static scene through disk — triangle records, BLAS nodes and the three.js geometry are written out as the build finishes with them — and raise the pbrt triangle and placement caps to 60M / 8M. See [On-disk storage](#on-disk-storage-opfs). |
331
+ | `options.memorySpill` | `'auto' \| boolean` | Default `'auto'`: build a static scene through disk when it would not fit in memory — triangle records, BLAS nodes and the three.js geometry are written out as the build finishes with them, and the instance matrix lists, the top-level tree and the triangle order maps once the load is done. `'auto'` does so only for a scene whose in-memory estimate passes the safe line (7 GB); `true` for every static scene; `false` never. Unless `false`, the pbrt and USD triangle and placement caps rise to 120M / 60M. Needs storage. Change it later with `engine.setMemorySpill( mode )`; `engine.sceneSpilled` says whether the current scene went to disk. See [On-disk storage](#on-disk-storage-opfs). |
332
332
 
333
333
  The engine creates and mounts everything it needs (denoiser canvas, tile/HUD overlay) into a single parent on `init()`. Performance HUDs (e.g. `stats-gl`) are not bundled — listen to `EngineEvents.FRAME` and tick your own panel.
334
334
 
@@ -390,8 +390,15 @@ await engine.loadFile({ name: 'Sponza', files: [ // or path + file pairs: a dro
390
390
 
391
391
  Loose files picked together load the same way. Hidden files and folders (`.DS_Store`, `.git/`) are left out. The model
392
392
  that loads is a `scene` / `model` / `main` / `asset` glTF when there is one, else the shallowest model, glTF first; the
393
- others are named in an `asset.ambiguous_entry` warning. A large pbrt folder of several parts asks which to load, as an
394
- archive does (below). `sceneSource` is `{ kind: 'local-folder', folder: { name, size, lastModified, sample, files }, key }`
393
+ others are named in an `asset.ambiguous_entry` warning. A large pbrt or USD folder of several parts asks which to load,
394
+ as an archive does (below).
395
+
396
+ A USD scene — a folder or archive whose main model is a `.usd` / `.usda` / `.usdc` layer, or a loose layer — loads
397
+ through the engine's own USD importer, part of the archives add-on: composition (sublayers, references, payloads,
398
+ inherits, specializes, variants), meshes, curves, point instancers, PxrDisneyBsdf / PxrSurface / UsdPreviewSurface
399
+ materials, cameras, lights and the dome light as the environment. Each layer is read only when composition reaches it. A
400
+ `.usdz` stays with three.js's loader (`usdFormat`). Ptex is not read: a Ptex input takes the mesh's mean
401
+ `displayColor`. `sceneSource` is `{ kind: 'local-folder', folder: { name, size, lastModified, sample, files }, key }`
395
402
  — `sample` hashes every file's path, size and date, so any change inside the folder changes it — and
396
403
  `sceneSourceFolder` hands the files back, as `{ name, files: [{ path, file }] }`.
397
404
 
@@ -406,8 +413,8 @@ The archive can be inspected without retaining any of it, then loaded one elemen
406
413
  ```js
407
414
  const { kind, root, elements, entryCount, totalBytes } = await engine.inspectArchive(file);
408
415
 
409
- await engine.loadFile(file, { element: elements[0].path }); // one element
410
- await engine.loadFile(file, { element: [ a.path, b.path ] }); // several together
416
+ await engine.loadFile(file, { element: elements[0].prefix }); // one element
417
+ await engine.loadFile(file, { element: [ a.prefix, b.prefix ] }); // several together
411
418
  ```
412
419
 
413
420
  Everything above a chosen element comes along — the root scene file, the material library, an
@@ -415,8 +422,9 @@ ancestor's `textures` folder — and an `Include` pointing at an element you lef
415
422
  which is what makes a partial load work. Selecting every element is a valid answer and loads the
416
423
  whole scene.
417
424
 
418
- Past 4 GB unpacked, a multi-element archive **throws** `ARCHIVE_NEEDS_ELEMENT` rather than taking
419
- all of it. The error carries the element list, so a host can turn it into a picker:
425
+ Past 4 GB unpacked (a USD scene: past 1 GB of layers), a multi-element archive or folder **throws**
426
+ `ARCHIVE_NEEDS_ELEMENT` rather than taking all of it. The error carries the element list — each
427
+ `{ name, prefix, files, bytes, scenes }` — so a host can turn it into a picker:
420
428
 
421
429
  ```js
422
430
  try {
@@ -427,20 +435,23 @@ try {
427
435
  }
428
436
  ```
429
437
 
430
- Per-load options for pbrt archives:
438
+ Per-load options for pbrt and USD scenes:
431
439
 
432
440
  | Option | Default | Effect |
433
441
  |---|---|---|
434
442
  | `promptBytes` | 4 GB | moves the line past which a multi-element archive asks for elements |
435
- | `maxTriangles` | 45M (60M with `memorySpill`) | past it, placements are skipped and the build reports itself truncated |
436
- | `maxPlacements` | 6M (8M with `memorySpill`) | the same, for placements |
443
+ | `maxTriangles` | 45M (120M with storage, unless `memorySpill: false`) | past it, placements are skipped and the build reports itself truncated (a USD scene thins curve sets and point instancers first, and never cuts a mesh short unless meshes alone are over) |
444
+ | `maxPlacements` | 6M (60M with storage, unless `memorySpill: false`) | the same, for placements |
437
445
  | `curveTolerance` | 0.05 | how far a curve segment may stray, × the curve's half-width; curves become strips with adaptive segments. `0` gives the old uniform strips bit for bit |
438
446
  | `instanceIncludes` | `true` | a file included again under the same material, with no side effects, is placed as an instance of its first reading instead of being read and stored again |
439
447
 
440
- 45M is the highest rung measured to survive without the spill; raising either cap is a deliberate
441
- act on a fresh browser tab. With `memorySpill`, 80M stored triangles (4.35M placements) have loaded
442
- and rendered; an 89M load ran out of memory while parsing, and WebGPU's 4 GB buffer limit stops the
443
- triangle data at 89.5M in any case.
448
+ 45M is the highest rung measured to survive without the spill, which is why the caps rise only where the build can
449
+ spill; the preflight still refuses what cannot fit. With the spill, the whole Moana USD island — 111.6M triangles, 51.0M
450
+ placed copies — loads and renders in Chrome on a 24 GB Apple M-series in about 4½ minutes. Past
451
+ WebGPU's 4 GB buffer limit the triangle and node stores go up in parts, so buffer size no longer caps a
452
+ scene. Past 1M placed copies the top-level tree is built over clusters of four neighbouring copies
453
+ (about 80 bytes a copy instead of 128), which costs such a scene 17–20 % frame time and nothing below
454
+ that size.
444
455
 
445
456
  #### Settings
446
457
 
@@ -461,6 +472,8 @@ Key settings:
461
472
  |---|---|---|---|
462
473
  | `maxBounces` | `number` | 3 | Max ray bounce depth |
463
474
  | `maxSamples` | `number` | 60 | Max accumulated samples before stopping |
475
+ | `renderLimitMode` | `string` | 'frames' | What else can stop the render: `'frames'` only `maxSamples`; `'time'` `maxSamples` or `renderTimeLimit`, whichever comes first; `'timeOnly'` only `renderTimeLimit` (`maxSamples` is ignored while the limit is above 0). Convergence (`useAdaptiveSampling`) can stop it in every mode |
476
+ | `renderTimeLimit` | `number` | 30 | Seconds of rendering before the time limit stops it, in `'time'` and `'timeOnly'`; `0` turns the limit off. Raising it after it stopped a render continues that render, unless another limit has been reached |
464
477
  | `exposure` | `number` | 1.0 | Exposure value |
465
478
  | `saturation` | `number` | 1.0 | Color saturation (1 = no grade) |
466
479
  | `enableEnvironment` | `boolean` | true | Use environment lighting |
@@ -593,8 +606,9 @@ Colour, storage and the integrators have no loader: colour and storage are used
593
606
  the moment it is chosen (a lazy one would trace plain frames meanwhile and break reproducible renders).
594
607
 
595
608
  **File formats.** The core reads glTF/GLB (Draco, KTX2 and meshopt decoders are fetched only for a file that uses
596
- them), `.hdr` and LDR images. FBX, OBJ, STL, PLY, Collada, 3MF, USD/USDZ and EXR are formats to register — import only
597
- those you read and a bundler leaves the rest out; each three.js loader is downloaded the first time its format is read:
609
+ them), `.hdr` and LDR images. FBX, OBJ, STL, PLY, Collada, 3MF, USDZ and EXR are formats to register — import only
610
+ those you read and a bundler leaves the rest out; each three.js loader is downloaded the first time its format is read
611
+ (a `.usd` / `.usda` / `.usdc` layer is read by the engine's own USD importer, in the archives add-on below):
598
612
 
599
613
  ```js
600
614
  import { objFormat, usdFormat, exrFormat } from 'rayzee/addons/formats';
@@ -827,6 +841,7 @@ engine.setMeshVisibilityByUuid(uuid, prev => !prev) // toggle via updater fn
827
841
  // Lower-level — for callers that already have a meshIndex or have mutated object.visible directly
828
842
  engine.setMeshVisibility(meshIndex, visible)
829
843
  engine.updateAllMeshVisibility() // re-sync after manual object.visible mutations
844
+ // With memorySpill, an edit whose top-level tree is on disk lands once it is read back (a moment later).
830
845
 
831
846
  // Read access to the active scene (returns the mesh-bearing scene)
832
847
  engine.getScene()
@@ -903,7 +918,11 @@ await engine.environmentManager.generateProcedural();
903
918
 
904
919
  ### engine.denoisingManager
905
920
 
906
- Denoiser strategy, ASVGF, OIDN, upscaler, and auto-exposure.
921
+ Denoiser strategy, ASVGF, OIDN, upscaler, and auto-exposure. Auto exposure meters the accumulated image on the GPU
922
+ and moves in stops; it follows a room level learned as the camera moves plus a damped share of each view, so a scene lit
923
+ for its manual exposure stays near it while one lit far off is corrected in full. It eases while the image restarts
924
+ every frame (a moving camera, playback) and lands within about a third of a second once the image builds up, so a
925
+ finished render shows its own exposure at any sample count. While it is on, `exposure` is its compensation.
907
926
 
908
927
  ```js
909
928
  // Strategy
@@ -916,7 +935,13 @@ engine.denoisingManager.setAutoExposure(true)
916
935
  // Fine-grained parameters
917
936
  engine.denoisingManager.setASVGFParams({ temporalAlpha: 0.1, maxAccumFrames: 16 })
918
937
  engine.denoisingManager.setEdgeAwareParams({ phiLuminance: 4.0, atrousIterations: 5 })
919
- engine.denoisingManager.setAutoExposureParams({ keyValue: 0.18 })
938
+ engine.denoisingManager.setAutoExposureParams({
939
+ strength: 0.3, // 0..1, how far it follows each view; 1 averages every view to middle grey
940
+ metering: 'center', // 'average' | 'center' | 'spot' (at the auto-focus point)
941
+ minExposure: 2 ** -8, // how far it may go from the manual exposure (default ±8 stops)
942
+ maxExposure: 2 ** 8,
943
+ keyValue: 0.18, // the grey it aims the average at
944
+ })
920
945
 
921
946
  // OIDN & Upscaler
922
947
  engine.denoisingManager.setOIDNEnabled(true)
@@ -929,6 +954,21 @@ engine.denoisingManager.setUpscalerScaleFactor(2) // 2 or 4
929
954
  engine.denoisingManager.setUpscalerQuality('quality') // 'fast' | 'balanced' | 'quality'
930
955
  ```
931
956
 
957
+ ### Local exposure
958
+
959
+ Unreal Engine 5's local exposure: brings large bright (and, if asked, dark) areas toward middle grey while keeping their
960
+ detail. It changes only what is shown — the canvas, `renderToBuffer( { colorSpace: 'srgb' } )`, the AI upscaler and the
961
+ neural passes apply the same per-pixel gain; EXR and `'linear'` reads stay scene-referred. Off by default.
962
+
963
+ ```js
964
+ engine.setLocalExposure(true)
965
+ engine.setLocalExposureParams({
966
+ highlightContrast: 0.6, // contrast of the base layer above middle grey; 1 = unchanged
967
+ shadowContrast: 1, // below middle grey; < 1 lifts shadows
968
+ detailStrength: 1, // contrast of detail within the base layer
969
+ })
970
+ ```
971
+
932
972
  ### engine.interactionManager
933
973
 
934
974
  Object picking and interaction modes.
@@ -964,9 +1004,11 @@ await engine.refitBVH(positions) // the whole scene is posed
964
1004
  ```
965
1005
 
966
1006
  **`updateMeshTransforms` is the one a gizmo drag wants.** Triangles are stored in each object's own
967
- space, so a rigid move only rewrites a matrix — no vertex pass, no geometry upload. Using
968
- `refitBLASes` for a move instead rewrites vertices needlessly, and drags along any other object
969
- sharing the same geometry.
1007
+ space, so a rigid move only rewrites a matrix — no vertex pass, no geometry upload — and refits only the
1008
+ tree nodes above what moved, uploading just those. Using `refitBLASes` for a move instead rewrites
1009
+ vertices needlessly, and drags along any other object sharing the same geometry. It returns
1010
+ `{ refitTimeMs, placements }`, or `null` on a `memorySpill` scene whose matrices or tree are on disk:
1011
+ the move then lands once they are read back.
970
1012
 
971
1013
  All three take **indices into `engine.sceneMeshes`**, which is a depth-first walk of the rendered
972
1014
  scene and includes the engine's own hidden ground disk. Build your index list from that array, never
@@ -1149,8 +1191,10 @@ Before extraction the engine prices the scene and applies two lines, both record
1149
1191
 
1150
1192
  Raise or lower the hard line with `new PathTracerApp(canvas, { maxSceneBytes })`. The estimate runs
1151
1193
  low at the very top of its range, so the per-load `maxTriangles` cap (45M) is the more reliable
1152
- guard on a scene of that size. With `memorySpill` the estimate leaves out what the build keeps on
1153
- disk (the BVH, and triangle records past what a streamed build holds at once).
1194
+ guard on a scene of that size. For a spilling build the estimate leaves out what the build keeps on
1195
+ disk (the BVH, the three.js geometry, and triangle records past what a streamed build holds at once)
1196
+ and prices the build by its larger phase — extraction, or the top-level tree — since the two never
1197
+ hold their arrays at the same time. The whole Moana island estimates 8.6 GB that way.
1154
1198
 
1155
1199
  ---
1156
1200
 
@@ -1216,8 +1260,8 @@ duration `renderUntilComplete` runs those readbacks in **lockstep**: one issued
1216
1260
  at frame N + 4 exactly, and the render waits if it has not landed; every reset also starts from seed
1217
1261
  0 and from nothing the previous render measured. It turns interaction mode off meanwhile (a
1218
1262
  wall-clock mode for camera drags), and keeps submissions at most 2 × `drainEvery` frames ahead of
1219
- the GPU. A time limit is wall-clock by nature, so that one stop is not reproducible; neither is
1220
- auto-exposure, if you turn it on.
1263
+ the GPU. A time limit is wall-clock by nature, so that one stop is not reproducible. Auto exposure, if you turn it
1264
+ on, lands on a metering of the finished image before the closing denoise.
1221
1265
 
1222
1266
  `engine.setLockstepReadbacks(true)` applies the same lockstep to the rAF loop and to `renderFrames`.
1223
1267
 
@@ -1389,23 +1433,35 @@ a link that expires (a signed URL). `fetchFile( url, storage )` / `cachedObjectU
1389
1433
  for a host's own assets. Failures record `storage.*` issues and fall back to memory; storage never
1390
1434
  throws for being absent or full.
1391
1435
 
1392
- **Memory spill (experimental).** With `memorySpill: true`, a static scene of more than one 64 MB chunk
1393
- is built through disk, so its large arrays are never all in memory at once:
1436
+ **Memory spill.** A static scene too large for memory is built through disk, so its large arrays are never all in
1437
+ memory at once. By default (`memorySpill: 'auto'`) a scene spills only when its in-memory estimate passes the safe
1438
+ line (7 GB) — ordinary models never do and load exactly as before; `true` spills every static scene of more than one
1439
+ 64 MB chunk, `false` none. The decision is made per load, and a spilling scene's environment is narrowed to 8192 texels
1440
+ wide:
1394
1441
 
1395
1442
  - Extraction and BVH building run together. Each stored range of triangles goes to a BLAS worker as
1396
- soon as it is written, and extraction waits while more than 1.5 GB of triangle records are held.
1443
+ soon as it is written, and extraction waits while more than 1 GB of triangle records are held.
1397
1444
  On the 55.7M-triangle Moana subset the build peak fell from 6.6 to 4.1 GB.
1398
- - The three.js geometry goes to disk after the build last reads it, and comes back when the build
1399
- ends. On a 70M-triangle scene the page held 0.84 GB after extraction instead of 4.07 GB.
1445
+ - The three.js geometry goes to disk after the build last reads it. Past 1 GB of it, it **stays there**
1446
+ until something needs it: `await engine.ensureSceneResident()` reads it back, and so does a rebuild of
1447
+ the same model. Meanwhile clicking the model selects nothing and a selection draws no outline (its
1448
+ arrays are empty). Less than 1 GB comes back when the build ends. On a 70M-triangle scene
1449
+ the page held 0.84 GB after extraction instead of 4.07 GB.
1400
1450
  - Each triangle chunk and BLAS goes to the GPU, then to disk. At 50M triangles the page settles at
1401
1451
  7.2 GB instead of 9.1 GB.
1452
+ - Once the load is done, the instance matrix lists, the top-level tree with its copy records and the
1453
+ triangle order maps go to disk too. An edit reads back what it needs (a visibility change the tree, a
1454
+ move the matrices, the tree and that object's copy records) and lands once it has; 30 s after the
1455
+ last edit it all goes back. On the Moana island the CPU data held at rest fell from 6.9 to 2.2 GB, and
1456
+ a tab in Chrome from 5.2 to 3.6 GB.
1457
+ - An environment wider than 8192 pixels is box-filtered down when it loads.
1402
1458
 
1403
1459
  With or without the spill, a mesh past 2M triangles is built as spatial pieces of ≤ 512k triangles
1404
1460
  on a worker pool and joined under the tree that split them, so no build holds a second copy of it.
1405
1461
 
1406
- The render is unchanged. Visibility and rigid moves need nothing read back. Material edits that
1407
- rewrite triangles (side, transparency, emission), and refits, read it back first. `refitBLASes`
1408
- throws on a spilled scene until `await engine.ensureSceneResident()`.
1462
+ The render is unchanged. Material edits that rewrite triangles (side, transparency, emission), and
1463
+ refits, read the scene back first. `refitBLASes` throws on a spilled scene until
1464
+ `await engine.ensureSceneResident()`.
1409
1465
 
1410
1466
  ### Saving Scene State
1411
1467
 
@@ -1473,7 +1529,7 @@ engine.addEventListener(EngineEvents.RENDER_COMPLETE, (e) => {
1473
1529
  | `SETTING_CHANGED` | A render setting is modified |
1474
1530
  | `AUTO_FOCUS_UPDATED` | Auto-focus recalculated — `worldDistance` in scene units, `distance` divided by the model's size |
1475
1531
  | `ORTHO_HEIGHT_UPDATED` | An orthographic view's height changed — `height` in scene units |
1476
- | `AUTO_EXPOSURE_UPDATED` | Auto-exposure recalculated |
1532
+ | `AUTO_EXPOSURE_UPDATED` | Auto exposure moved — `exposure` (applied, with the manual exposure), `autoExposure` (its own part), `targetExposure`, `luminance` (metered) |
1477
1533
  | `AF_POINT_PLACED` | Focus point placed on screen |
1478
1534
  | `ANIMATION_STARTED` / `ANIMATION_PAUSED` / `ANIMATION_STOPPED` / `ANIMATION_FINISHED` | Animation lifecycle |
1479
1535
  | `VIDEO_RENDER_PROGRESS` / `VIDEO_RENDER_COMPLETE` | Video export progress |
@@ -1545,6 +1601,7 @@ import {
1545
1601
  ENGINE_DEFAULTS,
1546
1602
  DENOISER_DEFAULTS,
1547
1603
  AUTO_EXPOSURE_DEFAULTS,
1604
+ LOCAL_EXPOSURE_DEFAULTS,
1548
1605
  AUTO_FOCUS_DEFAULTS,
1549
1606
  ASVGF_QUALITY_PRESETS,
1550
1607
  SKY_DEFAULTS,