@woosh/meep-engine 3.26.0 → 3.27.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.
- package/package.json +1 -1
- package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +542 -0
- package/src/core/binary/lz4/LZ4_LEVEL.d.ts +33 -0
- package/src/core/binary/lz4/LZ4_LEVEL.d.ts.map +1 -0
- package/src/core/binary/lz4/LZ4_LEVEL.js +32 -0
- package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts +33 -0
- package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts.map +1 -0
- package/src/core/binary/lz4/lz4_compress_block_at_level.js +47 -0
- package/src/core/binary/lz4/lz4_compress_block_hc.d.ts +40 -0
- package/src/core/binary/lz4/lz4_compress_block_hc.d.ts.map +1 -0
- package/src/core/binary/lz4/lz4_compress_block_hc.js +1382 -0
- package/src/core/binary/lz4/lz4_decompress_block.d.ts +2 -1
- package/src/core/binary/lz4/lz4_decompress_block.d.ts.map +1 -1
- package/src/core/binary/lz4/lz4_decompress_block.js +31 -6
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +14 -7
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +87 -78
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/vgeo_runtime/README.md +167 -22
- package/src/shade/playground/vgeo_runtime/index.html +112 -62
- package/src/shade/playground/vgeo_runtime/main.js +660 -83
- package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +1 -1
- package/src/shade/playground/vgeo_viewer/sample_asset.js +19 -2
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +11 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +16 -8
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +16 -11
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +73 -5
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +26 -15
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +5 -3
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +20 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +56 -9
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +24 -0
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +12 -0
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +13 -0
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +11 -3
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +10 -1
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.js +17 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts +8 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.js +7 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.js +13 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.js +10 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +20 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +4 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +4 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +29 -3
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +81 -122
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +40 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +44 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +69 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +236 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts +31 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.js +32 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +21 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.js +84 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +96 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +55 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +202 -13
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +147 -10
- package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +637 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +33 -36
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +109 -193
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +58 -28
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +77 -105
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +37 -0
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +105 -22
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +548 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +29 -50
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +47 -489
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts +84 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +393 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts +234 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +1363 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts +81 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.js +89 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +53 -34
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +407 -124
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts +100 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.js +219 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.js +47 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +30 -12
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +18 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +5 -15
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.js +77 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +186 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +20 -8
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +580 -188
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +15 -18
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +19 -28
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +45 -25
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +0 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +12 -10
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +7 -2
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +8 -3
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +2 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts +5 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.js +22 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.js +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.js +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts +23 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.js +28 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts +2 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +10 -5
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +15 -6
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +6 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +38 -1
- package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
- package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +4 -3
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.js +6 -3
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +6 -3
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -4
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +2 -0
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +3 -5
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +3 -4
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +3 -4
- package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +7 -7
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +83 -78
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +7 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +7 -1
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +22 -2
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +59 -4
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +92 -16
- package/src/shade/renderer/view/GPUViewContext.d.ts +5 -0
- package/src/shade/renderer/view/GPUViewContext.d.ts.map +1 -1
- package/src/shade/renderer/view/GPUViewContext.js +21 -0
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +74 -3
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +0 -723
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +0 -50
- package/src/shade/renderer/shader/chunk/jobs/NOTES.md +0 -47
- package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.js +0 -102
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sample_asset.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/vgeo_viewer/sample_asset.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sample_asset.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/vgeo_viewer/sample_asset.js"],"names":[],"mappings":"AA0GA;;;;;;;;;;;GAWG;AACH,2CALW,MAAM,UACN,MAAM,cACN,MAAM,GACJ;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAiBtD;+BA1HM,+DAA+D"}
|
|
@@ -56,13 +56,30 @@ function torus(major, minor) {
|
|
|
56
56
|
|
|
57
57
|
const stride = minor + 1;
|
|
58
58
|
|
|
59
|
+
/*
|
|
60
|
+
Wound so that a triangle seen from **outside** the tube is counter-clockwise, which is what
|
|
61
|
+
`CullPolicy.Standard` means by a front face and what `vgeo_compute_vertex_normals` takes the
|
|
62
|
+
sign of its normals from.
|
|
63
|
+
|
|
64
|
+
It was the other way round, and both of the pages that build this were blind to it: the viewer
|
|
65
|
+
draws its cut with `cullMode: 'none'` and colours it per cluster, so neither the culling nor
|
|
66
|
+
the shading could report it. The moment the same sample went through the engine's material
|
|
67
|
+
path — `playground/vgeo_runtime/`, where a `Front`-sided material culls back faces — the torus
|
|
68
|
+
rendered as its own far inner wall with the near surface missing, and the normals pointed into
|
|
69
|
+
the tube so what survived was lit from inside.
|
|
70
|
+
|
|
71
|
+
The winding of a sample is not a detail of the sample. Everything downstream of the builder
|
|
72
|
+
carries it: the cluster's triangles are stored in this order, the DAG's simplification
|
|
73
|
+
preserves it, and the pool hands it to the rasterizer untouched — so this one line decides
|
|
74
|
+
whether the whole chain has ever been seen drawing a surface the right way out.
|
|
75
|
+
*/
|
|
59
76
|
for (let i = 0; i < major; i++) {
|
|
60
77
|
for (let j = 0; j < minor; j++) {
|
|
61
78
|
const a = i * stride + j;
|
|
62
79
|
const b = a + stride;
|
|
63
80
|
|
|
64
|
-
indices.push(a,
|
|
65
|
-
indices.push(a + 1, b
|
|
81
|
+
indices.push(a, a + 1, b);
|
|
82
|
+
indices.push(a + 1, b + 1, b);
|
|
66
83
|
}
|
|
67
84
|
}
|
|
68
85
|
|
|
@@ -72,6 +72,17 @@ export class GPUGeometryBVHManager {
|
|
|
72
72
|
*/
|
|
73
73
|
get empty_tree_address(): number;
|
|
74
74
|
get gpu_memory_usage(): number;
|
|
75
|
+
/**
|
|
76
|
+
* Bytes of the node arena that trees actually occupy: its capacity, less every region
|
|
77
|
+
* {@link #allocator} still holds free.
|
|
78
|
+
*
|
|
79
|
+
* Beside {@link gpu_memory_usage}, which is what the arenas cost whether or not anything is
|
|
80
|
+
* in them. The gap between the two is what a released tree gives back and what the next tree
|
|
81
|
+
* of that size takes, so it is the one worth watching.
|
|
82
|
+
*
|
|
83
|
+
* @returns {number} in bytes
|
|
84
|
+
*/
|
|
85
|
+
get gpu_memory_usage_occupied(): number;
|
|
75
86
|
/**
|
|
76
87
|
* The tree belonging to `owner`, registered against `source`'s shape if it does not exist yet.
|
|
77
88
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GPUGeometryBVHManager.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GPUGeometryBVHManager.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH;;;;;GAKG;AACH,gDAGG;AAEH;;;;;;;;;;;GAWG;AACH,sCAFU,MAAM,CAEyB;AAoBzC;;;;;;;;;;;;;;GAcG;AACH;IA0II;;;OAGG;IACH,+BAoDC;IA1LD;;;OAGG;IACH,iCAEC;IAOD;;;OAGG;IACH,6BAEC;IAuCD;;OAEG;IACH,gCAEC;IAOD;;OAEG;IACH,mCAEC;IA+BD;;OAEG;IACH,gCAEC;IA6ED;;;;OAIG;IACH,iCAEC;IAED,+BAoBC;IAED;;;;;;;;;OASG;IACH,wCAIC;IAED;;;;;;;;;;;;;;OAcG;IACH,cALW,MAAM,UACN,eAAe,GAEb,sBAAsB,CAalC;IA8DD;;;;;;;;;;;;OAYG;IACH,wBAHW,MAAM,GACJ;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAC,GAAC,SAAS,CAqB3I;IAED;;;;;;OAMG;IACH,mBAHW,MAAM,GACJ,MAAM,GAAC,SAAS,CAU5B;IAED;;;;;;;;;OASG;IACH,0BAFW,WAAW,QAQrB;IAED;;;;;;;;;;;OAWG;IACH,cAHW,MAAM,GACJ,OAAO,CA8BnB;IA4LD;;;;;;;OAOG;IACH,eAOC;;CAgIJ;8BAj0BY,OAAO,uBAAuB,EAAE,eAAe;yBAC/C,OAAO,sDAAsD,EAAE,UAAU;6BAbzD,mCAAmC;uCASzB,6BAA6B"}
|
|
@@ -4,7 +4,6 @@ import { platform_compute_endianness } from "../../../../core/binary/platform_co
|
|
|
4
4
|
import { array_buffer_copy } from "../../../../core/collection/array/typed/array_buffer_copy.js";
|
|
5
5
|
import { Deque } from "../../../../core/collection/queue/Deque.js";
|
|
6
6
|
import { round_up } from "../../../../core/math/round_up.js";
|
|
7
|
-
import { number_format_by_thousands } from "../../../../core/primitives/numbers/number_format_by_thousands.js";
|
|
8
7
|
import { u32 } from "../../shader/type/WEBGPU_WGSL_PRIMITIVE_TYPES.js";
|
|
9
8
|
import { WebGPUStruct } from "../../shader/type/WebGPUStruct.js";
|
|
10
9
|
import { BVH2_NODE_STRUCT } from "../../shader/chunk/bvh/BVH2_NODE_STRUCT.js";
|
|
@@ -307,6 +306,22 @@ export class GPUGeometryBVHManager {
|
|
|
307
306
|
return r;
|
|
308
307
|
}
|
|
309
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Bytes of the node arena that trees actually occupy: its capacity, less every region
|
|
311
|
+
* {@link #allocator} still holds free.
|
|
312
|
+
*
|
|
313
|
+
* Beside {@link gpu_memory_usage}, which is what the arenas cost whether or not anything is
|
|
314
|
+
* in them. The gap between the two is what a released tree gives back and what the next tree
|
|
315
|
+
* of that size takes, so it is the one worth watching.
|
|
316
|
+
*
|
|
317
|
+
* @returns {number} in bytes
|
|
318
|
+
*/
|
|
319
|
+
get gpu_memory_usage_occupied() {
|
|
320
|
+
const free_nodes = this.#allocator.storageReport().totalFreeSpace;
|
|
321
|
+
|
|
322
|
+
return (this.#node_capacity - free_nodes) * BVH2_NODE_STRUCT.size;
|
|
323
|
+
}
|
|
324
|
+
|
|
310
325
|
/**
|
|
311
326
|
* The tree belonging to `owner`, registered against `source`'s shape if it does not exist yet.
|
|
312
327
|
*
|
|
@@ -828,13 +843,6 @@ export class GPUGeometryBVHManager {
|
|
|
828
843
|
);
|
|
829
844
|
}
|
|
830
845
|
|
|
831
|
-
const free_nodes = this.#allocator.storageReport().freeStorage;
|
|
832
|
-
|
|
833
|
-
console.warn(
|
|
834
|
-
`BLAS size: ${number_format_by_thousands((this.#node_capacity - free_nodes) * BVH2_NODE_STRUCT.size)} bytes` +
|
|
835
|
-
` of ${number_format_by_thousands(this.#node_capacity * BVH2_NODE_STRUCT.size)}`
|
|
836
|
-
);
|
|
837
|
-
|
|
838
846
|
device.queue.submit([encoder.finish()])
|
|
839
847
|
|
|
840
848
|
// free up the temp buffer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GPUMeshletManager.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/geometry/meshlet/GPUMeshletManager.js"],"names":[],"mappings":"AAuFA;
|
|
1
|
+
{"version":3,"file":"GPUMeshletManager.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/geometry/meshlet/GPUMeshletManager.js"],"names":[],"mappings":"AAuFA;IA4EI;;;;;;OAMG;IACH;QAJ8B,MAAM;QACR,qBAAqB,GAAtC,MAAM;QACW,yBAAyB,GAA1C,MAAM;OAkChB;IAhGD,6BAEC;IAOD,iCAEC;IAuFD,gBAKC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,sBAHW,MAAM,uBACN,MAAM,QAuHhB;IAoRD;;;;;;;;;;;OAWG;IACH,qBAJW,4BAA4B,GAE1B,OAAO,CAkBnB;IAgCD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,+BAHW,4BAA4B,GAC1B,4BAA4B,CA8BxC;IAED;;;;;;;;;;;;OAYG;IACH,gBAFW,sBAAsB,QA8GhC;IAED,gBAGC;IAED;;;;OAIG;IACH,iBAHW,YAAY,GACV,4BAA4B,CA6BxC;IAED,mCASC;IAED,wCAQC;IAED,+BAYC;;CACJ;yBAp5BY,OAAO,sDAAsD,EAAE,UAAU;qCACzE,OAAO,2CAA2C,EAAE,sBAAsB;2BAC1E,OAAO,mBAAmB,EAAE,YAAY;AA6BrD;IACI;;OAEG;IACH,UAFU,UAAU,CAEZ;IACR;;OAEG;IACH,MAFU,UAAU,CAEhB;IAEJ;;;OAGG;IACH,WAFU,MAAM,CAEF;IAEd;;;OAGG;IACH,eAFU,MAAM,CAEE;IAElB;;;OAGG;IACH,0BAFU,WAAW,CAEI;IAEzB;;;OAGG;IACH,aAAsB;CACzB"}
|
|
@@ -129,10 +129,17 @@ export class GPUMeshletManager {
|
|
|
129
129
|
#deallocations_since_last_rebuild = 0;
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
+
* Every live allocation, in the order it was made — which is the order a rebuild packs them in.
|
|
132
133
|
*
|
|
133
|
-
* @
|
|
134
|
+
* A `Set` and not an array: {@link remove_batch} used to find its record with `indexOf` and take
|
|
135
|
+
* it out with `splice`, a walk of every record the manager held per removal. A static scene
|
|
136
|
+
* never noticed. A virtual-geometry tier evicts pages every frame, and each eviction paid a
|
|
137
|
+
* scan of every resident page and every static batch — 15 µs at 64,000 records, and an asset's
|
|
138
|
+
* unregister paid it once per page (RUNTIME_PLAN_2026_09_14 §0, step 0).
|
|
139
|
+
*
|
|
140
|
+
* @type {Set<MeshletBatchAllocationRecord>}
|
|
134
141
|
*/
|
|
135
|
-
#records =
|
|
142
|
+
#records = new Set();
|
|
136
143
|
|
|
137
144
|
/**
|
|
138
145
|
* Queue of GPU-side clones that have been allocated but not yet
|
|
@@ -407,7 +414,7 @@ export class GPUMeshletManager {
|
|
|
407
414
|
const device = this.#device;
|
|
408
415
|
|
|
409
416
|
const records = this.#records;
|
|
410
|
-
const record_count = records.
|
|
417
|
+
const record_count = records.size;
|
|
411
418
|
const new_allocator_data = new OffsetAllocator(
|
|
412
419
|
new_data_size,
|
|
413
420
|
Math.max(record_count * 2, ALLOCATION_LIMIT)
|
|
@@ -438,9 +445,8 @@ export class GPUMeshletManager {
|
|
|
438
445
|
|
|
439
446
|
const moved_records = [];
|
|
440
447
|
|
|
441
|
-
// move data
|
|
442
|
-
for (
|
|
443
|
-
const record = records[i];
|
|
448
|
+
// move data, in allocation order, which is the order the set keeps
|
|
449
|
+
for (const record of records) {
|
|
444
450
|
|
|
445
451
|
const new_allocation_data = new_allocator_data.allocate(record.data_size);
|
|
446
452
|
const new_allocation_metadata = new_allocator_metadata.allocate(record.meshlet_count);
|
|
@@ -613,7 +619,7 @@ export class GPUMeshletManager {
|
|
|
613
619
|
|
|
614
620
|
record.metadata_buffer_original = batch.metadata_buffer;
|
|
615
621
|
|
|
616
|
-
this.#records.
|
|
622
|
+
this.#records.add(record);
|
|
617
623
|
|
|
618
624
|
return record;
|
|
619
625
|
}
|
|
@@ -633,8 +639,7 @@ export class GPUMeshletManager {
|
|
|
633
639
|
remove_batch(record) {
|
|
634
640
|
assert.defined(record, 'record');
|
|
635
641
|
|
|
636
|
-
|
|
637
|
-
if (index === -1) {
|
|
642
|
+
if (!this.#records.has(record)) {
|
|
638
643
|
return false;
|
|
639
644
|
}
|
|
640
645
|
|
|
@@ -643,7 +648,7 @@ export class GPUMeshletManager {
|
|
|
643
648
|
this.#allocator_data.free(record.data);
|
|
644
649
|
this.#allocator_metadata.free(record.metadata);
|
|
645
650
|
|
|
646
|
-
this.#records.
|
|
651
|
+
this.#records.delete(record);
|
|
647
652
|
this.#deallocations_since_last_rebuild++;
|
|
648
653
|
|
|
649
654
|
return true;
|
|
@@ -722,7 +727,7 @@ export class GPUMeshletManager {
|
|
|
722
727
|
// the clone's metadata correctly under its new data offset.
|
|
723
728
|
record.metadata_buffer_original = source.metadata_buffer_original;
|
|
724
729
|
|
|
725
|
-
this.#records.
|
|
730
|
+
this.#records.add(record);
|
|
726
731
|
|
|
727
732
|
// The queue names the two records rather than the addresses they
|
|
728
733
|
// hold right now. Both are in `#records`, so a rebuild between
|
|
@@ -116,7 +116,7 @@ All u64/u128 fields are naturally 8-byte aligned.
|
|
|
116
116
|
| offset | type | field | notes |
|
|
117
117
|
|-------:|-----------|--------------------------|-------|
|
|
118
118
|
| 0 | u32 | magic | `'VGEO'` = 0x4F454756 |
|
|
119
|
-
| 4 | u16 | format_version |
|
|
119
|
+
| 4 | u16 | format_version | 4 today; breaking layout changes only |
|
|
120
120
|
| 6 | u16 | min_reader_version | 3 today; reader MUST reject if its version is lower |
|
|
121
121
|
| 8 | u16 | header_size | = 256 |
|
|
122
122
|
| 10 | u8 | hash_algo | 0 = xxHash3; readers MUST reject unknown values |
|
|
@@ -145,6 +145,15 @@ All u64/u128 fields are naturally 8-byte aligned.
|
|
|
145
145
|
| 160 | [u8; 88] | reserved | |
|
|
146
146
|
| 248 | u64 | header_checksum | xxh3-64 of bytes 0–255 with this field zeroed (`asset_id` already final) |
|
|
147
147
|
|
|
148
|
+
**`format_version` 4 moved and `min_reader_version` did not**, which is the pair working as
|
|
149
|
+
intended rather than an oversight. 4 added the parent tables (§6.3, §6.4): they occupy bytes that
|
|
150
|
+
were reserved and written as zero, and they sit between the cluster records and the payload, so
|
|
151
|
+
every offset a version-3 reader computes lands exactly where it did and such a reader reads a
|
|
152
|
+
version-4 file correctly by ignoring them. In the other direction a version-4 reader meets
|
|
153
|
+
`parent_table_offset = 0` on an older file and takes it as "this container has no upward edge",
|
|
154
|
+
which is true — so neither direction needs a version test, and nothing has to be rebuilt to stay
|
|
155
|
+
readable.
|
|
156
|
+
|
|
148
157
|
### 4.1 Level table (`level_count` × 32 B, immediately after the header)
|
|
149
158
|
|
|
150
159
|
Statistics per level, at `256 + 32·i`, ending where the root page frame begins. Small —
|
|
@@ -293,9 +302,15 @@ about 0.6 ms for a 256 KB page.
|
|
|
293
302
|
## 6. Decoded page
|
|
294
303
|
|
|
295
304
|
```
|
|
296
|
-
[ PageHeader (56 B) ] [ Group table ] [ Node table ] [ Cluster records ]
|
|
305
|
+
[ PageHeader (56 B) ] [ Group table ] [ Node table ] [ Cluster records ]
|
|
306
|
+
[ Parent-page table ] [ Group-parent table ] [ Payload ]
|
|
297
307
|
```
|
|
298
308
|
|
|
309
|
+
The two parent tables sit between the cluster records and the payload, and that placement is the
|
|
310
|
+
reason `min_reader_version` did not move (§4): `node_table_offset`'s `= 56 + 8·group_count` stays
|
|
311
|
+
true, every earlier offset is where it was, and both tables hold 4-byte entries over sections that
|
|
312
|
+
are already multiples of four, so §11.8's word-aligned payload survives.
|
|
313
|
+
|
|
299
314
|
PageHeader:
|
|
300
315
|
|
|
301
316
|
| offset | type | field | notes |
|
|
@@ -309,7 +324,9 @@ PageHeader:
|
|
|
309
324
|
| 12 | u32 | payload_offset | |
|
|
310
325
|
| 16 | u32 | payload_size | |
|
|
311
326
|
| 20 | u32 | node_table_offset | = `56 + 8·group_count`; the node table follows the group table |
|
|
312
|
-
| 24 |
|
|
327
|
+
| 24 | u32 | parent_table_offset | start of the parent section — the parent-page table, then the group-parent table. **0 means the page carries neither**, which is what every container written before `format_version` 4 says about itself |
|
|
328
|
+
| 28 | u16 | parent_entry_count | entries in the group-parent table (§6.3) = Σ `parent_cluster_count` over this page's groups |
|
|
329
|
+
| 30 | u16 | parent_page_count | entries in the parent-page table (§6.4) |
|
|
313
330
|
| 32 | metric | metric | this page's own traversal metric (§6.2) — the root node's, repeated here so an installed page can be tested from itself |
|
|
314
331
|
|
|
315
332
|
Group table — `group_count` × 8 B. Groups are the atomic LOD/residency unit; because a
|
|
@@ -320,8 +337,44 @@ construction — no global group index exists or is needed:
|
|
|
320
337
|
|-------:|------|----------------------|-------|
|
|
321
338
|
| 0 | u16 | cluster_first | index into this page's cluster records |
|
|
322
339
|
| 2 | u16 | cluster_count | ≥ 1; ≤ 4096 (format cap, §11.5) |
|
|
323
|
-
| 4 | u16 | parent_cluster_count | number of parent clusters referencing this group, `SAME_PAGE` references included; 0 exactly for the root page's topmost-level groups — drives activation (§10) |
|
|
324
|
-
| 6 | u16 |
|
|
340
|
+
| 4 | u16 | parent_cluster_count | number of parent clusters referencing this group, `SAME_PAGE` references included; 0 exactly for the root page's topmost-level groups — drives activation (§10). Also the length of this group's run in the group-parent table |
|
|
341
|
+
| 6 | u16 | parent_first | index of that run's first entry (§6.3). Groups tile the table in order, exactly as `cluster_first` tiles the cluster records |
|
|
342
|
+
|
|
343
|
+
### 6.3 Group-parent table (`parent_entry_count` × 4 B)
|
|
344
|
+
|
|
345
|
+
The format's **only upward edge**, and the mirror of the child table with everything a parent does
|
|
346
|
+
not need taken out. Every entry here is the reverse of exactly one cluster's
|
|
347
|
+
`child_ref`/`child_group` pair, and §11.16 holds the two to each other.
|
|
348
|
+
|
|
349
|
+
| offset | type | field | notes |
|
|
350
|
+
|-------:|------|----------------|-------|
|
|
351
|
+
| 0 | u16 | parent_ref | index into the parent-page table (§6.4), or `SAME_PAGE` |
|
|
352
|
+
| 2 | u16 | parent_cluster | cluster index within that page |
|
|
353
|
+
|
|
354
|
+
**Four bytes an entry, against the child table's 48**, because the two directions are not
|
|
355
|
+
symmetric. A child reference carries a fetch range and a metric copy because the child may be
|
|
356
|
+
absent and the decision to fetch it is taken before it arrives. A parent of a group in the cut is
|
|
357
|
+
resident already — `VGEO_GROUP_FLAG_SELECTABLE` is exactly "every parent reference is live" — so a
|
|
358
|
+
parent reference has nothing to decide and only has to say where.
|
|
359
|
+
|
|
360
|
+
Measured across 8,899 pages of 30 converted containers: 17.4 entries a page on average and 26 at
|
|
361
|
+
the worst, so the table runs to about 70 bytes against pages of 56–256 KB, and compresses with the
|
|
362
|
+
rest of them (§5: tables are 7% of page bytes at 1.6×).
|
|
363
|
+
|
|
364
|
+
What it is *for* is a cut that is kept between frames. A cut that has descended to a group and then
|
|
365
|
+
sees the camera pull back has to draw the clusters that consumed that group instead, and without
|
|
366
|
+
this it could only find them by descending from the root again — which is the cost keeping a cut is
|
|
367
|
+
meant to remove, paid on the common case.
|
|
368
|
+
|
|
369
|
+
### 6.4 Parent-page table (`parent_page_count` × 4 B)
|
|
370
|
+
|
|
371
|
+
One `page_index u32` per **distinct other** page holding a parent of one of this page's groups,
|
|
372
|
+
deduplicated and ascending. A parent inside this page spends `SAME_PAGE` in §6.3 instead and has no
|
|
373
|
+
entry here, so a page never appears in its own table.
|
|
374
|
+
|
|
375
|
+
It lives in the decoded page rather than beside the plaintext child table because nothing needs it
|
|
376
|
+
before decode: no fetch range is computed from it, and in the blob it compresses with the other
|
|
377
|
+
tables. Measured: 4.9 entries a page on average, 18 at the worst.
|
|
325
378
|
|
|
326
379
|
### 6.1 Node table (`node_count` × 32 B)
|
|
327
380
|
|
|
@@ -734,6 +787,21 @@ clustering and simplification dominate regardless.
|
|
|
734
787
|
into more bytes than a page holds has no layout, and the builder refuses it naming the page size
|
|
735
788
|
that would work, rather than writing a page over the size the header states.
|
|
736
789
|
|
|
790
|
+
15. **Parent-table bounds.** `parent_entry_count ≤ 65,535`, so that a group record's u16
|
|
791
|
+
`parent_first` can index the whole table, and `parent_page_count ≤ parent_entry_count`, since
|
|
792
|
+
no entry can name more than one page. The first is a cap rather than a wider field because the
|
|
793
|
+
distance is not close: the worst page of 8,899 measured carries 26 entries, which leaves the
|
|
794
|
+
bound standing 2,521× above it, and widening `parent_first` would cost the group record its
|
|
795
|
+
8-byte size. A builder that ever approached the cap would split the page.
|
|
796
|
+
|
|
797
|
+
16. **The two edges agree.** Every entry of a page's group-parent table (§6.3) resolves to a
|
|
798
|
+
cluster whose `child_group` is the group whose run the entry is in; each group's run holds
|
|
799
|
+
exactly the clusters that name it, with no repeats; and the run is sorted by
|
|
800
|
+
(page index, cluster index). The first is what makes a coarsen legal at all. The third is what
|
|
801
|
+
lets a runtime elect one appender out of a group's several parents by taking the first entry of
|
|
802
|
+
the run, rather than carrying a dedup bitmask sized by instances × groups — so the order is
|
|
803
|
+
load-bearing and not presentation.
|
|
804
|
+
|
|
737
805
|
## 12. Open
|
|
738
806
|
|
|
739
807
|
- Page codec ids beyond raw and LZ4 (§5). LZ4 is the default and is where the cheap
|
|
@@ -245,8 +245,12 @@ Pinned:
|
|
|
245
245
|
same answer as by stopping its descent rather than by evaluating it everywhere: draw a
|
|
246
246
|
cluster iff
|
|
247
247
|
|
|
248
|
-
`project(parent) > τ AND (project(self) ≤ τ OR child group
|
|
248
|
+
`own group selectable AND project(parent) > τ AND (project(self) ≤ τ OR child group
|
|
249
|
+
non-selectable)`
|
|
249
250
|
|
|
251
|
+
The first term is not decoration: a group that is resident but not yet selectable satisfies
|
|
252
|
+
the rest of the rule while the coarser stand-in still covers the same surface, and both are
|
|
253
|
+
drawn a few microns apart — which reads as corrupt geometry rather than as a missing page.
|
|
250
254
|
The clamp is what makes "no holes, ever" (§6) true: without it, when the τ-selected
|
|
251
255
|
level lies below the streaming frontier, *no* resident cluster on that path satisfies
|
|
252
256
|
the pure rule and the guaranteed stand-in is never selected. The child-group
|
|
@@ -265,10 +269,12 @@ Pinned:
|
|
|
265
269
|
scale this design targets is millions of clusters every frame, most of them off-screen. A
|
|
266
270
|
traversal costs the cut. It also gets two things for free that the flat rule pays for
|
|
267
271
|
separately — a missing page simply stops the descent, which *is* the residency clamp, and
|
|
268
|
-
culling at an interior node discards a whole region rather than one cluster.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
culling at an interior node discards a whole region rather than one cluster. WebGPU guarantees no
|
|
273
|
+
forward progress across workgroups, so the persistent-thread answer (nvpro, vk_lod_clusters
|
|
274
|
+
comparison §2.4) is unbounded waiting and is not available; a wave redistributes its own
|
|
275
|
+
production among its lanes instead, and the descent's depth runs inside one dispatch. The
|
|
276
|
+
hierarchy it descends exists at two granularities. Pages form a rooted, acyclic,
|
|
277
|
+
fully-reachable DAG (§11.3–11.4 of the format), measurably
|
|
272
278
|
many-parented on any asset large enough to have more than one page per band, and every
|
|
273
279
|
child-table entry and page header now carries a **traversal metric** so a page can be tested
|
|
274
280
|
*before* it is fetched. Below page granularity, each page carries a tree over its own groups
|
|
@@ -493,12 +499,11 @@ because activation is safe at every arrival order). RAM eviction is no longer op
|
|
|
493
499
|
material upstream of the builder), so the mesh-keyed material buckets downstream stay
|
|
494
500
|
unchanged.
|
|
495
501
|
|
|
496
|
-
What it reads is the **
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
why the selection buffer exists.
|
|
502
|
+
What it reads is the **page arena** — one fixed-stride slot per resident page, holding that
|
|
503
|
+
page's header, node rows, group rows and cluster rows — and the per-geometry lookup that
|
|
504
|
+
resolves a child page index to a slot (RUNTIME_PLAN §3.1). There is no selection buffer and
|
|
505
|
+
no page table: a page's header is the first words of its own slot, and the two (sphere,
|
|
506
|
+
error) pairs are in its cluster rows.
|
|
502
507
|
|
|
503
508
|
Superseded: this step previously specified a **flat** evaluation of the rule over every
|
|
504
509
|
row of that buffer, at a cost of `O(Σ over visible instances of resident(geometry))`. §4
|
|
@@ -513,10 +518,16 @@ because activation is safe at every arrival order). RAM eviction is no longer op
|
|
|
513
518
|
instances enter through their own expansion step. M0 was where that seam was to be fixed
|
|
514
519
|
and it was not (§10); it is the first thing M1 has to do.
|
|
515
520
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
521
|
+
The traversal's GPU shape is settled and built: one indirect dispatch per spill generation, the
|
|
522
|
+
descent's depth inside the kernel, work redistributed across a subgroup's lanes by prefix sum. A
|
|
523
|
+
step costs about 0.5 µs whatever the wave holds, so a light asset pays for the shape of its descent
|
|
524
|
+
rather than for its geometry, while a container with real work in the frontier pays mostly for the
|
|
525
|
+
work. How many dispatches a cut needs is a queue-drain quantity rather than depth divided by the
|
|
526
|
+
step count: a wave keeps `subgroup_size` children and spills the rest to the next dispatch.
|
|
527
|
+
CUT_PLAN_2026_09_14 proposes to stop re-walking from the root by keeping the cut between frames.
|
|
528
|
+
|
|
529
|
+
Open: shadow views (v1 takes the one cut per view of §4, sharing setup rather than results, and a
|
|
530
|
+
proxy where a view does not justify a cut); DRS τ semantics.
|
|
520
531
|
|
|
521
532
|
Settled since: **flat evaluation versus a hierarchy is no longer a measurement to take.** §4
|
|
522
533
|
pins the traversal, and the hierarchy it descends is already written into every built asset
|
|
@@ -339,9 +339,11 @@ several cuts per frame for exactly this reason.
|
|
|
339
339
|
|
|
340
340
|
So the plan is **a cut per view**, with the setup shared rather than the result: the traversal
|
|
341
341
|
kernel, the residency state, and the page working set are all view-independent, and only the
|
|
342
|
-
projection and threshold differ.
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
projection and threshold differ. That half is built — a cut runs per expansion site per view, and
|
|
343
|
+
what it costs is measured in CUT_PLAN_2026_09_14 §0, which also plans to keep each view's cut
|
|
344
|
+
between frames rather than re-walking it from the root. And where a view does not justify a cut
|
|
345
|
+
at all — a distant cascade, a low-resolution reflection — it should be able to take a proxy
|
|
346
|
+
(§3.2) instead. That choice belongs to the view, not to the geometry.
|
|
345
347
|
|
|
346
348
|
### 3.4 GPU-driven allocation `[V §4.3]`
|
|
347
349
|
|
|
@@ -17,6 +17,24 @@ export class VGeoBuildOptions {
|
|
|
17
17
|
* @type {number}
|
|
18
18
|
*/
|
|
19
19
|
codec: number;
|
|
20
|
+
/**
|
|
21
|
+
* How hard the codec looks for matches, from {@link LZ4_LEVEL}. Nine by default, which is the
|
|
22
|
+
* top of the reference's hash-chain levels.
|
|
23
|
+
*
|
|
24
|
+
* **It changes nothing about the file.** Every level writes the same LZ4 block format, so a
|
|
25
|
+
* container built at any of them reads in every build of the reader that exists; the level is
|
|
26
|
+
* how long the *encoder* spent, and that is all.
|
|
27
|
+
*
|
|
28
|
+
* Nine rather than twelve because the ladder above it is not worth its time on this payload:
|
|
29
|
+
* measured over 2,718 real pages, the whole run from 3 to 12 is 0.077 percentage points of
|
|
30
|
+
* ratio, and 10 to 12 cost 15-40% more time for the last thousandth of it. Nine rather than
|
|
31
|
+
* three for the same reason in the other direction — it is 0.077 points smaller for about 5%
|
|
32
|
+
* more time. Nine rather than one because the fast encoder gives up 2.5% of every page to save
|
|
33
|
+
* compression time that is a couple of percent of a build.
|
|
34
|
+
*
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
codec_level: number;
|
|
20
38
|
/**
|
|
21
39
|
* How close two source vertices have to be to count as one point, as a fraction of the
|
|
22
40
|
* geometry's own bounding-box diagonal. **Leave it alone.** It is the one knob here that can
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VGeoBuildOptions.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VGeoBuildOptions.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH;IACI;;;;;;;;;;OAUG;IACH,OAFU,MAAM,CAEO;IAEvB;;;;;;;;;;;;;;;;OAgBG;IACH,aAFU,MAAM,CAEmB;IAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,YAFU,MAAM,CAEE;IAElB;;;;;;;;;;;;;;OAcG;IACH,mBAFU,MAAM,CAE2B;IAE3C;;OAEG;IACH,eAFU,gBAAgB,CAEa;CAC1C;iCA7FgC,uBAAuB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LZ4_LEVEL } from "../../../../../core/binary/lz4/LZ4_LEVEL.js";
|
|
1
2
|
import { VGEO_CODEC_LZ4 } from "../format/frame/VGEO_CODEC_LZ4.js";
|
|
2
3
|
import { VGEO_DEFAULT_PAGE_SIZE } from "../format/page/VGEO_DEFAULT_PAGE_SIZE.js";
|
|
3
4
|
import { VGeoLevelOptions } from "./VGeoLevelOptions.js";
|
|
@@ -22,6 +23,25 @@ export class VGeoBuildOptions {
|
|
|
22
23
|
*/
|
|
23
24
|
codec = VGEO_CODEC_LZ4;
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* How hard the codec looks for matches, from {@link LZ4_LEVEL}. Nine by default, which is the
|
|
28
|
+
* top of the reference's hash-chain levels.
|
|
29
|
+
*
|
|
30
|
+
* **It changes nothing about the file.** Every level writes the same LZ4 block format, so a
|
|
31
|
+
* container built at any of them reads in every build of the reader that exists; the level is
|
|
32
|
+
* how long the *encoder* spent, and that is all.
|
|
33
|
+
*
|
|
34
|
+
* Nine rather than twelve because the ladder above it is not worth its time on this payload:
|
|
35
|
+
* measured over 2,718 real pages, the whole run from 3 to 12 is 0.077 percentage points of
|
|
36
|
+
* ratio, and 10 to 12 cost 15-40% more time for the last thousandth of it. Nine rather than
|
|
37
|
+
* three for the same reason in the other direction — it is 0.077 points smaller for about 5%
|
|
38
|
+
* more time. Nine rather than one because the fast encoder gives up 2.5% of every page to save
|
|
39
|
+
* compression time that is a couple of percent of a build.
|
|
40
|
+
*
|
|
41
|
+
* @type {number}
|
|
42
|
+
*/
|
|
43
|
+
codec_level = LZ4_LEVEL.HC_DEFAULT;
|
|
44
|
+
|
|
25
45
|
/**
|
|
26
46
|
* How close two source vertices have to be to count as one point, as a fraction of the
|
|
27
47
|
* geometry's own bounding-box diagonal. **Leave it alone.** It is the one knob here that can
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vgeo_assemble_pages.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vgeo_assemble_pages.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js"],"names":[],"mappings":"AAwMA;;;;;;;;;GASG;AACH,qDAPW,sBAAsB,EAAE,iBAExB,YAAY,qBACZ,MAAM,GAEJ,mBAAmB,EAAE,CAiIjC;oCAzUmC,sCAAsC"}
|
|
@@ -4,6 +4,8 @@ import { VGEO_CLUSTER_RECORD_SIZE } from "../format/cluster/VGEO_CLUSTER_RECORD_
|
|
|
4
4
|
import { VGEO_GROUP_RECORD_SIZE } from "../format/page/VGEO_GROUP_RECORD_SIZE.js";
|
|
5
5
|
import { VGEO_NODE_RECORD_SIZE } from "../format/page/VGEO_NODE_RECORD_SIZE.js";
|
|
6
6
|
import { VGEO_PAGE_HEADER_SIZE } from "../format/page/VGEO_PAGE_HEADER_SIZE.js";
|
|
7
|
+
import { VGEO_PARENT_ENTRY_SIZE } from "../format/page/VGEO_PARENT_ENTRY_SIZE.js";
|
|
8
|
+
import { VGEO_PARENT_PAGE_ENTRY_SIZE } from "../format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.js";
|
|
7
9
|
import { VirtualGeometryPage } from "../format/dag/VirtualGeometryPage.js";
|
|
8
10
|
import { vgeo_build_page_nodes } from "./vgeo_build_page_nodes.js";
|
|
9
11
|
import { vgeo_page_node_count } from "./vgeo_page_node_count.js";
|
|
@@ -31,18 +33,59 @@ import { vgeo_page_node_count } from "./vgeo_page_node_count.js";
|
|
|
31
33
|
* @copyright Company Named Limited (c) 2026
|
|
32
34
|
*/
|
|
33
35
|
|
|
36
|
+
/**
|
|
37
|
+
* How many clusters, anywhere in the build, name each group as their child.
|
|
38
|
+
*
|
|
39
|
+
* The same count {@link vgeo_write_container} recomputes as `parent_cluster_count`, needed a step
|
|
40
|
+
* earlier because it is also the length of the group's slice of the page's group-parent table
|
|
41
|
+
* (format §6.3) and therefore part of what a page costs. It is a property of the DAG, which
|
|
42
|
+
* assembly does not change, so counting it here and counting it again at write time cannot
|
|
43
|
+
* disagree.
|
|
44
|
+
*
|
|
45
|
+
* @param {VirtualGeometryGroup[][]} groups_by_level
|
|
46
|
+
* @returns {Map<VirtualGeometryGroup, number>}
|
|
47
|
+
*/
|
|
48
|
+
function count_group_parents(groups_by_level) {
|
|
49
|
+
const counts = new Map();
|
|
50
|
+
|
|
51
|
+
for (const groups of groups_by_level) {
|
|
52
|
+
for (const group of groups) {
|
|
53
|
+
for (const cluster of group.clusters) {
|
|
54
|
+
const child = cluster.child_group;
|
|
55
|
+
|
|
56
|
+
if (child !== undefined) {
|
|
57
|
+
counts.set(child, (counts.get(child) ?? 0) + 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return counts;
|
|
64
|
+
}
|
|
65
|
+
|
|
34
66
|
/**
|
|
35
67
|
* @param {VirtualGeometryGroup} group
|
|
68
|
+
* @param {Map<VirtualGeometryGroup, number>} parent_counts
|
|
36
69
|
* @returns {number} bytes this group adds to a decoded page
|
|
37
70
|
*/
|
|
38
|
-
function group_size_in_page(group) {
|
|
71
|
+
function group_size_in_page(group, parent_counts) {
|
|
39
72
|
let size = VGEO_GROUP_RECORD_SIZE;
|
|
40
73
|
|
|
41
74
|
for (const cluster of group.clusters) {
|
|
42
75
|
size += VGEO_CLUSTER_RECORD_SIZE + cluster.payload.byteLength;
|
|
43
76
|
}
|
|
44
77
|
|
|
45
|
-
|
|
78
|
+
/*
|
|
79
|
+
The group's slice of the group-parent table, plus an upper bound on what it contributes to the
|
|
80
|
+
page's parent-page table. The exact parent-page count is not knowable here — it is how many
|
|
81
|
+
*distinct* pages the parents land in, and which page a parent lands in is what this function is
|
|
82
|
+
helping to decide — so bound it by one page per entry, which no group can exceed. Measured on
|
|
83
|
+
converted assets the real ratio is 17.4 entries to 4.9 pages, so the bound overstates a page's
|
|
84
|
+
parent section by about half of a number that is itself a fifth of a percent of the page.
|
|
85
|
+
*/
|
|
86
|
+
const parents = parent_counts.get(group) ?? 0;
|
|
87
|
+
|
|
88
|
+
return size + parents * (VGEO_PARENT_ENTRY_SIZE + VGEO_PARENT_PAGE_ENTRY_SIZE);
|
|
46
89
|
}
|
|
47
90
|
|
|
48
91
|
/**
|
|
@@ -96,10 +139,11 @@ function page_decoded_size(group_count, content) {
|
|
|
96
139
|
* its own, which is one group and therefore one node.
|
|
97
140
|
*
|
|
98
141
|
* @param {VirtualGeometryGroup} group
|
|
142
|
+
* @param {Map<VirtualGeometryGroup, number>} parent_counts
|
|
99
143
|
* @returns {number}
|
|
100
144
|
*/
|
|
101
|
-
function group_minimum_page_size(group) {
|
|
102
|
-
return page_decoded_size(1, group_size_in_page(group));
|
|
145
|
+
function group_minimum_page_size(group, parent_counts) {
|
|
146
|
+
return page_decoded_size(1, group_size_in_page(group, parent_counts));
|
|
103
147
|
}
|
|
104
148
|
|
|
105
149
|
/**
|
|
@@ -111,8 +155,9 @@ function group_minimum_page_size(group) {
|
|
|
111
155
|
*
|
|
112
156
|
* @param {VirtualGeometryGroup[][]} groups_by_level
|
|
113
157
|
* @param {number} nominal_page_size
|
|
158
|
+
* @param {Map<VirtualGeometryGroup, number>} parent_counts
|
|
114
159
|
*/
|
|
115
|
-
function reject_groups_larger_than_a_page(groups_by_level, nominal_page_size) {
|
|
160
|
+
function reject_groups_larger_than_a_page(groups_by_level, nominal_page_size, parent_counts) {
|
|
116
161
|
let worst = undefined;
|
|
117
162
|
let worst_level = 0;
|
|
118
163
|
let worst_index = 0;
|
|
@@ -125,7 +170,7 @@ function reject_groups_larger_than_a_page(groups_by_level, nominal_page_size) {
|
|
|
125
170
|
groups.forEach((group, index) => {
|
|
126
171
|
total_count++;
|
|
127
172
|
|
|
128
|
-
const needs = group_minimum_page_size(group);
|
|
173
|
+
const needs = group_minimum_page_size(group, parent_counts);
|
|
129
174
|
|
|
130
175
|
if (needs > nominal_page_size) {
|
|
131
176
|
over_count++;
|
|
@@ -167,7 +212,9 @@ export function vgeo_assemble_pages(groups_by_level, object_bounds, nominal_page
|
|
|
167
212
|
assert.isPositiveInteger(nominal_page_size, 'nominal_page_size');
|
|
168
213
|
assert.greaterThan(groups_by_level.length, 0, 'groups_by_level.length');
|
|
169
214
|
|
|
170
|
-
|
|
215
|
+
const parent_counts = count_group_parents(groups_by_level);
|
|
216
|
+
|
|
217
|
+
reject_groups_larger_than_a_page(groups_by_level, nominal_page_size, parent_counts);
|
|
171
218
|
|
|
172
219
|
const pages = [];
|
|
173
220
|
|
|
@@ -214,7 +261,7 @@ export function vgeo_assemble_pages(groups_by_level, object_bounds, nominal_page
|
|
|
214
261
|
for (const group of groups) {
|
|
215
262
|
place(page, group);
|
|
216
263
|
|
|
217
|
-
content += group_size_in_page(group);
|
|
264
|
+
content += group_size_in_page(group, parent_counts);
|
|
218
265
|
}
|
|
219
266
|
|
|
220
267
|
// The cap has no exception, and this is the one page the fill rule below does not get to
|
|
@@ -250,7 +297,7 @@ export function vgeo_assemble_pages(groups_by_level, object_bounds, nominal_page
|
|
|
250
297
|
let content = 0;
|
|
251
298
|
|
|
252
299
|
for (const entry of keyed) {
|
|
253
|
-
const size = group_size_in_page(entry.group);
|
|
300
|
+
const size = group_size_in_page(entry.group, parent_counts);
|
|
254
301
|
|
|
255
302
|
// What the page would come to *with* this group, node table and all — not what it came
|
|
256
303
|
// to without it. The node table grows with the group count, so a page measured before
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vgeo_build.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/vgeo_build.js"],"names":[],"mappings":"AA8GA;;;;;;;;;;;;;;;;;;GAkBG;AACH,2EAFa,eAAe,
|
|
1
|
+
{"version":3,"file":"vgeo_build.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/vgeo_build.js"],"names":[],"mappings":"AA8GA;;;;;;;;;;;;;;;;;;GAkBG;AACH,2EAFa,eAAe,CAyE3B;gCA9L+B,sBAAsB"}
|
|
@@ -168,6 +168,7 @@ export function vgeo_build(geometry, options) {
|
|
|
168
168
|
const write_options = new VGeoWriteOptions();
|
|
169
169
|
|
|
170
170
|
write_options.codec = options.codec;
|
|
171
|
+
write_options.codec_level = options.codec_level;
|
|
171
172
|
|
|
172
173
|
// The table the header carries. A runtime reads it before any cluster record exists to it, and
|
|
173
174
|
// brackets an instance's level range from the errors and radii alone — which is what lets a
|