@woosh/meep-engine 3.25.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.
Files changed (295) hide show
  1. package/package.json +1 -1
  2. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +542 -0
  3. package/src/core/binary/lz4/LZ4_LEVEL.d.ts +33 -0
  4. package/src/core/binary/lz4/LZ4_LEVEL.d.ts.map +1 -0
  5. package/src/core/binary/lz4/LZ4_LEVEL.js +32 -0
  6. package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts +33 -0
  7. package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts.map +1 -0
  8. package/src/core/binary/lz4/lz4_compress_block_at_level.js +47 -0
  9. package/src/core/binary/lz4/lz4_compress_block_hc.d.ts +40 -0
  10. package/src/core/binary/lz4/lz4_compress_block_hc.d.ts.map +1 -0
  11. package/src/core/binary/lz4/lz4_compress_block_hc.js +1382 -0
  12. package/src/core/binary/lz4/lz4_decompress_block.d.ts +2 -1
  13. package/src/core/binary/lz4/lz4_decompress_block.d.ts.map +1 -1
  14. package/src/core/binary/lz4/lz4_decompress_block.js +31 -6
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +66 -0
  16. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  17. package/src/core/geom/3d/shape/HeightMapShape3D.js +314 -20
  18. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  19. package/src/core/geom/3d/shape/PosedShape3D.js +57 -0
  20. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  21. package/src/core/geom/3d/shape/TransformedShape3D.js +102 -0
  22. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  23. package/src/core/geom/3d/shape/UnionShape3D.js +64 -0
  24. package/src/engine/physics/cloth/MEASUREMENTS.md +582 -0
  25. package/src/engine/physics/cloth/PLAN.md +330 -49
  26. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +84 -17
  27. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  28. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +451 -42
  29. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +65 -7
  30. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  31. package/src/engine/physics/cloth/ecs/ClothInstance.js +72 -8
  32. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +25 -4
  33. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  34. package/src/engine/physics/cloth/ecs/ClothSystem.js +75 -8
  35. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +41 -4
  36. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  37. package/src/engine/physics/cloth/ecs/ClothWorld.js +207 -63
  38. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +10 -0
  39. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -1
  40. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +17 -1
  41. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +27 -3
  42. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  43. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +28 -3
  44. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +75 -10
  45. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  46. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +131 -12
  47. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +19 -0
  48. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  49. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +249 -201
  50. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +21 -0
  51. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  52. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +62 -1
  53. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  54. package/src/engine/physics/cloth/ecs/cloth_write_back.js +18 -10
  55. package/src/engine/physics/cloth/playground/collide_main.js +1 -1
  56. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +29 -8
  57. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
  58. package/src/engine/physics/cloth/solver/cloth_contact_find.js +37 -9
  59. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +12 -2
  60. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  61. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +12 -2
  62. package/src/engine/physics/cloth/solver/cloth_step_instance.js +1 -1
  63. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +9 -2
  64. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -1
  65. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +9 -2
  66. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +23 -9
  67. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -1
  68. package/src/engine/physics/cloth/wind/AbstractClothWind.js +23 -9
  69. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +7 -5
  70. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -1
  71. package/src/engine/physics/cloth/wind/ClothAmbientWind.js +12 -9
  72. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -1
  73. package/src/engine/physics/cloth/wind/ClothFluidWind.js +5 -2
  74. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +7 -2
  75. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -1
  76. package/src/engine/physics/cloth/wind/cloth_sample_air.js +12 -5
  77. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  78. package/src/shade/playground/vgeo_runtime/README.md +167 -22
  79. package/src/shade/playground/vgeo_runtime/index.html +112 -62
  80. package/src/shade/playground/vgeo_runtime/main.js +660 -83
  81. package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +1 -1
  82. package/src/shade/playground/vgeo_viewer/sample_asset.js +19 -2
  83. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +11 -0
  84. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  85. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +16 -8
  86. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  87. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +16 -11
  88. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +73 -5
  89. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +26 -15
  90. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +5 -3
  91. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +18 -0
  92. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  93. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +20 -0
  94. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
  95. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +56 -9
  96. package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
  97. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -0
  98. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +22 -0
  99. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  100. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +24 -0
  101. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +12 -0
  102. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
  103. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +13 -0
  104. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +8 -1
  105. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
  106. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +11 -3
  107. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +9 -0
  108. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
  109. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +10 -1
  110. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts +18 -0
  111. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts.map +1 -0
  112. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.js +17 -0
  113. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts +8 -0
  114. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts.map +1 -0
  115. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.js +7 -0
  116. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +3 -0
  117. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -0
  118. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts +14 -0
  119. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts.map +1 -0
  120. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.js +13 -0
  121. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts +11 -0
  122. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts.map +1 -0
  123. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.js +10 -0
  124. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +20 -0
  125. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
  126. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +20 -2
  127. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +4 -0
  128. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
  129. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +4 -0
  130. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +29 -3
  131. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  132. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +81 -122
  133. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +40 -0
  134. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  135. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +44 -0
  136. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +69 -0
  137. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -0
  138. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +236 -0
  139. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +14 -0
  140. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
  141. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +15 -0
  142. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts +31 -0
  143. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts.map +1 -0
  144. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.js +32 -0
  145. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +10 -0
  146. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
  147. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +21 -1
  148. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts +15 -0
  149. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts.map +1 -0
  150. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.js +84 -0
  151. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts +22 -0
  152. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -0
  153. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +96 -0
  154. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  155. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +55 -0
  156. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  157. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +202 -13
  158. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  159. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +147 -10
  160. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +637 -0
  161. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +33 -36
  162. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -1
  163. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +109 -193
  164. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +58 -28
  165. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
  166. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +77 -105
  167. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +37 -0
  168. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +105 -22
  169. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +548 -0
  170. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +29 -50
  171. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
  172. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +47 -489
  173. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts +84 -0
  174. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -0
  175. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +393 -0
  176. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts +234 -0
  177. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -0
  178. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +1363 -0
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts +81 -0
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts.map +1 -0
  181. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.js +89 -0
  182. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +53 -34
  183. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -1
  184. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +407 -124
  185. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts +100 -0
  186. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts.map +1 -0
  187. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.js +219 -0
  188. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts +17 -0
  189. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts.map +1 -0
  190. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.js +47 -0
  191. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts +20 -0
  192. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts.map +1 -0
  193. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.js +19 -0
  194. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -1
  195. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +30 -12
  196. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
  197. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +15 -0
  198. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -1
  199. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +18 -1
  200. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -1
  201. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +5 -15
  202. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts +3 -0
  203. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts.map +1 -0
  204. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.js +77 -0
  205. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts +3 -0
  206. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts.map +1 -0
  207. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +186 -0
  208. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -1
  209. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +20 -8
  210. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
  211. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +580 -188
  212. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +15 -18
  213. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -1
  214. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +19 -28
  215. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
  216. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +45 -25
  217. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
  218. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +11 -0
  219. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
  220. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +0 -1
  221. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +12 -10
  222. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +1 -0
  223. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +8 -0
  224. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +7 -2
  225. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -1
  226. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +8 -3
  227. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +2 -0
  228. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +19 -0
  229. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -1
  230. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +1 -1
  231. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts +5 -0
  232. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts.map +1 -0
  233. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.js +22 -0
  234. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts +14 -0
  235. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts.map +1 -0
  236. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.js +13 -0
  237. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts +17 -0
  238. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts.map +1 -0
  239. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.js +16 -0
  240. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts +15 -0
  241. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts.map +1 -0
  242. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.js +16 -0
  243. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts +23 -0
  244. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts.map +1 -0
  245. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.js +28 -0
  246. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts +2 -0
  247. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts.map +1 -0
  248. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.js +13 -0
  249. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +10 -5
  250. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -1
  251. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +15 -6
  252. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +6 -1
  253. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
  254. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +38 -1
  255. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
  256. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +4 -3
  257. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  258. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  259. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +6 -3
  260. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  261. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +6 -3
  262. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  263. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  264. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -4
  265. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +2 -0
  266. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  267. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
  268. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  269. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +3 -5
  270. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  271. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +3 -4
  272. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  273. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +3 -4
  274. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
  275. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +7 -7
  276. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
  277. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +83 -78
  278. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  279. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +7 -1
  280. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  281. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +7 -1
  282. package/src/shade/renderer/scene/GPUSceneContext.d.ts +22 -2
  283. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  284. package/src/shade/renderer/scene/GPUSceneContext.js +59 -4
  285. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  286. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +92 -16
  287. package/src/shade/renderer/view/GPUViewContext.d.ts +5 -0
  288. package/src/shade/renderer/view/GPUViewContext.d.ts.map +1 -1
  289. package/src/shade/renderer/view/GPUViewContext.js +21 -0
  290. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  291. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +74 -3
  292. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +0 -723
  293. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +0 -50
  294. package/src/shade/renderer/shader/chunk/jobs/NOTES.md +0 -47
  295. 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":"AAyFA;;;;;;;;;;;GAWG;AACH,2CALW,MAAM,UACN,MAAM,cACN,MAAM,GACJ;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAiBtD;+BAzGM,+DAA+D"}
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, b, a + 1);
65
- indices.push(a + 1, b, b + 1);
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":"AAmBA;;;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;;;;;;;;;;;;;;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;;CAuIJ;8BAxzBY,OAAO,uBAAuB,EAAE,eAAe;yBAC/C,OAAO,sDAAsD,EAAE,UAAU;6BAbzD,mCAAmC;uCASzB,6BAA6B"}
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;IAqEI;;;;;;OAMG;IACH;QAJ8B,MAAM;QACR,qBAAqB,GAAtC,MAAM;QACW,yBAAyB,GAA1C,MAAM;OAkChB;IAzFD,6BAEC;IAOD,iCAEC;IAgFD,gBAKC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,sBAHW,MAAM,uBACN,MAAM,QAuHhB;IAqRD;;;;;;;;;;;OAWG;IACH,qBAJW,4BAA4B,GAE1B,OAAO,CAmBnB;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;yBA/4BY,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"}
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
- * @type {MeshletBatchAllocationRecord[]}
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.length;
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 (let i = 0; i < record_count; i++) {
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.push(record);
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
- const index = this.#records.indexOf(record);
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.splice(index, 1);
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.push(record);
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 | 3 today; breaking layout changes only |
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 ] [ Payload ]
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 | [u8; 8] | reserved | |
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 | reserved | |
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 non-selectable)`
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. The parallelism
269
- objection is answered by nvpro's persistent-thread and subgroup-ballot scheme (see the
270
- vk_lod_clusters comparison, §2.4); and the hierarchy it descends exists at two granularities. Pages
271
- form a rooted, acyclic, fully-reachable DAG (§11.3–11.4 of the format), measurably
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 **VG selection buffer** — a VG-owned, densely maintained table of
497
- resident clusters (two (sphere, error) pairs, geometry id, own-page and child-page/group
498
- indices, child-group selectable bit — the page indices are what touch and want emission
499
- stamp), plus per-geometry resident-cluster index tables. The existing metadata buffer is
500
- *not* usable for this (freed slices hold garbage, and it carries no error pairs), which is
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
- Open: shadow views (v1 takes the one cut per view of §4, sharing setup rather than results,
517
- and a proxy where a view does not justify a cut); DRS τ semantics; the traversal's GPU shape —
518
- persistent threads over a queue, per nvpro, against what WebGPU without 64-bit atomics actually
519
- makes affordable.
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. And where a view does not justify a cut at all — a distant
343
- cascade, a low-resolution reflection — it should be able to take a proxy (§3.2) instead. That
344
- choice belongs to the view, not to the geometry.
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":"AAIA;;;;;GAKG;AACH;IACI;;;;;;;;;;OAUG;IACH,OAFU,MAAM,CAEO;IAEvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,YAFU,MAAM,CAEE;IAElB;;;;;;;;;;;;;;OAcG;IACH,mBAFU,MAAM,CAE2B;IAE3C;;OAEG;IACH,eAFU,gBAAgB,CAEa;CAC1C;iCA1EgC,uBAAuB"}
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":"AA2JA;;;;;;;;;GASG;AACH,qDAPW,sBAAsB,EAAE,iBAExB,YAAY,qBACZ,MAAM,GAEJ,mBAAmB,EAAE,CA+HjC;oCA5RmC,sCAAsC"}
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
- return size;
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
- reject_groups_larger_than_a_page(groups_by_level, nominal_page_size);
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,CAwE3B;gCA7L+B,sBAAsB"}
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