@woosh/meep-engine 3.24.0 → 3.26.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 (349) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +59 -0
  3. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  4. package/src/core/geom/3d/shape/HeightMapShape3D.js +285 -0
  5. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  6. package/src/core/geom/3d/shape/PosedShape3D.js +57 -0
  7. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  8. package/src/core/geom/3d/shape/TransformedShape3D.js +102 -0
  9. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  10. package/src/core/geom/3d/shape/UnionShape3D.js +64 -0
  11. package/src/engine/graphics3/PickingSystem.d.ts +3 -3
  12. package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
  13. package/src/engine/graphics3/PickingSystem.js +52 -55
  14. package/src/engine/graphics3/shader_picking_gather.d.ts +40 -0
  15. package/src/engine/graphics3/shader_picking_gather.d.ts.map +1 -0
  16. package/src/engine/graphics3/shader_picking_gather.js +120 -0
  17. package/src/engine/physics/cloth/MEASUREMENTS.md +1632 -0
  18. package/src/engine/physics/cloth/PLAN.md +738 -95
  19. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +84 -17
  20. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  21. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +451 -42
  22. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts +52 -0
  23. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts.map +1 -0
  24. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.js +70 -0
  25. package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts +12 -3
  26. package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
  27. package/src/engine/physics/cloth/ecs/ClothDynamics.js +340 -331
  28. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +101 -29
  29. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  30. package/src/engine/physics/cloth/ecs/ClothInstance.js +110 -31
  31. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts +84 -0
  32. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts.map +1 -0
  33. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.js +141 -0
  34. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +71 -81
  35. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  36. package/src/engine/physics/cloth/ecs/ClothSystem.js +135 -856
  37. package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts +48 -0
  38. package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts.map +1 -0
  39. package/src/engine/physics/cloth/ecs/ClothWorkerCore.js +107 -0
  40. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +323 -0
  41. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -0
  42. package/src/engine/physics/cloth/ecs/ClothWorld.js +1248 -0
  43. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +192 -0
  44. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -0
  45. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +578 -0
  46. package/src/engine/physics/cloth/ecs/cloth.worker.d.ts +2 -0
  47. package/src/engine/physics/cloth/ecs/cloth.worker.d.ts.map +1 -0
  48. package/src/engine/physics/cloth/ecs/cloth.worker.js +12 -0
  49. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  50. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +2 -1
  51. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +85 -3
  52. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  53. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +366 -271
  54. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +95 -12
  55. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  56. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +170 -27
  57. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +19 -0
  58. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  59. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +249 -201
  60. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +21 -0
  61. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
  62. package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +62 -1
  63. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts +29 -0
  64. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts.map +1 -0
  65. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.js +78 -0
  66. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
  67. package/src/engine/physics/cloth/ecs/cloth_write_back.js +18 -10
  68. package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts +17 -0
  69. package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts.map +1 -0
  70. package/src/engine/physics/cloth/ecs/makeClothWorker.js +18 -0
  71. package/src/engine/physics/cloth/playground/README.md +73 -2
  72. package/src/engine/physics/cloth/playground/collide_main.js +1 -1
  73. package/src/engine/physics/cloth/playground/wind.html +166 -0
  74. package/src/engine/physics/cloth/playground/wind_build.d.ts +81 -0
  75. package/src/engine/physics/cloth/playground/wind_build.d.ts.map +1 -0
  76. package/src/engine/physics/cloth/playground/wind_build.js +141 -0
  77. package/src/engine/physics/cloth/playground/wind_main.d.ts +2 -0
  78. package/src/engine/physics/cloth/playground/wind_main.d.ts.map +1 -0
  79. package/src/engine/physics/cloth/playground/wind_main.js +681 -0
  80. package/src/engine/physics/cloth/solver/ClothState.d.ts +134 -79
  81. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  82. package/src/engine/physics/cloth/solver/ClothState.js +273 -12
  83. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +29 -8
  84. package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
  85. package/src/engine/physics/cloth/solver/cloth_contact_find.js +37 -9
  86. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +45 -2
  87. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  88. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +46 -2
  89. package/src/engine/physics/cloth/solver/cloth_step.d.ts +21 -2
  90. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  91. package/src/engine/physics/cloth/solver/cloth_step.js +173 -4
  92. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +40 -0
  93. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -0
  94. package/src/engine/physics/cloth/solver/cloth_step_instance.js +284 -0
  95. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts +107 -0
  96. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts.map +1 -0
  97. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.js +263 -0
  98. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +9 -2
  99. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -1
  100. package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +9 -2
  101. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +102 -0
  102. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -0
  103. package/src/engine/physics/cloth/wind/AbstractClothWind.js +114 -0
  104. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +87 -0
  105. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -0
  106. package/src/engine/physics/cloth/wind/ClothAmbientWind.js +185 -0
  107. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +79 -0
  108. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -0
  109. package/src/engine/physics/cloth/wind/ClothFluidWind.js +213 -0
  110. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +106 -0
  111. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -0
  112. package/src/engine/physics/cloth/wind/cloth_sample_air.js +297 -0
  113. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  114. package/src/shade/device/mock/texture_copy.d.ts.map +1 -1
  115. package/src/shade/device/mock/texture_copy.js +28 -0
  116. package/src/shade/playground/vgeo_runtime/README.md +94 -0
  117. package/src/shade/playground/vgeo_runtime/index.html +62 -0
  118. package/src/shade/playground/vgeo_runtime/main.d.ts +2 -0
  119. package/src/shade/playground/vgeo_runtime/main.d.ts.map +1 -0
  120. package/src/shade/playground/vgeo_runtime/main.js +399 -0
  121. package/src/shade/playground/vgeo_viewer/sample_asset.js +1 -1
  122. package/src/shade/renderer/camera/Camera.d.ts +14 -0
  123. package/src/shade/renderer/camera/Camera.d.ts.map +1 -1
  124. package/src/shade/renderer/camera/Camera.js +7 -1
  125. package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.d.ts.map +1 -1
  126. package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.js +16 -1
  127. package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
  128. package/src/shade/renderer/geometry/GPUGeometryManager.js +29 -9
  129. package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts +10 -0
  130. package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts.map +1 -1
  131. package/src/shade/renderer/geometry/GPUGeometryMetadata.js +13 -0
  132. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +46 -18
  133. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +70 -22
  134. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +97 -19
  135. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +12 -2
  136. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  137. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +14 -3
  138. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +2 -2
  139. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
  140. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +124 -6
  141. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +22 -0
  142. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +1 -0
  143. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.js +53 -0
  144. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +8 -1
  145. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  146. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +8 -1
  147. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts +16 -0
  148. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts.map +1 -0
  149. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.js +15 -0
  150. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts +54 -0
  151. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts.map +1 -0
  152. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.js +53 -0
  153. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +13 -2
  154. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
  155. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +14 -2
  156. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +100 -0
  157. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  158. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +113 -0
  159. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
  160. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +5 -0
  161. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  162. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +108 -0
  163. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  164. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +23 -0
  165. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  166. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +40 -0
  167. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +228 -0
  168. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -0
  169. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +932 -0
  170. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +176 -0
  171. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -0
  172. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +425 -0
  173. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +241 -0
  174. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +574 -0
  175. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +120 -0
  176. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -0
  177. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +674 -0
  178. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +287 -0
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -0
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +976 -0
  181. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +42 -0
  182. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -0
  183. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +96 -0
  184. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts +16 -0
  185. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts.map +1 -0
  186. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.js +15 -0
  187. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts +19 -0
  188. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -0
  189. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +83 -0
  190. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts +9 -0
  191. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts.map +1 -0
  192. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.js +8 -0
  193. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +19 -0
  194. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -0
  195. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +160 -0
  196. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts +3 -0
  197. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -0
  198. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +255 -0
  199. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts +3 -0
  200. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -0
  201. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +80 -0
  202. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts +3 -0
  203. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -0
  204. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +144 -0
  205. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts +3 -0
  206. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -0
  207. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +530 -0
  208. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +39 -0
  209. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -0
  210. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +54 -0
  211. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts +41 -0
  212. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts.map +1 -0
  213. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.js +56 -0
  214. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts +49 -0
  215. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -0
  216. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +328 -0
  217. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +10 -0
  218. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts.map +1 -0
  219. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +51 -0
  220. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +13 -0
  221. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -0
  222. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +12 -0
  223. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +17 -0
  224. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts.map +1 -0
  225. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +99 -0
  226. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts +9 -0
  227. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts.map +1 -0
  228. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +8 -0
  229. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +7 -0
  230. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts.map +1 -0
  231. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +27 -0
  232. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts +14 -0
  233. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts.map +1 -0
  234. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js +13 -0
  235. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts +7 -0
  236. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts.map +1 -0
  237. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.js +35 -0
  238. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts +9 -0
  239. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts.map +1 -0
  240. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.js +8 -0
  241. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts +15 -0
  242. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts.map +1 -0
  243. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.js +14 -0
  244. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +7 -0
  245. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts.map +1 -0
  246. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +36 -0
  247. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +10 -0
  248. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -0
  249. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +9 -0
  250. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts +11 -0
  251. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts.map +1 -0
  252. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.js +54 -0
  253. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts +9 -0
  254. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts.map +1 -0
  255. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.js +8 -0
  256. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts +11 -0
  257. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts.map +1 -0
  258. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.js +10 -0
  259. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts +7 -0
  260. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts.map +1 -0
  261. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.js +30 -0
  262. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts +9 -0
  263. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts.map +1 -0
  264. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.js +8 -0
  265. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts +13 -0
  266. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts.map +1 -0
  267. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.js +12 -0
  268. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts +14 -0
  269. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts.map +1 -0
  270. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.js +13 -0
  271. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +11 -0
  272. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts.map +1 -0
  273. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +61 -0
  274. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts +9 -0
  275. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -0
  276. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +8 -0
  277. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts +13 -0
  278. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts.map +1 -0
  279. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.js +12 -0
  280. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts +9 -0
  281. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts.map +1 -0
  282. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.js +8 -0
  283. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts +20 -0
  284. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts.map +1 -0
  285. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.js +19 -0
  286. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts +23 -0
  287. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -0
  288. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +70 -0
  289. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +18 -0
  290. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -0
  291. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +25 -0
  292. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +35 -0
  293. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -0
  294. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +41 -0
  295. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +32 -0
  296. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -0
  297. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +138 -0
  298. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  299. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  300. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  301. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +9 -0
  302. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  303. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +12 -1
  304. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  305. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +12 -1
  306. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  307. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +10 -0
  308. package/src/shade/renderer/scene/GPUSceneContext.d.ts +18 -0
  309. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  310. package/src/shade/renderer/scene/GPUSceneContext.js +647 -594
  311. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
  312. package/src/shade/renderer/scene/rows/GPUSceneRows.js +22 -0
  313. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts +35 -0
  314. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts.map +1 -0
  315. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.js +91 -0
  316. package/src/engine/graphics3/instantiate_scene_bundle.d.ts +0 -54
  317. package/src/engine/graphics3/instantiate_scene_bundle.d.ts.map +0 -1
  318. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +0 -39
  319. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts.map +0 -1
  320. package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts +0 -35
  321. package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts.map +0 -1
  322. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts +0 -96
  323. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts.map +0 -1
  324. package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts +0 -20
  325. package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts.map +0 -1
  326. package/src/shade/renderer/animation/skin_bind_transform.d.ts +0 -25
  327. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +0 -1
  328. package/src/shade/renderer/animation/skin_frame_defect.d.ts +0 -34
  329. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +0 -1
  330. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +0 -24
  331. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +0 -1
  332. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +0 -70
  333. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +0 -1
  334. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +0 -96
  335. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +0 -1
  336. package/src/shade/renderer/scene/InstanceBatch.d.ts +0 -92
  337. package/src/shade/renderer/scene/InstanceBatch.d.ts.map +0 -1
  338. package/src/shade/renderer/scene/Mesh.d.ts +0 -55
  339. package/src/shade/renderer/scene/Mesh.d.ts.map +0 -1
  340. package/src/shade/renderer/scene/Node3D.d.ts +0 -159
  341. package/src/shade/renderer/scene/Node3D.d.ts.map +0 -1
  342. package/src/shade/renderer/scene/Node3DDirtyList.d.ts +0 -59
  343. package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +0 -1
  344. package/src/shade/renderer/scene/SceneNodeBinding.d.ts +0 -52
  345. package/src/shade/renderer/scene/SceneNodeBinding.d.ts.map +0 -1
  346. package/src/shade/renderer/scene/SkinnedMesh.d.ts +0 -27
  347. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +0 -1
  348. package/src/shade/renderer/scene/TransformAuthority.d.ts +0 -19
  349. package/src/shade/renderer/scene/TransformAuthority.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GPUGeometryManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/geometry/GPUGeometryManager.js"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH;IAqJI;;;OAGG;IACH,+BAUC;IA7JD;;;OAGG;IACH,iBAFU,YAAU,IAAI,CAED;IA0CvB;;;OAGG;IACH,4GAEC;IAED;;;;OAIG;IACH,8BAA0B;IAE1B;;;;OAIG;IACH,uBAAsB;IAOtB;;;OAGG;IACH,kCAEC;IAwBD;;;OAGG;IACH,kCAEC;IAGD;;;OAGG;IACH,0EAEC;IAED;;;OAGG;IACH,+BAiBC;IAkBD;;;;OAIG;IACH,qBAHW,mBAAmB,GAClB,MAAM,CAMjB;IAED;;;;OAIG;IACH,cAHW,eAAe,GACd,OAAO,CAIlB;IAGD;;;;OAIG;IACH,cAHW,eAAe,GACb,OAAO,CAiEnB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,2BAPW,eAAe,GACb,mBAAmB,CAiC/B;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,uBAHW,mBAAmB,GACjB,OAAO,CAqBnB;IAED;;;;;;;OAOG;IACH,uBAHW,eAAe,GACb,mBAAmB,GAAC,SAAS,CAIzC;IAED;;;;OAIG;IACH,iBAHW,eAAe,GACb,OAAO,CAsCnB;IAyBD,gBAWC;IAED;;;;;;OAMG;IACH,qBANW,sBAAsB,QAuChC;IAmDD,cAmCC;IAED,0CAWC;;CA+BJ;qCArnBY,OAAO,wCAAwC,EAAE,sBAAsB;8BACvE,OAAO,sBAAsB,EAAE,eAAe;oCAC9C,OAAO,gCAAgC,EAAE,qBAAqB;oCANvC,0BAA0B;kCAC5B,gCAAgC;sCAH5B,gCAAgC"}
1
+ {"version":3,"file":"GPUGeometryManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/geometry/GPUGeometryManager.js"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH;IAqJI;;;OAGG;IACH,+BAUC;IA7JD;;;OAGG;IACH,iBAFU,YAAU,IAAI,CAED;IA0CvB;;;OAGG;IACH,4GAEC;IAED;;;;OAIG;IACH,8BAA0B;IAE1B;;;;OAIG;IACH,uBAAsB;IAOtB;;;OAGG;IACH,kCAEC;IAwBD;;;OAGG;IACH,kCAEC;IAGD;;;OAGG;IACH,0EAEC;IAED;;;OAGG;IACH,+BAiBC;IAkBD;;;;OAIG;IACH,qBAHW,mBAAmB,GAClB,MAAM,CAMjB;IAED;;;;OAIG;IACH,cAHW,eAAe,GACd,OAAO,CAIlB;IAGD;;;;OAIG;IACH,cAHW,eAAe,GACb,OAAO,CA+EnB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,2BAPW,eAAe,GACb,mBAAmB,CAiC/B;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,uBAHW,mBAAmB,GACjB,OAAO,CAqBnB;IAED;;;;;;;OAOG;IACH,uBAHW,eAAe,GACb,mBAAmB,GAAC,SAAS,CAIzC;IAED;;;;OAIG;IACH,iBAHW,eAAe,GACb,OAAO,CAsCnB;IAyBD,gBAWC;IAED;;;;;;OAMG;IACH,qBANW,sBAAsB,QAuChC;IAmDD,cAmCC;IAED,0CAWC;;CAqCJ;qCAzoBY,OAAO,wCAAwC,EAAE,sBAAsB;8BACvE,OAAO,sBAAsB,EAAE,eAAe;oCAC9C,OAAO,gCAAgC,EAAE,qBAAqB;oCANvC,0BAA0B;kCAC5B,gCAAgC;sCAH5B,gCAAgC"}
@@ -250,18 +250,32 @@ export class GPUGeometryManager {
250
250
  metadata.allocated = false;
251
251
  metadata.index = this.__last_geometry_index++;
252
252
 
253
- const meshlets = geometry.meshlets;
253
+ if (geometry.isVirtualGeometry === true) {
254
+ /*
255
+ A virtual geometry has no meshlets to allocate and no tree to trace. Everything it
256
+ draws comes from a cut selected per view per frame, out of pages
257
+ `GPUVirtualGeometryPool` installed into the *shared* meshlet pool — so its clusters are
258
+ in the same buffers as everything else, and only the route to them differs.
254
259
 
255
- metadata.meshlets = this.#meshlets.add_batch(meshlets);
260
+ The row still carries the source's bounds and triangle count, which is what
261
+ instance-level culling reads and what makes a VG instance an ordinary instance
262
+ everywhere above this line.
263
+ */
264
+ metadata.vg_row = geometry.vg_row;
265
+ } else {
266
+ const meshlets = geometry.meshlets;
256
267
 
257
- // watch for data migration
258
- metadata.meshlets.changed.add(() => {
268
+ metadata.meshlets = this.#meshlets.add_batch(meshlets);
259
269
 
260
- // TODO need to handle this properly
270
+ // watch for data migration
271
+ metadata.meshlets.changed.add(() => {
261
272
 
262
- console.warn('Meshlet GPU address changed.')
273
+ // TODO need to handle this properly
263
274
 
264
- });
275
+ console.warn('Meshlet GPU address changed.')
276
+
277
+ });
278
+ }
265
279
 
266
280
  this.#geometries.set(geometry, metadata);
267
281
 
@@ -634,8 +648,14 @@ export class GPUGeometryManager {
634
648
  index_count: indexCount,
635
649
  bounding_sphere: geometry.bounding_sphere,
636
650
  bounding_box: geometry.bounding_box,
637
- meshlets_address: g_meta.meshlets.metadata.offset,
638
- meshlets_count: g_meta.meshlets.meshlet_count,
651
+ /*
652
+ Zero and zero for a virtual geometry, and the zero count is load-bearing rather than
653
+ defensive: `shader_meshes_to_meshlet_counts` reads it and therefore contributes nothing
654
+ for a VG instance without knowing what one is.
655
+ */
656
+ meshlets_address: g_meta.meshlets?.metadata.offset ?? 0,
657
+ meshlets_count: g_meta.meshlets?.meshlet_count ?? 0,
658
+ vg_row: g_meta.vg_row,
639
659
  }, GEOMETRY_METADATA_STRUCT, metadata.buffer, offset);
640
660
 
641
661
  }
@@ -24,6 +24,16 @@ export class GPUGeometryMetadata {
24
24
  * @type {number}
25
25
  */
26
26
  references: number;
27
+ /**
28
+ * Row in `GPUVirtualGeometryPool`'s geometry table, or {@link VGEO_GEOMETRY_ROW_NONE}.
29
+ *
30
+ * Set for a {@link VirtualGeometry} and for nothing else. It travels into the GPU row so a pass
31
+ * handed a list of visible instances can tell a virtual one from a static one — which is the
32
+ * whole of the seam, on this side of it.
33
+ *
34
+ * @type {number}
35
+ */
36
+ vg_row: number;
27
37
  allocated: boolean;
28
38
  }
29
39
  export type MeshletGeometry = import("./MeshletGeometry.js").MeshletGeometry;
@@ -1 +1 @@
1
- {"version":3,"file":"GPUGeometryMetadata.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/geometry/GPUGeometryMetadata.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;IACI;;OAEG;IACH,UAFU,eAAe,CAEjB;IAER;;;OAGG;IACH,OAFU,MAAM,CAEN;IAEV;;OAEG;IACH,uCAAS;IAET;;;;;;;OAOG;IACH,YAFU,MAAM,CAED;IAEf,mBAAkB;CACrB;8BA/BY,OAAO,sBAAsB,EAAE,eAAe"}
1
+ {"version":3,"file":"GPUGeometryMetadata.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/geometry/GPUGeometryMetadata.js"],"names":[],"mappings":"AAEA;;GAEG;AAEH;IACI;;OAEG;IACH,UAFU,eAAe,CAEjB;IAER;;;OAGG;IACH,OAFU,MAAM,CAEN;IAEV;;OAEG;IACH,uCAAS;IAET;;;;;;;OAOG;IACH,YAFU,MAAM,CAED;IAEf;;;;;;;;OAQG;IACH,QAFU,MAAM,CAEgB;IAEhC,mBAAkB;CACrB;8BA1CY,OAAO,sBAAsB,EAAE,eAAe"}
@@ -1,3 +1,5 @@
1
+ import { VGEO_GEOMETRY_ROW_NONE } from "./virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js";
2
+
1
3
  /**
2
4
  * @typedef {import("./MeshletGeometry.js").MeshletGeometry} MeshletGeometry
3
5
  */
@@ -29,5 +31,16 @@ export class GPUGeometryMetadata {
29
31
  */
30
32
  references = 0;
31
33
 
34
+ /**
35
+ * Row in `GPUVirtualGeometryPool`'s geometry table, or {@link VGEO_GEOMETRY_ROW_NONE}.
36
+ *
37
+ * Set for a {@link VirtualGeometry} and for nothing else. It travels into the GPU row so a pass
38
+ * handed a list of visible instances can tell a virtual one from a static one — which is the
39
+ * whole of the seam, on this side of it.
40
+ *
41
+ * @type {number}
42
+ */
43
+ vg_row = VGEO_GEOMETRY_ROW_NONE;
44
+
32
45
  allocated = false;
33
46
  }
@@ -18,9 +18,16 @@ rather than baked in.
18
18
  implements the *mechanics* of §10 — cold start, one fetch per page from the byte range its parent
19
19
  wrote down, want dedup, activation, eviction, and the mandatory reject rules of §4 and §5. It
20
20
  implements none of the *policy*: no fetch scheduler, no budgets, no touch-LRU, no quarantine, and
21
- the pinning rules are left to whoever manages residency. It also decodes neither the node table
22
- (§6.1) nor the child-table traversal metric (§5, §6.2) — `VGeoPage` records `node_count` and stops
23
- — because nothing consumes them yet; a GPU cut pass is the first thing that would.
21
+ the pinning rules are left to whoever manages residency.
22
+
23
+ It decodes the **node table** (§6.1) — the GPU cut pass is the consumer that was predicted here and
24
+ it arrived; the node metric's `max_parent_error` is what lets a whole subtree be skipped without
25
+ touching a cluster. It decodes the **child-table traversal metric** (§5, §6.2) as well, because a
26
+ reader that decodes two thirds of a structure is worse than one that decodes all of it — but no
27
+ consumer reads it, and `runtime/RUNTIME_PLAN.md` §4.8 says why: at the moment a want is emitted, the
28
+ per-cluster test the cut has already passed is strictly tighter than any page-level test, so the
29
+ page-level one can never reject anything. Its consumer is prefetch, which is a later campaign.
30
+
24
31
  `shade/playground/vgeo_viewer/` drives the reader end to end, and its `select_cut.js` is a CPU
25
32
  reference traversal of the cut rule this format was shaped around.
26
33
 
@@ -115,7 +122,7 @@ All u64/u128 fields are naturally 8-byte aligned.
115
122
  | 10 | u8 | hash_algo | 0 = xxHash3; readers MUST reject unknown values |
116
123
  | 11 | u8 | reserved | |
117
124
  | 12 | u32 | feature_flags_required | readers MUST reject any unknown set bit |
118
- | 16 | u32 | feature_flags_ignorable | readers MUST ignore unknown set bits |
125
+ | 16 | u32 | feature_flags_ignorable | readers MUST ignore unknown set bits; bit 0 = every page is within `nominal_page_size` (§11.14) |
119
126
  | 20 | u32 | payload_encoding | 1 = `SHADE_MESHLET_V1` (§8); readers MUST reject unknown values |
120
127
  | 24 | [u8; 16] | asset_id | xxh3-128 of the whole file with every **derived checksum field** zeroed (see below); stored low64 LE at bytes 0–7, high64 LE at bytes 8–15 |
121
128
  | 40 | u32 | vertex_attribute_mask | advisory attribute presence (§8 bit table) |
@@ -127,7 +134,7 @@ All u64/u128 fields are naturally 8-byte aligned.
127
134
  | 76 | u32 | root_page_decoded_size | RAM admission for the always-resident set, known from the header alone |
128
135
  | 80 | u64 | directory_offset | also the exact end of the frame region |
129
136
  | 88 | u64 | dir_block_checksums_offset | `ceil(total_pages / 128)` × u64 |
130
- | 96 | u32 | nominal_page_size | build parameter, bytes; informational — readers MUST NOT assume (§11.5 exception) |
137
+ | 96 | u32 | nominal_page_size | bytes a decoded page may come to; a **hard bound** when bit 0 of `feature_flags_ignorable` is set (§11.14), advisory otherwise |
131
138
  | 100 | u16 | reserved | |
132
139
  | 102 | u8 | level_count | levels `0..level_count-1` |
133
140
  | 103 | u8 | reserved | |
@@ -492,8 +499,8 @@ about a scene and not about a container.
492
499
  always-resident set is small: invariant §11.7 puts exactly one root cluster in the file and the
493
500
  assembler gives the root page the complete topmost level, so a measured root page is one cluster —
494
501
  1,732 B and 1,472 B on two torus builds, header and level table included coming to about 2.4 KB.
495
- A prefix past that is speculation about the *next* band, and at the hundreds-of-KB pages design §5
496
- targets it buys one page. `VGeoReadOptions.head_prefix_bytes` therefore defaults to 64 KiB rather
502
+ A prefix past that is speculation about the *next* band, and at the 64 KiB default page size it
503
+ buys about one page. `VGeoReadOptions.head_prefix_bytes` therefore defaults to 64 KiB rather
497
504
  than the 256 KiB it did, and the reader **releases the prefix** the first time a read falls
498
505
  outside it (`VGeoContainerReader.release_head`, and `head_bytes` to see it). Measured, at the old
499
506
  default a 256 KiB prefix over a 22 MB container covered 105 of its 4,000 pages and served nothing
@@ -537,12 +544,14 @@ about a scene and not about a container.
537
544
  child installs — known at queue time, purely local. Arrivals verify the
538
545
  identity-seeded frame checksum; activation makes any arrival order safe beyond that.
539
546
  The reader dedups in-flight fetches by page index but pins nothing.
540
- Still open, and now for a nameable reason rather than for want of an owner: a `VGeoPageWant`
541
- carries the page it names and not the page it was *discovered through*, so neither the reader nor
542
- the manager can pin P without the cut telling them which P it was. It is an efficiency item and not
543
- a correctness one — evicting P while its child is on the wire wastes the fetch, because the child
544
- installs non-selectable and activation is safe at every arrival order — and closing it means adding
545
- the source page to the want, which is a change to the cut as much as to the reader.
547
+ Still open, and the blocker named here is gone. It was that a `VGeoPageWant` carried the page it
548
+ named and not the page it was *discovered through*, so neither the reader nor the manager could
549
+ identify P — the format stores parent counts rather than parent references, deliberately, so
550
+ nothing below the cut can name one. The cut says it: a want record is
551
+ `(source_page_row, child_ref, child_page_index, priority)` and the source row is P. Nothing pins
552
+ anything yet, and that is the whole of what remains. It is an efficiency item and not a
553
+ correctness one — evicting P while its child is on the wire wastes the fetch, because the child
554
+ installs non-selectable and activation is safe at every arrival order.
546
555
  - **Eviction** *(mechanics shipped in the reader, policy shipped above it)*: within each residency tier, a page is
547
556
  evictable only while it has no resident child pages in that tier — checked against its own
548
557
  child table (`SAME_PAGE` references never pin). Leaves-first order emerges from the rule;
@@ -619,8 +628,12 @@ about a scene and not about a container.
619
628
  lives within bands — the within-band spatial curve (§12) is what carries fetch
620
629
  locality and must be chosen with that as a criterion. Fetching through small unwanted
621
630
  gaps is permitted under an implementation waste threshold.
622
- - *(shipped)* Never assume `nominal_page_size`; per-page sizes come from child tables
623
- (traversal), frames (prefix scan), or the directory (special tasks).
631
+ - *(shipped)* Take a page's *actual* size from child tables (traversal), frames (prefix scan), or
632
+ the directory (special tasks) — never from `nominal_page_size`, which is an upper bound and not a
633
+ size. The bound itself may be relied on, and only when bit 0 of `feature_flags_ignorable` is set:
634
+ then no page exceeds it (§11.14) and a reader may size fixed-stride page slots against it. A
635
+ reader that depends on that MUST check the bit and refuse a container without it — containers
636
+ written before the bound existed carry a `nominal_page_size` that pages ran over.
624
637
  - *(shipped)* **Nothing a runtime holds may be able to read the whole file.** The whole-file audit
625
638
  (§11, every checksum, the directory against a frame scan) and the install-every-page sweep are in
626
639
  `format/read/tool/` as `vgeo_audit_container` and `vgeo_install_all_pages`, not on the reader —
@@ -672,8 +685,7 @@ clustering and simplification dominate regardless.
672
685
  design §3 grouping is adaptive from ~4). `parent_cluster_count` equals the exact
673
686
  number of clusters (across all pages, `SAME_PAGE` included) whose
674
687
  `child_ref/child_group` name this group; it is 0 exactly for the root page's
675
- topmost-level groups. A page holding one indivisible oversized group MAY exceed
676
- `nominal_page_size`; the hard bound is the frame's u32 `stored_size`.
688
+ topmost-level groups.
677
689
  6. `1 ≤ triangle_count ≤ 128`, `3 ≤ vertex_count ≤ 128` (Shade meshlet limits);
678
690
  `total_clusters ≥ 1`; `source_vertex_count ≥ 3`, `source_face_count ≥ 1`.
679
691
  7. Error/sphere monotonicity per design §4; `self_error = 0` and `child_ref = NO_CHILD`
@@ -709,6 +721,18 @@ clustering and simplification dominate regardless.
709
721
  13. `root_page_offset = align16(256 + 32 · level_count)`; the level table's `cluster_count` and
710
722
  `group_count` columns sum to `total_clusters` and `total_groups`; every level holds at least
711
723
  one cluster and one group; `min_error ≤ max_error`, both finite and ≥ 0.
724
+ 14. **Page size is a bound.** When bit 0 of `feature_flags_ignorable` is set, every page's
725
+ `decoded_size` is ≤ `nominal_page_size`, and `nominal_page_size` is non-zero. The bit is what a
726
+ reader checks; unset, `nominal_page_size` is the advisory build parameter it used to be and
727
+ nothing here applies.
728
+
729
+ The bound is what makes a page slot a fixed stride in VRAM — a slot address is a multiply, with
730
+ no allocator, no growth path and no address migration behind it (design §6). It costs the
731
+ internal fragmentation of each band's last page, measured at about 9% of a page across a
732
+ 254-page build, and it is kept by splitting a band into more pages. What it cannot be kept by is
733
+ splitting a **group**: §11.1 makes a group atomic to its page. So a build whose clusters group
734
+ into more bytes than a page holds has no layout, and the builder refuses it naming the page size
735
+ that would work, rather than writing a page over the size the header states.
712
736
 
713
737
  ## 12. Open
714
738
 
@@ -717,7 +741,11 @@ clustering and simplification dominate regardless.
717
741
  the tables) is a per-frame `codec` id away and costs no reader that already handles a
718
742
  mixture. The quantized-position `payload_encoding` is the other half of the same
719
743
  question, and would take most of what LZ4 currently finds.
720
- - Default `nominal_page_size` (design §5: order hundreds of KB, measured).
744
+ - ~~Default `nominal_page_size`~~ — 64 KiB, `VGEO_DEFAULT_PAGE_SIZE`: one page size rather than
745
+ Nanite's two, set between hiding fetch latency and paying for it in response time and in VRAM per
746
+ resident page on a low-end device, with a floor at the largest group a build produces. Provisional
747
+ — reopen it on a device measurement, and when quantization lands, since that moves what a cluster
748
+ costs.
721
749
  - Spatial ordering curve within a level band (Morton over group centroids is the
722
750
  default candidate; chosen with fetch-coalescing locality as an explicit criterion,
723
751
  §10).
@@ -1,19 +1,27 @@
1
1
  # Virtual Geometry — Design
2
2
 
3
- Status: **builder, container and reader implemented; the GPU runtime is not**. This document
4
- pins only what is certain; everything else is listed explicitly under *Open*. Runtime home is
5
- this directory (`shade/renderer/geometry/virtual/`, mirroring `texture/virtual/`); builder
6
- lives with `core/geom/3d/topology/struct/binary/` and `core/graph/metis/`.
3
+ Status: **builder, container, reader and GPU runtime implemented; the runtime is under review and
4
+ not shippable** — see [runtime/REVIEW_LEDGER.md](./runtime/REVIEW_LEDGER.md), which found seven
5
+ critical defects including one that breaks §4's no-double-draw guarantee on the ordinary streaming
6
+ path. §10's M1 is written below as closed; treat it as *built and not yet trustworthy*. This document pins only what
7
+ is certain; everything else is listed explicitly under *Open*. Runtime home is this directory
8
+ (`shade/renderer/geometry/virtual/`, mirroring `texture/virtual/`); builder lives with
9
+ `core/geom/3d/topology/struct/binary/` and `core/graph/metis/`.
7
10
 
8
11
  What exists, in the order a newcomer meets it: `build/` turns a `Geometry` into a cluster DAG,
9
12
  `format/` writes and validates the `.vgeo` container ([VGEO_FORMAT.md](./VGEO_FORMAT.md)),
10
- `format/read/` reads one back a page at a time with residency and activation, and
11
- `shade/playground/vgeo_viewer/` puts a clamped CPU cut of a *streaming* container on screen.
12
- What does not exist is everything §7 describes: the selection buffer, the compute cut pass, the
13
- VRAM page pool, and the seam through which VG instances enter the meshlet pipeline. The
14
- playground draws through Shade's direct geometry path instead, which is exactly why it proves
15
- the DAG and not the pipeline. §10 says which milestone that leaves us in, and the order the work
16
- actually arrived in.
13
+ `format/read/` reads one back a page at a time with residency and activation, `residency/` owns the
14
+ RAM tier's budget, and `runtime/` is everything §7 describes — the selection buffer, the compute cut
15
+ pass, the VRAM page pool, the feedback loop, and the seam through which VG instances enter the
16
+ meshlet pipeline ([runtime/RUNTIME_PLAN.md](./runtime/RUNTIME_PLAN.md)).
17
+
18
+ Two playgrounds prove two different things and it is worth not confusing them.
19
+ `shade/playground/vgeo_viewer/` puts a clamped **CPU** cut of a streaming container on screen
20
+ through Shade's direct geometry path — it proves the DAG, the format and the clamp, and by
21
+ construction it proves nothing about the engine. `shade/playground/vgeo_runtime/` puts an ordinary
22
+ `Mesh` in an ordinary `Scene` and draws it entirely from a **GPU** cut through the meshlet
23
+ pipeline — it proves the seam and nothing about the DAG. §10 says which milestone that leaves us
24
+ in, and the order the work actually arrived in.
17
25
 
18
26
  ## 1. What and why
19
27
 
@@ -317,7 +325,10 @@ Pinned:
317
325
  fill in M2, never from these nominals.
318
326
  - **Immutable and content-addressed** (`hash(output bytes)` in header and filename):
319
327
  perfect HTTP caching, CDN-friendly, no invalidation protocol.
320
- - **Fetch unit = size-bounded page** (order hundreds of KB; measured in M2), fetched by
328
+ - **Fetch unit = size-bounded page** (64 KiB, `VGEO_DEFAULT_PAGE_SIZE` — **one** page size, against
329
+ Nanite's two, traded off between hiding fetch latency and both response time and VRAM per
330
+ resident page on a low-end device; this section's "order hundreds of KB" was a first-principles
331
+ guess and is superseded, see the constant's own note), fetched by
321
332
  HTTP **single-range** requests and **independently decodable**: a group never spans
322
333
  pages; a page carries whole groups with payloads in (near-)upload-ready encoded form.
323
334
  - **Pages carry the DAG, fetchably**: each cluster record stores its two (sphere, error)
@@ -348,7 +359,7 @@ Pinned:
348
359
  - **Coarse-first layout**: pages ordered so a plain prefix read of the file yields a
349
360
  complete coarse asset — progressive load without a feedback loop.
350
361
 
351
- Open: page size; a denser page codec than LZ4, if quantization leaves one worth having;
362
+ Open: a denser page codec than LZ4, if quantization leaves one worth having;
352
363
  per-attribute quantization widths (cluster-local grids); relationship to
353
364
  `MeshletGeometrySerializationAdapter` (extend vs parallel).
354
365
 
@@ -376,6 +387,13 @@ Pinned:
376
387
  intent is **fixed-size page-slot allocation** (no compaction, no CPU metadata mirror,
377
388
  no transient 2× rebuild), plus propagation of address migration to every consumer —
378
389
  the existing `GPUGeometryManager` TODO becomes load-bearing under churn.
390
+
391
+ What that rests on is the container, and it now holds: FORMAT §11.14 makes
392
+ `nominal_page_size` a bound no page exceeds, announced by a feature bit a pool can require.
393
+ Before that it was an advisory number the builder itself broke — 13 of 40 pages over it on a
394
+ measured build — and a fixed stride sized against it would have been a slot overrun. A pool
395
+ taking the guarantee is entitled to one slot size and nothing else: it refuses a container
396
+ built at a different page size rather than carrying a size-class table for it.
379
397
  - **Activation invariant (closure without dependency metadata)**: a group is
380
398
  *selectable* iff **all** of its parent-cluster references are live — each referencing
381
399
  cluster's page installed and that cluster's own group selectable (per-group reference
@@ -565,6 +583,14 @@ canonicalizing them is a preprocessor's job).
565
583
  and the reports behind it. `cone_reserved` stays a zeroed reserved field (format §7). This is
566
584
  the one entry here that is not a "v1" qualifier.
567
585
  - Skinned / morphing / WPO meshes through the VG path (static geometry only).
586
+ - **Non-opaque VG instances.** The cut runs beside the mesh→meshlet expansion of the *opaque*
587
+ rasterization paths, and the transparent and alpha-tested bucket path
588
+ (`prepare_meshlet_draw_commands_by_material`) has no seam: it is handed a scene and an optional
589
+ camera resource rather than a view context, where a cut needs the camera's uniform buffer to put
590
+ each instance into object space. A VG instance with a non-opaque material therefore expands to
591
+ zero meshlets and draws nothing at all. `GPUSceneRows.attach_mesh` says so out loud when one is
592
+ attached, because this bullet is the authority for it and did not exist while the code was citing
593
+ it.
568
594
  - Compute/software rasterization — WebGPU lacks portable 64-bit atomics; the hardware
569
595
  raster path through the existing pipeline is the only raster path. Deferred G-buffer
570
596
  shading already bounds the overshading cost of small triangles.
@@ -597,13 +623,31 @@ is the piece still missing, and M1 is where the work now starts.
597
623
  which was M2's job; τ-sweep watertightness as a spec (`build/vgeo_cut.spec.js` counts edges
598
624
  used by one triangle instead of two, at a spread of thresholds, and wants zero); the
599
625
  group-boundary polyline guarantee, enforced by the builder rather than merely validated.
600
- **Not done**: the expansion seam — the playground draws through Shade's direct geometry path
601
- (a `DynamicMesh` from raw attributes, no meshlet encode), so nothing has yet entered the
602
- meshlet pipeline through a VG-shaped door. Flip validation is not done either (§2, extension
603
- 3).
604
- - **M1 — GPU cut**: selection buffer + compute cut pass; wants/touches readback wired
605
- (still fully resident). Acceptance: parity with M0, selection cost measured
606
- (flat-vs-hierarchy decision data).
626
+ **Done since**: the expansion seam. `runtime/graph_vgeo_expand_instances_to_meshlets` runs beside
627
+ `graph_expand_meshes_to_meshlets` at each of the three opaque call sites and appends into the same
628
+ collection, so a VG instance enters through a VG-shaped door and everything past the expansion is
629
+ untouched. `shade/playground/vgeo_runtime/` is that, on screen.
630
+ **Still not done**: flip validation (§2, extension 3).
631
+ - **M1 — GPU cut** — **done**: selection buffer + compute cut pass; wants/touches readback wired.
632
+ `runtime/` holds all of it and [runtime/RUNTIME_PLAN.md](./runtime/RUNTIME_PLAN.md) is its design.
633
+ Acceptance was parity with M0 and selection cost measured, and both were taken:
634
+
635
+ *Parity* is `runtime/cut/vgeo_cut_agreement.spec.js` — the kernels run under the WGSL emulator
636
+ against a real container and their drawn cluster **multiset** is held to exactly what
637
+ `vgeo_select_cut` selects, over seven cameras and four thresholds, fully resident and with pages
638
+ withheld, one instance and two. Multiset rather than set because a duplicate is the failure that
639
+ reads as corrupt geometry rather than as a missing page.
640
+
641
+ *Measured*, on an nvidia/lovelace adapter, a 13-page torus opened holding only its root page and
642
+ streamed entirely by the cut's own wants: 50 drawn clusters at τ=0.5 down to 8 at τ=16, monotone,
643
+ visiting 9 pages down to 5, clamping to zero as pages arrive, dropping nothing. Half the frames
644
+ skip feedback entirely under a two-deep readback cap, which is the virtual-texture contract and
645
+ costs streaming latency rather than pixels.
646
+
647
+ The flat-vs-hierarchy decision data is *not* what M1 produced, and §7 already says why: §4 pinned
648
+ the traversal and the question stopped being a measurement to take. What the cut measures instead
649
+ is that the hierarchy's own accelerator is sound — the node metric's `max_parent_error` cull fires
650
+ across the whole agreement sweep and changes not one selected cluster.
607
651
  - **M2 — format + streaming** — *the RAM tier landed early*: `.vgeo` container, pager,
608
652
  residency tiers with the §6 invariants (page-slot pool, address-migration propagation,
609
653
  occlusion-aware wants, touch-LRU, in-flight pinning), budgets, eviction. Position
@@ -613,8 +657,12 @@ is the piece still missing, and M1 is where the work now starts.
613
657
  **Done ahead of M1**: the container and its validator; the RAM-tier pager with residency,
614
658
  activation and eviction (`format/read/`); TTFP, which is header + level table + root page and
615
659
  measured to hold on a 27M-triangle asset; the page codec, chosen on measurement (§5).
616
- **Still M2's**: the VRAM page-slot pool, address-migration propagation, occlusion-aware
617
- wants, touch-LRU, in-flight pinning, budgets, and position quantization.
660
+ **Done with M1**: the VRAM page pool (`runtime/GPUVirtualGeometryPool`) with a byte budget and
661
+ touch-LRU under a protect window, and the want record now naming the page it was discovered
662
+ *through* — which is what in-flight pinning was blocked on, since the format stores parent counts
663
+ rather than parent references and nothing below the cut could name that page.
664
+ **Still M2's**: address-migration propagation, occlusion-aware wants, the in-flight pin itself,
665
+ and position quantization.
618
666
  - **M3 — hardening**: prefetch, disk page store decision, budget defaults, low-limit
619
667
  adapters (τ fallback), stress on CAD-class input; browser builder-harness proof
620
668
  (build a mid-size asset in-tab).
@@ -6,10 +6,11 @@ This file holds what is *next*, and — more usefully — the reasoning and the
6
6
  ideas that are accepted but not yet scheduled, so that picking one up does not mean
7
7
  rediscovering it.
8
8
 
9
- Current scope is **the GPU runtime**. The builder, the container and an incremental reader are
10
- built — design §10 says which milestone that leaves us in and in what order the work arrived —
11
- so §3 no longer waits on "a runtime" as a whole. Each item there now names what is actually
12
- blocking it.
9
+ The GPU runtime is **built** — `runtime/`, and
10
+ [runtime/RUNTIME_PLAN.md](./runtime/RUNTIME_PLAN.md) is its design and its adversarial review. The
11
+ cut runs on the device, the VRAM tier holds pages under a budget, wants and touches come back
12
+ through a readback, and a VG instance enters the meshlet pipeline through a door of its own. §3's
13
+ items are no longer blocked on "a runtime"; each names what it is actually waiting for now.
13
14
 
14
15
  Items carry their provenance. `[M §x]` is a section of
15
16
  [MICRON_COMPARISON_2026_08_20.md](./MICRON_COMPARISON_2026_08_20.md), the review of the
@@ -78,11 +79,43 @@ accepted-and-waiting one.
78
79
  meshlet pipeline — so it proves the DAG and the format, and proves nothing about the engine
79
80
  seam.
80
81
 
82
+ - **The GPU runtime** — `runtime/`, and [runtime/RUNTIME_PLAN.md](./runtime/RUNTIME_PLAN.md) for the
83
+ design and the adversarial review that reshaped it. The cut is a wavefront BFS over the page DAG
84
+ with a per-cluster predicate at the leaves, and that combination is the whole design: the
85
+ hierarchy decides which clusters to *look at*, the predicate decides whether to *draw* them, and
86
+ because the predicate is a pure function of one row, a page visited twice costs a duplicate record
87
+ rather than a wrong answer — so dedup is one bit per (instance, resident page) and never per
88
+ cluster, which is the number design §4 is trying not to pay.
89
+
90
+ Three things the review found, in descending order of how much they cost to rediscover:
91
+
92
+ - **Design §4's per-cluster rule double-draws as written.** A group that is resident but not
93
+ selectable has clusters satisfying it while their coarse stand-in is drawn over the same
94
+ surface. The rule needs an *own group selectable* term that a traversal gets for free and an
95
+ evaluator does not. §6 states the fact without stating it as a term of the rule, because §4 was
96
+ written for a traversal.
97
+ - **Page size decides whether that state is even reachable.** At the 8 KB pages
98
+ `select_cut.spec.js` uses, a torus puts exactly one group in every page — so a blocked group is
99
+ a blocked page, no page is ever mixed, and no page is ever entered twice. Measured: 40 pages
100
+ withheld one at a time and 60 random residency subsets produced *no* mixed page. At 64 KB the
101
+ median page holds eight groups and it is ordinary. A spec for anything about mixed selectability
102
+ has to be written at a page size that produces it.
103
+ - **Every bound degrades in one direction.** A full meshlet collection drops records; a claim
104
+ allocation that does not fit drops the *instance*, because an instance run unclaimed draws its
105
+ whole cut once per parent page that reaches it.
106
+
107
+ Held against mutation: removing the own-group term, the claim, the child selectability check or
108
+ the parent projection each turns the agreement suite red. Proved on a device in
109
+ `playground/vgeo_runtime/` — a 13-page container opened holding only its root page, streamed to 9
110
+ by its own wants, 50 drawn clusters at τ=0.5 falling monotonically to 8 at τ=16, clamping to zero,
111
+ dropping nothing.
112
+
81
113
  ## 2. Next
82
114
 
83
115
  §2.1 and §2.2 both landed. They are kept because their notes are the reasoning behind decisions
84
- that are now only visible in code, and rediscovering them is the expensive part. **§2.3 is the
85
- open one**, and it is the one to read if you are about to start on the cut pass.
116
+ that are now only visible in code, and rediscovering them is the expensive part. §2.3 **half**
117
+ closed, and the half that did not is the more interesting one — read it before building a
118
+ prefetch.
86
119
 
87
120
  ### 2.1 A partitioning fallback for when METIS fails `[M §3.3]`, `[V §3.2]` — LANDED
88
121
 
@@ -204,7 +237,7 @@ instance count first and from fan-out immediately after — measured out-degree
204
237
  the middle bands of the 98k sphere. It bites only for a single instance in close-up, and only
205
238
  for the first few bands, which are also the cheapest to walk.
206
239
 
207
- ### 2.3 Nothing reads the traversal metric or the node tree
240
+ ### 2.3 The node tree has a reader; the child-table metric still does not — LANDED (half)
208
241
 
209
242
  §2.2 built both, and the format specifies both: every child-table entry carries the child page's
210
243
  LOD-metric sphere and error range (VGEO_FORMAT §5, §6.2), and every page carries a fanout-4 tree
@@ -231,13 +264,36 @@ What it buys when the pass arrives, and what to hold it to:
231
264
  Neither is measurable until there is a GPU traversal to measure, which is the honest reason it
232
265
  has waited.
233
266
 
234
- ## 3. Waiting on a GPU runtime
267
+ **What happened when the traversal arrived**, and it is not what this section predicted.
268
+
269
+ *Below the page*, the node tree has its reader. `runtime/cut/shader_vgeo_cut_traverse` descends it,
270
+ tests each node's sphere and `max_parent_error`, and discards a region rather than a cluster. The
271
+ cull fires across the whole agreement sweep and changes not one selected cluster, which is what
272
+ sound means for an accelerator. The 0.3%–1.6% of asset size is earning something now.
273
+
274
+ *Before the fetch*, the child-table metric **still has no reader, and now for a reason rather than
275
+ for want of a consumer.** Held against the cut, it buys nothing. A want is emitted from a cluster
276
+ that has already passed `project(parent) > τ` and `project(self) > τ`; the child page's metric
277
+ covers that cluster's child group among others, so its `max_parent_error` is at least that group's
278
+ `parent_error` and its sphere contains that group's `parent_sphere` — and the projection is monotone
279
+ in both. The page-level test is therefore **always** above τ at exactly the moment the question is
280
+ asked. It cannot reject what a per-cluster test has just accepted, because the per-cluster test is
281
+ strictly tighter. The frustum half dies the same way: the cluster's own `self_sphere` is a tighter
282
+ test than the child page's union sphere, available locally, for free.
283
+
284
+ Where the copy *is* load-bearing is **prefetch** — ranking and bounding pages nobody has asked for
285
+ yet, which by definition has no cluster-level evidence to be tighter than. `min_self_error` is in
286
+ the same position: it ranks a want and the cut never asks it anything. So the field is not dead, it
287
+ is early, and the thing it is early for is §3's territory rather than the cut's. Anyone building a
288
+ prefetch should start here.
235
289
 
236
- Accepted, documented here so the reasoning is not lost. "Waiting on a runtime" used to mean
237
- waiting on all of it; a CPU traversal and a RAM-tier pager now exist (§1), so what each item is
238
- actually blocked on is named individually.
290
+ ## 3. Was waiting on a GPU runtime
239
291
 
240
- ### 3.1 Object-space refinement `[M §2.7]`
292
+ Accepted, documented here so the reasoning is not lost. "Waiting on a runtime" used to mean waiting
293
+ on all of it; the runtime exists now (`runtime/`), so §3.1 has landed with it and the rest name what
294
+ they are actually waiting for.
295
+
296
+ ### 3.1 Object-space refinement `[M §2.7]` — LANDED
241
297
 
242
298
  `RefinementSpec.build` in `Micron/render/refinement/RefinementSpec.js`: multiply the camera's
243
299
  view-projection by the instance transform once, derive the frustum planes from the result,
@@ -245,10 +301,17 @@ and run every cull and error test in **object space** against untransformed clus
245
301
  One matrix multiply per instance instead of transforming every cluster's bounds to world
246
302
  space. With thousands of instances sharing one geometry that difference is the entire cost.
247
303
 
248
- **Blocked on**: instances. The CPU traversal already tests in object space — but only because
249
- the viewer draws one geometry at identity and never had to decide, so the rule is written the
250
- right way round and the multiply that makes it pay has never been exercised. This becomes real
251
- work at the same moment the cut pass does.
304
+ **Landed** with the cut. `runtime/cut/shader_vgeo_cut_collect` is the one place a VG transform is
305
+ touched: it multiplies the camera's view-projection by the instance transform, extracts the frustum
306
+ from the result, inverts the transform once for the eye, and writes both into the instance record.
307
+ Every cull and error test downstream runs against untransformed cluster bounds.
308
+
309
+ One thing fell out that `RefinementSpec.build` never had to face, because object space is not free
310
+ when an instance is scaled: error and distance are **both** object-space, so a *uniform* scale
311
+ cancels out of `r_e · k / d` exactly and needs no term at all. A non-uniform one does not, and the
312
+ correction is the ratio of the largest axis scale to the smallest — conservative, so it over-refines
313
+ rather than under-refines, and exactly 1 for every instance that is not stretched. Worth knowing
314
+ before anyone adds a scale term that does nothing in the common case.
252
315
 
253
316
  ### 3.2 Proxy geometry from a DAG cut `[M §2.6]`
254
317
 
@@ -282,6 +345,16 @@ choice belongs to the view, not to the geometry.
282
345
 
283
346
  ### 3.4 GPU-driven allocation `[V §4.3]`
284
347
 
348
+ **Narrower now, and the runtime says exactly how much.** A VG page is one arena allocation holding
349
+ its node, group and cluster rows back to back, and an eviction frees one allocation and writes one
350
+ lookup word. Nothing inside a resident page can move, growth appends through `OffsetAllocator#grow`
351
+ and relocates nothing, and no address migrates. What VG's churn *does* still reach is
352
+ `GPUMeshletManager`: a page install is an `add_batch` and an eviction a `remove_batch`, so the holes
353
+ accumulate in that pool and closing them is still a CPU-side rebuild with the unhandled
354
+ address-migration TODO downstream of it. Pages are near-uniform in size, so a hole is usually
355
+ re-usable by the next install — which narrows the problem without closing it, and is the reason this
356
+ is still an item rather than an incident.
357
+
285
358
  Worth pursuing **beyond virtual geometry** — this is a GPU-driven engine and CPU-side
286
359
  allocation for GPU-resident data is a standing mismatch.
287
360
 
@@ -384,9 +457,14 @@ attacked:
384
457
  `MICRON_PATCH_SIZE_MAX × 3` vertices with occupancy in a BitSet: no allocator, no
385
458
  fragmentation, no address migration, at the cost of internal fragmentation proportional to
386
459
  `1 − fill`. **Declined: pages already are the compaction mechanism.** A page is fixed-size
387
- by construction and carries whole groups; the lever to pull is page waste, by keeping
388
- clusters full — which §1's cluster-count target and repack are for — not by giving up
389
- variable-size allocation.
460
+ and carries whole groups; the lever to pull is page waste, by keeping clusters full — which
461
+ §1's cluster-count target and repack are for — not by giving up variable-size allocation.
462
+
463
+ *(2026-09-12: "by construction" was the original wording and it was not true — the builder
464
+ overran `nominal_page_size` whenever a group did not fit, and the format told readers not to
465
+ assume the number at all. It is true now, by enforcement rather than by construction:
466
+ FORMAT §11.14, the builder refusing a page size no group fits, and the validator behind it.
467
+ The conclusion this bullet reaches is unchanged; the premise had to be made real first.)*
390
468
  - **Shape-aware greedy leaf clustering** `[M §2.8]`. Micron grows leaf patches with a heap
391
469
  scored on perimeter change, added-neighbour count, and "open face freedom" — how many of a
392
470
  candidate face's neighbours are about to be stranded
@@ -48,8 +48,18 @@ export class VGeoBuildOptions {
48
48
  */
49
49
  weld_ratio: number;
50
50
  /**
51
- * Target decoded bytes per page. Informational in the file; the format forbids readers from
52
- * assuming it.
51
+ * Decoded bytes per page, as a **hard cap**: no page of the asset exceeds it, and the container
52
+ * says so with a feature bit a runtime can require (`VGEO_FORMAT.md` §11.14). That is what makes
53
+ * a page slot a fixed stride on the device — a slot address is a multiply, with no allocator, no
54
+ * growth path and no address migration behind it.
55
+ *
56
+ * The cap is kept by splitting bands into more pages, which costs only the internal
57
+ * fragmentation of the last page of each band — measured at about 9% on a 254-page build. The
58
+ * one thing it cannot do is split a group: `VGEO_FORMAT.md` §11.1 makes a group atomic to its
59
+ * page and the whole activation invariant rests on that. So a build whose clusters group into
60
+ * more bytes than a page holds **throws**, naming the page size that would work, and the remedy
61
+ * is that page size rather than anything about the mesh.
62
+ *
53
63
  * @type {number}
54
64
  */
55
65
  nominal_page_size: number;
@@ -1 +1 @@
1
- {"version":3,"file":"VGeoBuildOptions.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACI;;;;;;;;;;OAUG;IACH,OAFU,MAAM,CAEO;IAEvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,YAFU,MAAM,CAEE;IAElB;;;;OAIG;IACH,mBAFU,MAAM,CAEe;IAE/B;;OAEG;IACH,eAFU,gBAAgB,CAEa;CAC1C;iCAhEgC,uBAAuB"}
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,4 +1,5 @@
1
1
  import { VGEO_CODEC_LZ4 } from "../format/frame/VGEO_CODEC_LZ4.js";
2
+ import { VGEO_DEFAULT_PAGE_SIZE } from "../format/page/VGEO_DEFAULT_PAGE_SIZE.js";
2
3
  import { VGeoLevelOptions } from "./VGeoLevelOptions.js";
3
4
 
4
5
  /**
@@ -53,11 +54,21 @@ export class VGeoBuildOptions {
53
54
  weld_ratio = 1e-5;
54
55
 
55
56
  /**
56
- * Target decoded bytes per page. Informational in the file; the format forbids readers from
57
- * assuming it.
57
+ * Decoded bytes per page, as a **hard cap**: no page of the asset exceeds it, and the container
58
+ * says so with a feature bit a runtime can require (`VGEO_FORMAT.md` §11.14). That is what makes
59
+ * a page slot a fixed stride on the device — a slot address is a multiply, with no allocator, no
60
+ * growth path and no address migration behind it.
61
+ *
62
+ * The cap is kept by splitting bands into more pages, which costs only the internal
63
+ * fragmentation of the last page of each band — measured at about 9% on a 254-page build. The
64
+ * one thing it cannot do is split a group: `VGEO_FORMAT.md` §11.1 makes a group atomic to its
65
+ * page and the whole activation invariant rests on that. So a build whose clusters group into
66
+ * more bytes than a page holds **throws**, naming the page size that would work, and the remedy
67
+ * is that page size rather than anything about the mesh.
68
+ *
58
69
  * @type {number}
59
70
  */
60
- nominal_page_size = 256 * 1024;
71
+ nominal_page_size = VGEO_DEFAULT_PAGE_SIZE;
61
72
 
62
73
  /**
63
74
  * @type {VGeoLevelOptions}