@woosh/meep-engine 3.60.4 → 3.60.5

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 (379) hide show
  1. package/CHANGELOG.md +2602 -0
  2. package/CONTRIBUTING.md +7 -0
  3. package/MIGRATION_2_TO_3.md +136 -0
  4. package/SECURITY.md +4 -0
  5. package/editor/EditorCameraNavigation.d.ts +2 -1
  6. package/editor/EditorCameraNavigation.d.ts.map +1 -1
  7. package/editor/EditorCameraNavigation.js +51 -57
  8. package/package.json +6 -2
  9. package/src/engine/EngineHarness.d.ts +3 -9
  10. package/src/engine/EngineHarness.d.ts.map +1 -1
  11. package/src/engine/EngineHarness.js +6 -8
  12. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
  13. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
  14. package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
  15. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
  16. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
  17. package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
  18. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
  19. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
  20. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
  21. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
  22. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  23. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
  24. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
  25. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  26. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
  27. package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
  28. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  29. package/src/engine/graphics/ecs/water/Water.js +8 -0
  30. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  31. package/src/engine/graphics3/WaterSystem.js +731 -734
  32. package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
  33. package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
  34. package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
  35. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
  36. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  37. package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
  38. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
  39. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
  40. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
  41. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
  42. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
  43. package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
  44. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
  45. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  46. package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
  47. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
  48. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
  49. package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
  50. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
  51. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
  52. package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
  53. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
  54. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
  55. package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
  56. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
  57. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
  58. package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
  59. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  60. package/src/shade/playground/water_refraction/README.md +5 -6
  61. package/src/shade/playground/water_refraction/index.html +1 -1
  62. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  63. package/src/shade/renderer/Renderer.js +7 -5
  64. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  65. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  66. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  67. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  68. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  69. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  70. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  71. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  72. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +2 -2
  73. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +2 -2
  74. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +2 -1
  75. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +18 -1
  77. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  78. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  79. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  80. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  81. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  82. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  83. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  84. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  85. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  86. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
  87. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +5 -5
  88. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  89. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +10 -10
  90. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
  91. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  92. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
  93. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  94. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  95. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  96. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  98. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  99. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  100. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  101. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  102. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  103. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  104. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  105. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  106. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  107. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  108. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  109. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  110. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  111. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  112. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  113. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  114. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  115. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  116. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  117. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  118. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  119. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  120. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  121. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  122. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  123. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  124. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  125. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  126. package/src/shade/renderer/material/GPUMaterialManager.d.ts +7 -0
  127. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  128. package/src/shade/renderer/material/GPUMaterialManager.js +11 -0
  129. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  130. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  131. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  132. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  133. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  134. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  135. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  136. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  137. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  138. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  139. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  140. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  141. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  142. package/src/shade/renderer/water/shader_water.js +22 -13
  143. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  144. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  145. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  146. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  147. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  148. package/editor/REWORK_PLAN_2026_08.md +0 -692
  149. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  150. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  151. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  152. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  153. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  154. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  155. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  156. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  157. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  158. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  159. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  160. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  161. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  162. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  163. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  164. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  165. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  166. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  167. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  168. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  169. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  170. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  171. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  172. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  173. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  174. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  175. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  176. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  177. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  178. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  179. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  180. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  181. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  182. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  183. package/src/REVIEW_2026_08_06/generation.md +0 -176
  184. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  185. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  186. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  187. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  188. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  189. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  190. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  191. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  192. package/src/REVIEW_2026_08_06/view.md +0 -667
  193. package/src/REVIEW_2026_08_06.md +0 -610
  194. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  195. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  196. package/src/core/bvh8/NOTES.md +0 -63
  197. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  198. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  199. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  200. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  201. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  202. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  203. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  204. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  205. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  206. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  207. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  208. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  209. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  210. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  211. package/src/engine/REVIEW_2026_07_03.md +0 -211
  212. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  213. package/src/engine/control/first-person/DESIGN.md +0 -658
  214. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  215. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  216. package/src/engine/control/first-person/TODO.md +0 -160
  217. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  218. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  219. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  220. package/src/engine/ecs/ik/PLAN.md +0 -179
  221. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  222. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  223. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  224. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  225. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  226. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  227. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  228. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  229. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  230. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  231. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  232. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  233. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  234. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  235. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  236. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  237. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  238. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  239. package/src/engine/physics/cloth/PLAN.md +0 -3056
  240. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  241. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  242. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  243. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  244. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  245. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  246. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  247. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  248. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  249. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  250. package/src/format/image/avif/DECISIONS.md +0 -545
  251. package/src/format/image/jpeg/NOTES.md +0 -3
  252. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  253. package/src/generation/COORDINATES.md +0 -54
  254. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  255. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  256. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  257. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  258. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  259. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  260. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  261. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  262. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  263. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  264. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  265. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  266. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  267. package/src/shade/RENDERER_CONTRACT.md +0 -697
  268. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  269. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  270. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  271. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  272. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  273. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  274. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  275. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  276. package/src/shade/renderer/NOTES.md +0 -78
  277. package/src/shade/renderer/PLAN.md +0 -8
  278. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  279. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  280. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  281. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  282. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  283. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  284. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  285. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  286. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  287. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  288. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  289. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  290. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  291. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  292. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  293. package/src/shade/renderer/camera/orbital/README.md +0 -81
  294. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  295. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  296. package/src/shade/renderer/camera/orbital/index.js +0 -9
  297. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  298. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  299. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  300. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  301. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  302. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  303. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  304. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  305. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  306. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
  307. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  308. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  309. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  310. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  311. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  312. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  313. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  314. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
  315. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  316. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
  317. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  318. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  319. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  320. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  321. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  322. package/src/shade/renderer/hiz/NOTES.md +0 -3
  323. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  324. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  325. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  326. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  327. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  328. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  329. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  330. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  331. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  332. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
  333. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  334. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  335. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  336. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  337. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  338. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
  339. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  340. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  341. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  342. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  343. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  344. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  345. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  346. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  347. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  348. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  349. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  350. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  351. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
  352. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
  353. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  354. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  355. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  356. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  357. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  358. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  359. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  360. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  361. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  362. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
  363. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  364. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  365. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  366. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  367. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  368. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  369. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  370. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  371. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  372. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  373. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
  374. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  375. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  376. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  377. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  378. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  379. package/src/shade/wgsl/NOTES.md +0 -4
@@ -1,657 +0,0 @@
1
- # Virtual Geometry — the GPU runtime
2
-
3
- > **Two sections are rewritten, and the rest stands.** An adversarial review on 2026-09-11 found
4
- > ~35 distinct defects in what this document described; they were worked to closure on 2026-09-12
5
- > and [REVIEW_LEDGER.md](./REVIEW_LEDGER.md) records each one against its commit. What that campaign
6
- > changed *here* is **§3.1**, where the variable `OffsetAllocator` became fixed-stride page slots
7
- > with the page header inside the slot, and **§4.9**, whose binding list named a buffer the kernel
8
- > does not bind and omitted one it does. §4's proofs, §4.2–§4.8 and §5 are as they were, and §8 —
9
- > the author's own review — is kept for the record with the ledger's corrections noted in it.
10
- >
11
- > The ledger is still the place to start for what was wrong and how it was found. It is no longer a
12
- > list of open defects.
13
- >
14
- > **And the cut is under review, 2026-09-14.** §4's rule and its proofs stand, but the claim
15
- > that it is evaluated from the root every frame is what
16
- > [CUT_PLAN_2026_09_14.md](./CUT_PLAN_2026_09_14.md) proposes to replace, with a per-view cut kept
17
- > between frames and a `.vgeo` change to make walking up possible. Nothing of it is built: its §0
18
- > measures what a cut dispatch costs in the runtime as it stands here.
19
- >
20
- > **And residency is rewritten, 2026-09-14.** §3.2, §3.3, the touch half of §4.5 and everything
21
- > `VGeoResidencyManager` did are superseded by
22
- > [RUNTIME_PLAN_2026_09_14.md](./RUNTIME_PLAN_2026_09_14.md): selectability is kept by a counter in
23
- > the group row rather than recomputed per install, a touch is one bit, the want stamps are a
24
- > persistent frame-tagged buffer, both tiers live in one flat `VGeoResidency` with CLOCK eviction
25
- > under a per-reading test budget, and pages are fetched, verified and decoded on workers. The cut
26
- > also frustum-tests a cluster before it claims its child page. What is left of this document is
27
- > still right about the slot layout, the claim, the cut rule and its proofs, and the seam.
28
-
29
- **Status: built.** This was the plan for what
30
- [VIRTUAL_GEOMETRY_DESIGN.md](../VIRTUAL_GEOMETRY_DESIGN.md) §7 describes — the selection buffer, the
31
- compute cut pass, the VRAM page pool, the wants/touches loop, and the seam through which a VG
32
- instance enters the meshlet pipeline — and it is kept as written plus §8, the adversarial review,
33
- because the review is where most of the design actually came from.
34
-
35
- What it describes now exists: `cut/` is the kernels, `layout/` the record formats,
36
- `VGeoRuntimeTables` the layout with no device in it, `GPUVirtualGeometryPool` the VRAM tier,
37
- `VGeoFeedbackLoop` the readback, and `graph_vgeo_expand_instances_to_meshlets` the seam. The cut is
38
- held to `vgeo_select_cut` exactly, and `playground/vgeo_runtime/` runs it on a device.
39
-
40
- Everything below the runtime was already built when this was written. The builder produces a DAG,
41
- the container holds it, the reader streams it a page at a time with residency and activation,
42
- `VGeoResidencyManager` owns the RAM budget, and `runtime/cut/__fixtures__/vgeo_select_cut.js` selects a
43
- correct cut on the CPU against a partially resident container. What had never run was any of it on
44
- the GPU, and the one piece of M0 that is engine-facing — the expansion seam — had never been built
45
- at all.
46
-
47
- ## 0. The shape of the answer, in one paragraph
48
-
49
- A cluster is drawn iff `project(parent) > τ AND (project(self) ≤ τ OR child group not selectable)`.
50
- That is **a pure function of one cluster record**. Design §4 pins it, and every hard problem in a
51
- GPU cut comes from noticing which half of that sentence you are leaning on. Lean on the
52
- *traversal* (descend the DAG, stop when fine enough) and you must dedup visits, because the DAG is
53
- many-parented and a group reached twice is drawn twice. Lean on the *predicate* (evaluate it
54
- everywhere) and you need no dedup at all, but you pay the resident set every frame, which is what
55
- §4 rejects. The runtime does both, in the only combination that has neither cost:
56
-
57
- > **Traverse the hierarchy to decide which clusters to look at; evaluate the predicate to decide
58
- > whether to draw them.**
59
-
60
- Visiting a cluster twice then costs a duplicate *record*, so the traversal dedups at **page**
61
- granularity only — one bit per (instance, resident page), which is affordable — and never at
62
- cluster or group granularity, which would not be. Visiting a cluster the traversal "should not"
63
- have reached costs nothing but the test, because the predicate is local and gives the same answer
64
- from every direction. §4 below proves the two halves that matter: no cluster is drawn twice, and
65
- no cluster the flat rule would draw is missed.
66
-
67
- ## 1. What is already there, and what each piece is worth
68
-
69
- | piece | state | what the runtime does with it |
70
- |---|---|---|
71
- | `format/read/VGeoPage` | reads the page header, group table, cluster table | extended (§2) — `parent_sphere`, `aabb`, `payload_size`, the node table and every metric are in the file and unread |
72
- | page node tree (format §6.1) | written, validated, **no reader** | the sub-page cull; plan §2.3's open item half-closes here |
73
- | child-table traversal metric (format §5, §6.2) | written, validated, **no reader** | **still no reader, and the review below says why** (§4.8) — it is a prefetch structure, not a cut structure |
74
- | `VGeoContainerReader` | residency, activation, quarantine, cancel | the RAM tier; the runtime never fetches, it *wants* |
75
- | `VGeoResidencyManager` | byte budget, touch-LRU, shared fetch gate | the runtime hands it wants and touches and nothing else |
76
- | `GPUMeshletManager` | data + metadata pools, `add_batch`/`remove_batch` | the VRAM tier's storage; format §8 makes a page payload a **copy** into the data pool |
77
- | `MESHLET_DEFINITION_STRUCT` | `(meshlet index, mesh index)` | the cut's output record type — unchanged, which is what makes the seam one call |
78
- | `graph_expand_meshes_to_meshlets` | four call sites, three of them opaque | the seam: a VG expansion runs beside each opaque one and appends into the same collection (§5) |
79
- | `cut/__fixtures__/vgeo_select_cut.js` | the rule on the CPU | **the oracle**. The GPU cut is held to agreeing with it exactly |
80
-
81
- ## 2. Phase 1 — the page decode has holes, and they are load-bearing
82
-
83
- `vgeo_read_page` reads two thirds of a cluster record and none of the node table. Everything it
84
- skips is something the GPU runtime needs:
85
-
86
- - `parent_sphere` (offset 24) and `parent_error` — `parent_error` is read, `parent_sphere` is not.
87
- Half the cut rule is `project(parent_sphere, parent_error) > τ`. The CPU traversal never needed
88
- it because arriving through a parent *is* that test; a predicate evaluated per cluster needs the
89
- sphere.
90
- - `aabb` (offset 56) — this is the meshlet metadata's `bounds_box`, which the HZB filter and the
91
- small-primitive cull read. Without it a VG meshlet cannot be culled.
92
- - `payload_size` (offset 96) — the number of bytes to copy into the data pool.
93
- - the **node table** (§6.1) and the page's own **metric** (§6) — the sub-page hierarchy. The page's
94
- own metric arrives for free: format §11.10 makes it equal to node 0's, and the traversal always
95
- starts a page at node 0.
96
- - the **child-table metrics** (§5) are read too, because a reader that decodes two thirds of a
97
- structure is worse than one that decodes all of it — but see §4.8: the cut does not use them.
98
-
99
- Phase 1 is a pure addition to `VGeoPage` and `vgeo_read_page`, with the existing reader spec
100
- extended to hold the new columns against the bytes the writer emitted. Nothing that reads the page
101
- today changes.
102
-
103
- **Cost check — measured, 2026-09-12.** Per resident page this adds `4·4 + 6·4 + 4` bytes per cluster
104
- and `32` per node of CPU-side typed arrays. The gate this paragraph set was never discharged, so
105
- here it is: summing every typed array a decoded `VGeoPage` holds that is not a view over the page's
106
- own bytes, on a 240×120 torus,
107
-
108
- | page size | pages | overhead per page | mean | of the decoded bytes |
109
- |---:|---:|---:|---:|---:|
110
- | 64 KB | 44 | 0.2–4.1 KB | 2.9 KB | 6.1% |
111
- | 128 KB | 26 | 0.2–8.4 KB | 4.8 KB | 6.0% |
112
- | 544 KB | 13 | 0.2–33.9 KB | 9.4 KB | 5.9% |
113
-
114
- It is a **fraction** — six per cent, flat across page size — rather than a per-page constant, which
115
- is the more useful shape to know: the overhead is per cluster and per node, and a page's cluster
116
- count is what its size buys. The residency audit's "6.4–11.9 KB against a 544 KB page" is the mean
117
- of the same distribution and not its range; the widest page here is 33.9 KB, and a document quoting
118
- the narrow band as a bound would be wrong by 2.8× at the top.
119
-
120
- ## 3. Phase 2 — the VRAM tier
121
-
122
- ### 3.1 What a resident page costs on the GPU
123
-
124
- One `GPUMeshletManager.add_batch` (payload bytes → data pool, synthesized 40-byte metadata rows →
125
- metadata pool) **plus one fixed-stride slot** of the arena:
126
-
127
- ```
128
- [ page header ][ node rows ][ group rows ][ cluster rows ]
129
- ```
130
-
131
- A page's slot is at `page_row × slot_words`. There is no allocator, no growth path, no ceiling, and
132
- no page table: the header is the first eight words of the page's own slot, so the traversal reads it
133
- out of a buffer it already binds.
134
-
135
- **This is design §6's "fixed-size page-slot allocation", and it took a format change to be
136
- available.** The first draft here used a variable `OffsetAllocator`, on the argument that pages
137
- carry whole groups and a group may be oversized, so a hard slot size would refuse a legal file —
138
- format §11.5's escape. §11.14 closed that: `nominal_page_size` is a bound every page is within,
139
- announced by a feature bit, and the builder refuses a page size no group fits rather than writing a
140
- page over it. `GPUVirtualGeometryPool.register` refuses a container that does not carry the
141
- guarantee or carries it at a different size than the pool holds.
142
-
143
- **The stride is a choice, and the numbers behind it are worth carrying.** The provable worst case —
144
- a page of many minimal clusters — puts the runtime rows at about half the page size, which would
145
- over-provision the arena by fifteen times. The measured maximum, over a 160×80 torus built at 16,
146
- 32, 64 and 128 KB pages, is **3.9%**. `vgeo_runtime_page_slot_words` takes an eighth, and an install
147
- that does not fit is refused and counted rather than overrunning its slot — the same direction
148
- `register` takes for a container built at the wrong page size, and for the same reason.
149
-
150
- The page header, in its slot:
151
-
152
- | field | why |
153
- |---|---|
154
- | `node_count`, `group_count`, `cluster_count` | sub-table extents; each sub-table is a count away from the one before it |
155
- | `meshlet_base` | the metadata-pool row of this page's cluster 0 — `meshlet_index = meshlet_base + local`. The one field that moves without the page moving: the shared meshlet pool relocates batches when it compacts, and the pool follows its `changed` signal |
156
- | `geometry_row`, `ordinal` | the claim index (§4.3) and the page-lookup base |
157
- | `page_index` | the container's global page index — what a touch reports |
158
- | `decoded_size` | what this page cost the tier, so an eviction refunds what the install charged rather than re-deriving it from a RAM tier that may have dropped the page |
159
-
160
- The group row carries its `parent_count` for the same reason: **this tier answers its own activation
161
- question**. Selectability is "every parent reference is live", and which references are live depends
162
- on which pages are resident *in the tier asking* — so the RAM tier's answer is not this one's, and
163
- publishing it here is what drew a group under a coarse stand-in on the ordinary streaming path.
164
-
165
- And a per-geometry **page lookup**: `total_pages` words mapping a container page index to a page
166
- table row, or `VGEO_PAGE_ROW_NONE`. 80 KB for a 20,000-page asset, one word written per install
167
- and per evict, and it is what removes every scatter from the install path: nothing else in the
168
- runtime has to be patched when a page arrives.
169
-
170
- > **Why a lookup and not a pointer.** The obvious alternative is to store the child page's *row*
171
- > in each referring cluster row. That makes an install a scatter over every cluster that refers
172
- > into the arriving page — across pages, unbounded, and re-done on every eviction. One indirection
173
- > in the shader is cheaper than that every time.
174
-
175
- ### 3.2 Selectability
176
-
177
- The cut rule's clamp reads *selectable*, not *resident*, and `VGeoContainerReader.is_selectable`
178
- already computes it. The group row carries the bit; after an install or evict the reader's
179
- activation cascade is reflected into the group rows of the affected reader. The CPU keeps the
180
- bits in a shadow `Uint32Array` and uploads the dirty span — a page has a handful of groups, and a
181
- thousand resident pages is a few kilobytes.
182
-
183
- ### 3.3 Budgets and eviction
184
-
185
- `GPUVirtualGeometryPool` holds a VRAM byte budget, a touch stamp per page row, and evicts on
186
- design §6's rule — LRU on last-touched under a protect window no shorter than feedback latency,
187
- and never a page with resident children *in this tier*. The tier's child check is the pool's own,
188
- not the reader's: VRAM residency is a subset of RAM residency, so `resident_child_count` answers a
189
- different question.
190
-
191
- Uploads are budgeted per frame in bytes. The root page is pinned.
192
-
193
- **The binding limit is `maxStorageBufferBindingSize`, not `maxBufferSize`** — design §6 says so and
194
- names `GPUMeshletManager` as clamping against the wrong one. The VG arena clamps against the
195
- binding limit, and the failure policy is a refused install with telemetry, never a silently
196
- invalid buffer.
197
-
198
- ## 4. Phase 3 — the cut pass
199
-
200
- ### 4.1 Shape
201
-
202
- A descent that **lives inside one dispatch**, redistributing its own work across the lanes of a
203
- subgroup. Not persistent threads: WebGPU gives no forward-progress guarantee across workgroups, and
204
- an MPMC queue serviced by persistent threads is unbounded cross-workgroup waiting. But a lane may
205
- wait on the other lanes of *its own* subgroup, and that is enough.
206
-
207
- A queue item is `(instance_slot, page_row, visit, aux)` — one lane each, a node index or, above bit
208
- 31, a group index with `aux` naming one cluster inside it.
209
-
210
- ```
211
- seed: one item per visible VG instance = (instance, root page row, node 0)
212
- step: every lane executes its item and returns a production *count*
213
- subgroupExclusiveAdd numbers the wave's whole production densely
214
- the first subgroup_size of them are taken back into the wave's own lanes
215
- the rest spill to the global queue under one atomicAdd
216
- dispatch: descent_steps of those, then whatever is still held goes back to the queue
217
- ```
218
-
219
- Enqueueing a *child page* means enqueueing `(instance, child_page_row, 0, 0)` — its node tree's
220
- root. Uniform items, one kernel, no special cases.
221
-
222
- **This replaces a level-synchronous wavefront**, which ping-ponged through global memory once per
223
- level of the descent. What killed it was depth rather than geometry: measured on a 28M-face
224
- container at τ = 1 on a 4090, the descent converged at **39 levels** carrying 15,027 items — an
225
- average frontier of 385 items, six workgroups of 64 on a 128-SM part — while an empty
226
- prepare/traverse pair costs 3.9 µs whatever it contains. Page depth grows like log2(triangles), so
227
- the dispatch count got worse with better assets and never better with a faster GPU.
228
-
229
- The redistribution is Karis' "Variable sized work" (graphicrants, 2026-03) with its groupshared
230
- scatter removed; §4.10 records why that half does not port and what replaces it.
231
-
232
- ### 4.2 What one node visit does
233
-
234
- 1. Read the node metric — sphere and `max_parent_error` — in object space, against the instance's
235
- object-space eye and frustum (design §3.1: one matrix per instance, never one per cluster).
236
-
237
- *Object space costs one correction.* Error and distance are both object-space, so a **uniform**
238
- instance scale cancels out of `r_e · k / d` exactly and the projection needs no scale term at
239
- all. A non-uniform one does not cancel, and the runtime inflates `r_e` by
240
- `max axis scale / min axis scale` — conservative, so it over-refines rather than under-refines,
241
- and it is exactly 1 for every instance that is not stretched.
242
- 2. **Frustum-cull** the sphere. A node's sphere contains every `parent_sphere` under it, and
243
- monotonicity makes `parent_sphere ⊇ self_sphere ⊇ the cluster's vertices`, so a node outside
244
- the frustum discards a region.
245
- 3. **LOD-cull**: if `project(sphere, max_parent_error) ≤ τ`, nothing under this node can satisfy
246
- `project(parent) > τ`, so the entire subtree is skippable (format §6.2 — this is the field's
247
- whole purpose).
248
- 4. Interior → enqueue the `child_count` children. Leaf → enqueue a **group-chunk item** per group
249
- per `VGEO_CUT_CLUSTER_CHUNK` clusters (§4.6).
250
- 5. A group-chunk item first asks whether **its own group is selectable**, and stops if it is not.
251
- This is not decoration — see §4.4, where leaving it out double-draws.
252
- 6. Per cluster, the predicate:
253
- - `project(parent_sphere, parent_error) ≤ τ` → not drawn, and no descent.
254
- - `project(self_sphere, self_error) ≤ τ`, or `child_ref == NO_CHILD` → **draw**.
255
- - resolve the child page: `SAME_PAGE` → this row; otherwise
256
- `lookup[geometry.lookup_base + child_page_index]`.
257
- - not resident → **draw** (clamp) and **want**.
258
- - resident but the child group not selectable → **draw** (clamp), no want: there is nothing to
259
- fetch, the group is waiting on a page holding another of its parents, and some other cluster
260
- in this same cut is clamped directly against that page.
261
- - otherwise → do not draw; claim the child page and enqueue its node 0.
262
-
263
- ### 4.3 Dedup, and why page granularity is enough
264
-
265
- The page DAG is many-parented — 82 of 91 pages on a 98k sphere have more than one parent — so a
266
- descent that did not dedup would enqueue a page once per parent and draw its whole cut several
267
- times over. The claim is **one bit per (instance, resident page)**, set with `atomicOr` at
268
- enqueue, indexed `claim_base[instance] + page_row.ordinal`, where `ordinal` is the page's dense
269
- index *within its geometry* and `claim_base` comes from a prefix scan over visible VG instances of
270
- their geometry's ordinal capacity. `graph_prefix_scan_csdldf` already does this scan for
271
- mesh→meshlet expansion; this is the same pattern.
272
-
273
- Cluster-granular dedup is what this avoids, and it is worth being explicit about why it would not
274
- do: the resident cluster count is the number the design is trying not to pay per frame.
275
-
276
- ### 4.4 The correctness proofs, and the hole the first draft had
277
-
278
- Everything above only works if three things hold. All three go in the kernel's own doc comment,
279
- because they are the reason its shape is what it is.
280
-
281
- **The predicate is not the one design §4 writes down.** §4's per-cluster form is
282
-
283
- > `project(parent) > τ AND (project(self) ≤ τ OR child group non-selectable)`
284
-
285
- and evaluating exactly that, per cluster, **double-draws**. Take a cluster `C` whose own group `G`
286
- is resident but not selectable — its clusters' parents are split across pages and one of those
287
- pages has not arrived. The clamp draws `C`'s parent `P`, correctly, standing in over that surface.
288
- But `C` itself satisfies §4's form — `project(C.parent) > τ` and `project(C.self) ≤ τ` — so a
289
- flat evaluation draws it as well, underneath `P`, and the depth buffer picks between two surfaces
290
- a few microns apart. This is exactly the failure `vgeo_select_cut.spec.js` was written to catch, and
291
- the CPU traversal never meets it because *not entering a non-selectable group* is how it descends.
292
- A predicate has to say so out loud:
293
-
294
- > `own group selectable AND project(parent) > τ AND (project(self) ≤ τ OR child group
295
- > non-selectable)`
296
-
297
- Design §6 states the fact — "resident-but-inactive groups draw nothing" — without stating it as a
298
- term of the rule, because §4 was written for a traversal. The runtime evaluates the rule, so the
299
- runtime carries the term. This is the single most important line in the kernel.
300
-
301
- **No cluster is drawn twice.** A cluster is evaluated exactly once per (instance, page visit), and
302
- a page is visited at most once per instance by the claim. It is drawn only from its own page.
303
- Therefore at most one record per (instance, cluster). ∎
304
-
305
- **No cluster the flat rule would draw is missed.** Let `C` satisfy `project(C.parent) > τ`. Take
306
- any parent cluster `P` of `C`'s group. Every cluster of a group shares the consuming step's sphere
307
- and error, so `project(P.self) = project(C.parent) > τ`. Monotonicity gives
308
- `project(P.parent) ≥ project(P.self) > τ`, so `P` satisfies the predicate's first half, and by
309
- induction on the level (the root page is always visited) `P`'s page is visited and `P` is
310
- evaluated. `P` is not drawn — it fails `project(self) ≤ τ` — so it either enqueues `C`'s page or
311
- clamps, and it clamps only when `C`'s group is not selectable, in which case `C` must not be drawn.
312
- Neither cull can intervene: a node's sphere contains `P`'s `parent_sphere` and its
313
- `max_parent_error` is at least `P.parent_error`, so `project(node) ≥ project(P.parent) > τ` and
314
- the LOD cull does not fire; the frustum cull fires only when `C` is off-screen too. ∎
315
-
316
- The second proof is why *over*-visiting is harmless and *under*-visiting is not, which is the
317
- asymmetry the whole design leans on.
318
-
319
- One qualifier on the frustum cull, since the proof waves at it: a culled node can leave a genuine
320
- hole, because the parent that would have stood in is not drawn either (it fails
321
- `project(self) ≤ τ`) and the child is never reached. Everything involved is inside the culled
322
- sphere, so the hole is in geometry the frustum rejected. The agreement spec (§6) therefore runs
323
- with culling **off**, which is also how `vgeo_select_cut` defaults, and the culls are held against
324
- a separate claim: turning them on removes only clusters whose `self_sphere` is outside the
325
- frustum.
326
-
327
- ### 4.5 Outputs
328
-
329
- - **Draw records** — `MESHLET_DEFINITION_STRUCT(meshlet_base + local, mesh_index)`, appended by
330
- `atomicAdd` into the same meshlet collection the static expansion writes. Bounds-checked; an
331
- overflow increments a stat rather than corrupting the count.
332
- - **Wants** — `(source_page_row, child_ref, child_page_index, priority)`, deduped by an atomic frame
333
- stamp indexed the same way as the page lookup. Naming the page the want was *discovered through*
334
- as well as the page wanted is what **unblocks** format §10's in-flight-pin item: §10 had it open
335
- for want of anybody able to name the parent, because the format stores parent counts rather than
336
- parent references. The record names it. Nothing pins anything yet, and §10 is still open — this
337
- document claimed it closed, which it is not (REVIEW_LEDGER D4). Priority is the clamped cluster's
338
- projected error, taken from the first writer — documented as a v1 heuristic; the residency manager
339
- maxes across frames.
340
- - **Touches** — page rows, deduped by an atomic frame stamp per page row.
341
- - **Stats** — the fourteen of `VGEO_CUT_STAT`. These are what `M1`'s "selection cost measured" means
342
- and what the playground asserts on.
343
-
344
- Every append is bounded, and every bound degrades in the same direction: **coarser or missing, never
345
- doubled**. A meshlet collection that fills drops records and counts them; a claim allocation that
346
- does not fit drops the *instance* rather than running it unclaimed, because an unclaimed instance
347
- is the one failure that draws twice. τ is the dial that makes the first case go away and the
348
- scene's VG instance count is the dial for the second.
349
-
350
- **The one exception, and it is now visible.** A queue item that does not fit, or a descent that runs
351
- out of iterations with work still queued, is a subtree never descended — and the parent that would
352
- have stood in over it was not drawn either, because it failed `project(self) ≤ τ`. That is a hole,
353
- not a coarser surface, and it was the one degradation nothing counted: §4.7 below promised the
354
- kernel recorded it and no such counter existed (REVIEW_LEDGER M8, D1). `QUEUE_DROPPED` is the items
355
- lost at the append; `QUEUE_REMAINING` is what was outstanding when the descent stopped, left behind
356
- by one extra prepare pass the seam records after the wavefront loop for exactly this; and
357
- `WAVEFRONTS` is how deep it went, so reaching `max_iterations` is legible rather than inferred.
358
-
359
- ### 4.6 One lane per cluster
360
-
361
- A node leaf covers at most 4 groups and a group may hold up to 4096 clusters (format §11.5), so a
362
- kernel that walks a leaf's clusters in the lane that popped it can be handed 16,384 of them — legal
363
- input, and a loop nothing bounds. The answer used to be a **group-chunk item** per
364
- `VGEO_CUT_CLUSTER_CHUNK` clusters, which bounded the loop at 8 and paid for it in the memory
365
- pattern: lane *i* walking clusters `8i..8i+8` puts 32 lanes on 32 separate cache lines at every
366
- step, over rows that are contiguous.
367
-
368
- There is no chunk any more, because the bound now comes from the redistribution. A leaf's
369
- production is **every cluster its groups hold**, the wave packs them one per lane, and consecutive
370
- lanes read consecutive cluster rows. `cluster_chunk` is gone from the params.
371
-
372
- An item is `(instance, page_row, visit, aux)`, with bit 31 of `visit` distinguishing a node index
373
- from a group index and `aux` naming the cluster inside that group. One kernel, one queue, two
374
- shapes.
375
-
376
- ### 4.7 Two bounds, and what each one costs
377
-
378
- Depth along a path is page depth × (node depth + one group step) — 12 and 1–3 when this was
379
- written, 39 levels measured on the 28M-face container. That depth is now paid **inside** a dispatch:
380
- `descent_steps` (default 32) steps of the loop above, and `max_iterations` (default 8) dispatches
381
- for the breadth that spills out of it. Measured on that container, the descent converges in **five**
382
- dispatches with work, against 39 before.
383
-
384
- WGSL is what makes `descent_steps` a tuning knob rather than a safety bound. A loop around a
385
- subgroup operation needs a uniform trip count and nothing launders one — not `subgroupAdd`, not
386
- `subgroupMax`, not `subgroupBroadcastFirst`, which launders a value for a use but not for control
387
- flow — and it may not carry a non-uniform `break`, `continue` or `return`. So the descent cannot
388
- stop when the wave runs dry: it runs the full count and masks, at roughly ten instructions a step
389
- for a wave with nothing left. Both numbers trade idle steps against dispatches, and neither is safe
390
- by construction. `VGEO_CUT_STAT.QUEUE_REMAINING` is what says a pair was set too low — measured, it
391
- reads 207 outstanding at 64×4 and 8,332 at 96×3, where `DRAWN` falls short by the same shape.
392
-
393
- "Records whether the queue was non-empty at the end" is what this paragraph promised and what it did
394
- not do: nothing anywhere recorded it, which made the descent's one hole-shaped failure the only one
395
- with no reading (REVIEW_LEDGER M8). `VGEO_CUT_STAT.QUEUE_REMAINING` is that number now, left behind
396
- by one extra prepare pass after the loop, and `WAVEFRONTS` says how many iterations actually ran.
397
- Measured on the playground's 13-page torus through a real adapter: fourteen wavefronts at τ = 0.5,
398
- eight at τ = 16, and nothing outstanding at any threshold.
399
-
400
- ### 4.10 What WGSL would not let the redistribution be
401
-
402
- Karis' `DistributeWork` scatters each producer's packed state into a groupshared slot and has every
403
- consumer ballot for the nearest preceding one. That needs the scatter's writes visible across the
404
- wave before the gather, and HLSL spells it `GroupMemoryBarrier()` — memory ordering with no
405
- execution sync, sound because a wave is in lockstep.
406
-
407
- WGSL has no memory-only workgroup barrier. `workgroupBarrier()` is an execution barrier, the batch
408
- loop's trip count is per-subgroup, and two subgroups of one workgroup entering it a different number
409
- of times is a deadlock. Tint refuses it: *"'workgroupBarrier' must only be called from uniform
410
- control flow"*.
411
-
412
- So the kernel finds each item's producer by **binary search over the same prefix sums** instead of
413
- scattering to it — the owner is the largest lane whose base is at or below the index, which stays
414
- correct when empty lanes repeat a base because a repeat sits at the start of the next producer's run
415
- rather than the end of the previous one. `log2(subgroup_size)` shuffles, no workgroup memory and no
416
- barrier anywhere in the file.
417
-
418
- Probed on Chrome against a real adapter, because two of the rules are not what they look like:
419
-
420
- | | |
421
- | --- | --- |
422
- | a non-uniform `if` that opens and closes | fine — Tint models reconvergence |
423
- | `workgroupBarrier` after one | fine |
424
- | `subgroupShuffle` with a non-uniform lane id | fine |
425
- | a non-uniform-bounded loop with no subgroup op inside | fine, and taints nothing after it |
426
- | loop bound: literal, `subgroup_size`, uniform buffer field | fine |
427
- | loop bound through `subgroupAdd` / `subgroupMax` / `subgroupBroadcastFirst` | rejected |
428
- | non-uniform `break` / `continue` / `return` in a loop with a subgroup op | rejected |
429
-
430
- The emulator is the other half of this. It runs one invocation at a time and refused the reductions
431
- and shuffles on the argument that "a reduction over one lane would pass tests a real wave fails" —
432
- right about what they cover, wrong that there was an alternative. A subgroup kernel must be correct
433
- at *every* width, WGSL promising nothing about which one a device hands it, so one is a
434
- configuration rather than a stand-in, and refusing to model it left the kernel with no CPU-tier
435
- coverage at all. It has them now, at width one, and `vgeo_cut_agreement.spec.js` drives the real
436
- `main` through them. What that tier cannot see is the redistribution itself; that is a device test.
437
-
438
- ### 4.8 What the review took out: the child-table metric is not a cut structure
439
-
440
- The plan's first draft claimed the child-table metric copy as the cut's pre-fetch cull, which is
441
- what plan §2.3 and format §6.2 both predict. Held against the kernel above, it buys **nothing**,
442
- and the reason is worth writing down so nobody adds it back:
443
-
444
- A want is emitted from a cluster that has already passed `project(parent) > τ` and
445
- `project(self) > τ`. The child page's metric covers that cluster's child group among others, so
446
- its `max_parent_error` is at least that group's `parent_error` and its sphere contains that
447
- group's `parent_sphere` — and the projection is monotone in both. So `project(child page metric)`
448
- is **always** greater than τ at exactly the moment the question is asked. The page-level test
449
- cannot reject what a per-cluster test has just accepted, because the per-cluster test is strictly
450
- tighter. The same argument kills the frustum half: the cluster's own `self_sphere` is a tighter
451
- frustum test than the child page's union sphere, available locally, for free.
452
-
453
- Where the metric copy *is* load-bearing is **prefetch** — walking a resident page's child table to
454
- rank pages nobody has asked for yet, which by definition has no cluster-level evidence to be
455
- tighter than. That is plan §3's territory and not this campaign's. `min_self_error` is in the same
456
- position: it ranks a want, and the cut never asks it anything.
457
-
458
- So plan §2.3 half-closes. The node tree gets its consumer; the child-table metric keeps waiting,
459
- for a named reason rather than for want of anyone to build it.
460
-
461
- ### 4.9 Binding budget
462
-
463
- The engine requires a device to offer 10 storage buffers per stage and refuses one with fewer, so
464
- a kernel that declares more cannot run anywhere. The traversal declares **eight**:
465
-
466
- `vg_lookup`, `vg_arena`, `instances`, `queue_in`, `queue_out`, `claim`, `feedback`, `out_meshlets`
467
- — plus one uniform.
468
-
469
- This list was wrong in two directions when it was written, which is why
470
- `vgeo_cut_shaders.compile.spec.js` now asserts the exact count of every kernel rather than only the
471
- bound. It named `stamps`, which is not a buffer — the touch and want stamps are *regions of*
472
- `feedback`, which is what makes them per-frame and shared across every view and bucket — and it
473
- omitted `vg_lookup`, which the descent is gated on. And `vg_pages` is gone: a page's header is the
474
- first words of its own arena slot (§3.1), so the table that used to hold it is not a binding any
475
- more. That is the headroom this kernel did not otherwise have.
476
-
477
- It remains the reason the node/group/cluster tables share one arena rather than being three buffers,
478
- which is worth recording because "a table each" is the obvious first draft.
479
-
480
- `claim` and `stamps` are separate because their lifetimes are: the claim is per *invocation* and
481
- cleared, since each expansion site sees a different instance set; the touch and want stamps are
482
- per *frame* and shared across every view and bucket, so they are a persistent buffer compared
483
- against a monotonic frame counter and never cleared at all. Merging them would either clear the
484
- stamps (duplicating every touch and want across buckets) or stop clearing the claim (drawing every
485
- VG instance's cut once per bucket).
486
-
487
- ## 5. Phase 4 — the seam
488
-
489
- ```js
490
- const gr_bucket_meshlets = graph_expand_meshes_to_meshlets({ graph, input_meshes, ... });
491
-
492
- graph_vgeo_expand_instances_to_meshlets({
493
- graph, input_meshes, output_meshlets: gr_bucket_meshlets, view, vg, limits
494
- });
495
- ```
496
-
497
- One call beside each of the **three opaque** expansion sites, appending into the same collection.
498
- Everything downstream — the HZB meshlet filter, the material bucket, the indirect draw, the
499
- G-buffer — is untouched, because the record type and the metadata pool are the ones it already
500
- reads.
501
-
502
- Three, not the four `graph_expand_meshes_to_meshlets` has (REVIEW_LEDGER D3). The fourth is
503
- `prepare_meshlet_draw_commands_by_material`, which serves the transparent and alpha-tested buckets
504
- and is handed a scene and an optional camera *resource* rather than a view context — where a cut
505
- needs the camera's uniform buffer to put each instance into object space. DESIGN §9 scopes non-opaque
506
- VG instances out of v1, and `GPUSceneRows.attach_mesh` says so out loud when one is attached, because
507
- a VG instance in that path expands to zero meshlets and draws nothing at all.
508
-
509
- And one thing the seam has to refuse: a rasterization path recording on a **different**
510
- `FrameGraph`. `GPUSceneShadowmapContext` reaches `graph_rasterize_scene_fast_raw`, which carries the
511
- seam, on a graph of its own — so a cube face inside an open frame would run a whole cut at the main
512
- camera's projection scale into a resource handle belonging to a graph that has never heard of it.
513
- `graph_vgeo_expand_if_enabled` checks the graph against the one the frame was opened on and skips.
514
-
515
- Three things make that a one-line seam rather than a new pipeline:
516
-
517
- - **The instance list is already the right one.** Each expansion site is handed a set of visible
518
- mesh instances for one bucket and one HZB pass. Those sets are disjoint (the two-way filter
519
- splits positive from maybe), so running the cut over each of them costs one cut per view in
520
- total, and a VG instance cannot be cut twice.
521
- - **VG meshes expand to zero static meshlets.** `geometry.meshlets_count` is 0 for a VG geometry,
522
- so `shader_meshes_to_meshlet_counts` already contributes nothing for them.
523
- - **`GEOMETRY_METADATA_STRUCT` gains one field**, `vg_row`, `0xFFFFFFFF` for everything that is not
524
- virtual. That is how a shader tells a VG instance from a static one.
525
-
526
- ## 6. Phase 5 — proof
527
-
528
- Four tiers, in the order they catch things.
529
-
530
- 1. **WGSL parse + validate + binding budget**, as every shader module in this tree has.
531
- 2. **The agreement spec — the one that matters.** Build a torus with `vgeo_build`, install it into
532
- a real `VGeoContainerReader`, mirror it into a CPU model of the arena, and run the cut kernels
533
- under `ComputeShaderEmulator`. Assert the **drawn cluster multiset is exactly** what
534
- `vgeo_select_cut` returns, over a sweep of camera positions and τ, both fully resident and with
535
- pages withheld. Multiset, not set: a duplicate is the failure mode that reads as corrupt
536
- geometry rather than as a missing page, and a set comparison cannot see it.
537
-
538
- **At 64 KB pages, not the 8 KB `vgeo_select_cut.spec.js` uses** (REVIEW_LEDGER D2). The 8 KB fixture
539
- is the one that puts a group's parents on either side of a page boundary thirteen times over,
540
- which is what *that* spec is for — and it is the one fixture in which this spec's headline case
541
- cannot occur: measured, 8 KB puts exactly one group in every page of this torus, so a blocked
542
- group is a blocked page, no page is ever mixed, and the cut's own-group term is unreachable.
543
- At 64 KB the median page holds eight groups and the state is ordinary.
544
-
545
- What the spec withholds matters as much as what it builds. Withholding from the **reader** makes
546
- a page missing from both tiers, which is the easy case; withholding from the **tables** alone
547
- makes VRAM a strict subset of RAM, which is the ordinary streaming state and the one every test
548
- here used to skip. See C1.
549
- 3. **`SoftwareGPUDevice`** for the pool's orchestration — install, evict, budget, page-row reuse
550
- and the meshlet manager's allocations — where no shader has to run. Not the arena's growth:
551
- there is none, and has not been since the fixed-stride conversion (REVIEW_LEDGER C3). The
552
- same tier is what holds the **seam**, which records passes rather than running them:
553
- `graph_vgeo_expand.spec.js` compiles the cut onto a real `FrameGraph` and asserts what came out
554
- of the recording, and `VGeoFeedbackLoop.spec.js` runs a readback that really lands.
555
- 4. **The playground**, `playground/vgeo_runtime/`, drawing a streaming container through the real
556
- meshlet pipeline with a `__state()` a headless Chrome session can assert on: drawn triangles
557
- track τ and resolution and not source triangle count; clamped falls to zero as pages arrive;
558
- the same τ sweep is watertight.
559
-
560
- ## 7. Order, and what each step leaves green
561
-
562
- | step | landed | proved by |
563
- |---|---|---|
564
- | 1 | page decode: metrics, node table, `parent_sphere`, `aabb`, `payload_size` | `vgeo_read_page.spec.js`, against the writer's bytes and the payload's own positions |
565
- | 2 | record structs + the arena + the pool | `GPUVirtualGeometryPool.spec.js` on a `SoftwareGPUDevice` — whole-buffer comparison against the tables, since a dirty range that was not marked leaves a page pointing at another page's clusters |
566
- | 3 | the cut kernels | `vgeo_cut_agreement.spec.js`, against `vgeo_select_cut`, multisets, seven cameras × four thresholds, resident, RAM-withheld and **VRAM-withheld**, one geometry and two, culling on and off, and the collect pass's own record rather than a fabricated one |
567
- | 4 | the seam + `vg_row` + `VirtualGeometry` | `graph_vgeo_expand.spec.js`, on what the seam records onto a real `FrameGraph`: the passes, the resource each is bound to, the feedback version it publishes, and a graph that is not the frame's getting nothing. It says three opaque call sites — and **nothing else**; see below |
568
- | 5 | feedback readback → residency | `VGeoFeedbackLoop.spec.js` on a real frame graph, plus the playground: 13 pages, one resident, streamed to 9 by the cut's own wants |
569
- | 6 | playground + headless drive | an nvidia/lovelace adapter; the τ sweep in `playground/vgeo_runtime/README.md` |
570
-
571
- **Step 4's row said "4,667 engine specs green", and that was not evidence** (REVIEW_LEDGER P7). Not
572
- one of those specs executed a line of the seam: they were green before it existed and green after,
573
- which is a statement about the seam not breaking anything rather than about the seam working. A row
574
- of a proof table has to name a test that would go red.
575
-
576
- Replacing it with "step 6, the headless drive" was closer and still not enough. The drive is a
577
- whole-system check on one adapter and it cannot be run per commit; a seam defect that the drive's
578
- six numbers happen not to move — the tail pass bound to the wrong queue at an odd `max_iterations`,
579
- say — is invisible to it. The row above names `graph_vgeo_expand.spec.js`, which is the tier that
580
- can go red on one line of the seam, and the drive stays what it is: the only tier that has ever
581
- noticed the playground failing to boot at all (REVIEW_LEDGER, re-review 2026-09-12).
582
-
583
- One thing that step 6 found and no earlier tier could: the feedback resource is **versioned**, and a
584
- second expansion site that wrote version 0 again after the first had produced version 65 is reading
585
- stale contents while claiming not to. The FrameGraph refuses to compile a graph that does it, which
586
- is why `GPUVirtualGeometryRuntime.publish_feedback` exists at all.
587
-
588
- ## 8. What the adversarial review changed
589
-
590
- Kept here rather than silently folded in, because three of these are things a reader would
591
- otherwise re-derive — and the first one is a bug the plan shipped with.
592
-
593
- 1. **The predicate double-draws as design §4 writes it.** A resident-but-not-selectable group's
594
- clusters satisfy it while their coarse stand-in is also drawn. The rule needs an *own group
595
- selectable* term that a traversal gets for free and an evaluator does not. §4.4.
596
- 2. **The child-table metric is not a cut structure.** The per-cluster test is strictly tighter at
597
- the moment the want is emitted, so the page-level test can never reject anything. Its consumer
598
- is prefetch. §4.8. Plan §2.3 predicted the cut would read both accelerators; it reads one.
599
- 3. **A node leaf's cluster loop was unbounded** — 4 groups × 4096 clusters in one lane, on legal
600
- input. Group-chunk queue items bound it and widen the common case. §4.6.
601
- 4. **`claim` and `stamps` cannot share a buffer**, because one is per-invocation-and-cleared and
602
- the other is per-frame-and-not. Merging them either duplicates every want across buckets or
603
- draws every instance once per bucket. §4.9.
604
- 5. **Object space needs a scale correction** for non-uniform instance scale, and *only* for
605
- non-uniform: the uniform case cancels exactly, which is worth knowing before adding a term that
606
- does nothing. §4.2.
607
- 6. **Overflow must degrade in one direction.** A full meshlet collection drops records; a claim
608
- allocation that does not fit drops the instance rather than running it unclaimed. §4.5.
609
- 7. **A frustum cull can leave a real hole**, in geometry the frustum rejected. Named, and the
610
- agreement spec runs with culling off for that reason rather than by accident. §4.4. What the
611
- review did not say is that culling is **on** by default and therefore had no oracle at all, which
612
- is how a near plane extracted with the wrong sign shipped (REVIEW_LEDGER M1, M2). It has one now:
613
- turning culling on removes only clusters whose own sphere is outside the frustum.
614
-
615
- ### 8.1 What §8 got wrong, and the sweep that found it
616
-
617
- The adversarial review above is the author's own, and a second one on 2026-09-11 — twelve reviewers
618
- over disjoint dimensions, every finding attacked by three verifiers — contradicted it in three
619
- places and found ~35 distinct defects it had not. [REVIEW_LEDGER.md](./REVIEW_LEDGER.md) is that
620
- sweep, what it cost to close, and what it itself got wrong.
621
-
622
- Three of its findings are worth carrying here rather than only there, because they are about the
623
- shape of the thing rather than about a line of it:
624
-
625
- - **A tier has to answer its own residency questions.** Selectability, the bytes an eviction
626
- refunds, and eviction's child rule were all taken from the RAM tier by the VRAM tier, and each of
627
- them is a different answer over a different set. The VRAM tier holds what it needs to answer them
628
- now (§3.1).
629
- - **A readback is about a frame that has gone.** The layout it is decoded with, and the identity of
630
- every page row it names, belong to that frame and not to this one. Both are snapshotted when the
631
- reading is scheduled.
632
- - **Proof tiers are not interchangeable.** Nothing below the headless drive exercises the
633
- engine-integration path, and the suite being green says nothing about it. §6's fourth tier is a
634
- required check.
635
-
636
- ## 9. What this does **not** do, deliberately
637
-
638
- - **No proxy geometry, no shadow-view cuts** (plan §3.2, §3.3). One cut per view is what §4 pins;
639
- a view that does not justify a cut taking a proxy instead is the next campaign, not this one. The
640
- seam enforces it rather than assuming it: a rasterization path recording on a `FrameGraph` other
641
- than the one the frame was opened on is skipped (§5).
642
- - **No position quantization** (plan §4.2). It is a `payload_encoding` id and a decode-shader
643
- change, and it is orthogonal to every line here.
644
- - **No GPU-driven allocation** (plan §3.4). The arena is CPU-allocated, one fixed-stride slot per
645
- page, which is the shape that makes a device-side allocator a later swap rather than a rewrite —
646
- and slots make it a smaller swap than variable blocks would have.
647
- - **No slot-size classes.** One page size per pool, checked at `register` and refused otherwise
648
- (§3.1). A tier that reconciled two page sizes would need a table of slot sizes, and that is a
649
- later change than this one.
650
- - **No non-opaque VG instances** (design §9). The cut runs beside the opaque expansions only, and a
651
- VG instance with a transparent or alpha-tested material draws nothing — said out loud at
652
- attachment rather than discovered on screen.
653
- - **No compaction of `GPUMeshletManager` under VG churn.** Growth appends and moves nothing;
654
- compaction is still a CPU-side rebuild and still has the unhandled address-migration TODO
655
- downstream. VG pages are near-uniform in size, so the holes an eviction leaves are re-usable by
656
- the next install, and the campaign to fix compaction properly is plan §3.4's.
657
- - **No disk tier** (design §6). CDN + RAM + VRAM.