@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,529 +0,0 @@
1
- # Virtual geometry casts shadows — the plan
2
-
3
- **Closed 2026-09-29.** Everything below is done, withdrawn or measured moot, and §5 is what it
4
- deliberately leaves out.
5
-
6
- **2026-09-28, revision 3**, after an adversarial pass over revision 2 (three reviewers against the
7
- code; §6 is the ledger, and the sections it changed say so). **S1–S5 are done, S6 is withdrawn, and shadow
8
- views cut at twice the camera's τ; §7 is what each step measured.** It changes no decision in
9
- `VIRTUAL_GEOMETRY_DESIGN.md` — §4 already pins one cut per view and withdrew the claim that shadow
10
- views could reuse the camera's — and it takes `RUNTIME_PLAN.md` §9's "no shadow-view cuts" out of
11
- the list of things v1 does not do.
12
-
13
- ## Decided
14
-
15
- 1. **One cut per view, and a shadow view is a view.** Cascades, spots and a point light's cube faces
16
- go through the same seam, the same per-view state and the same rules as the main view. Nothing in
17
- the VG runtime knows which kind of view it is cutting for.
18
- 2. **The engine owns the VG frame.** It is a core feature; a host does not open, read or close it.
19
- 3. **VG shadows are not optional.** How is open; whether is not.
20
- 4. **Proxies are not built now, and the architecture leaves room for them.** Nanite-style proxies
21
- drawn in place of a cut for some views, behind a switch, may come later; §2.6 is where they would
22
- go and what this work must not close off.
23
- 5. **The order is §4's.**
24
- 6. **Shadow views cut at twice the camera's τ** *(after S4)*. The shadow context sets
25
- `tau_scale` to 2 on every view it owns (`SHADOWMAP_VGEO_TAU_SCALE`); §7.2 is the price.
26
- 7. **No batched cascade cut** *(after S5)*. Cascades are boxes fitted to separate slices of the view
27
- frustum — misaligned, overlapping only at the blend margins — so one descent over all three
28
- shares no traversal work, only dispatches. §3 item 2 and S6 are withdrawn.
29
-
30
- Revision 1 argued from a measured match between a cascade's texel and the screen pixel at the far end
31
- of its slice. That argument is withdrawn: what a view's cut needs is a function of what projects into
32
- *that* view, and §1 says why the camera's view says nothing about it.
33
-
34
- ## 0. What is true today — measured
35
-
36
- ### 0.1 Virtual geometry receives shadows and never casts them
37
-
38
- Measured on `playground/vgeo_scene` with the textured Zorah rebuild (1,012 containers, 12,597
39
- instances, the scene's own sun), headless Chrome on the 4090, 1556×592. Each condition is a
40
- 32-frame average after TAA has settled; the column is the fraction of pixels that go more than 16
41
- luma darker when shadows are switched on.
42
-
43
- | view | only VG in view | plus two ordinary `BoxGeometry` casters |
44
- |---|---:|---:|
45
- | authored | 0 | 0.110% |
46
- | oblique | 0 | 0.076% |
47
- | plan | 0 | 0.106% |
48
-
49
- Two identical captures differ by the same 0 at that threshold, so the VG column is noise. The boxes'
50
- shadows land on VG ground and are the only thing in the on/off difference; the dome and the
51
- greenhouse, under a sun 52° above the horizon, leave nothing on it.
52
-
53
- Before the far-plane fix of 2026-09-28 the page hid every shadow, not only VG's: `look()` set
54
- `camera.far = distance × 2000`, and `DirectionalLightShadowmap.update` takes `camera.far` as its
55
- shadow distance, so from the authored viewpoint the nearest cascade spanned 0–5.8 km. The page now
56
- fits the far plane to the world's bounding sphere, capped at twenty times the orbit distance, and
57
- the numbers above are from that version.
58
-
59
- ### 0.2 Why, in the code
60
-
61
- Three things each stop it, in the order a frame meets them:
62
-
63
- - **The frame is not open yet.** `Renderer.render` calls `sm.select_for_draw` / `sm.draw` before it
64
- creates the `Shading` graph, and the VG frame is opened by the host's `RenderExtension` at
65
- `FramePhase.FrameStart` — on that graph. Every shadow view is recorded while
66
- `runtime.feedback === -1`, and `graph_vgeo_expand_if_enabled` returns at its first check.
67
- - **The graph is not the frame's.** Each shadow map records on a `new FrameGraph("shadows")` of its
68
- own, and the feedback buffer is a transient of the `Shading` graph. Were the frame open, the
69
- second check (`runtime.frame_graph !== graph`, REVIEW_LEDGER M13) would skip anyway, and must:
70
- the handle names a resource that graph has never heard of.
71
- - **An orthographic view has no projection the cut can take.** `VGeoViewCut.set_projection` reads
72
- `view.camera.camera.fov`; a cascade's `OrthographicCamera` has none, and `vg_project` divides by a
73
- distance that means nothing to a parallel projection.
74
-
75
- Nothing else stops it. The seam already sits in both rasterization paths shadow views use —
76
- `graph_rasterize_scene` for cascades and spots, `graph_rasterize_scene_fast` for a point light's cube faces — and
77
- every `GPUViewContext`, shadow views included, already owns a `VGeoViewCut`. The cascade fit's
78
- caster range (`shader_csm_caster_range`) already reads every `meshes` row's world bounds, VG rows
79
- among them, so the near planes already reach VG casters.
80
-
81
- ### 0.3 What a cut costs now
82
-
83
- `GPUProfileSession` at `TIMING`, 90 frames, medians, the same page, `max_iterations` 16 (escalated),
84
- `descent_steps` 24:
85
-
86
- | | authored | oblique |
87
- |---|---:|---:|
88
- | GPU frame, sum of spans | 2,903 µs | 2,931 µs |
89
- | `VGeo cut traverse`, 48 dispatches | 984 µs | 814 µs |
90
- | `VGeo cut queue prepare`, 48 dispatches | 60 µs | 60 µs |
91
- | collect + seed, 3 each | 29 µs | 35 µs |
92
- | per site: traverse | 815 / 120 / 46 µs | 630 / 98 / 70 µs |
93
-
94
- The main view records **three cut sites** — the per-opaque-bucket seam of
95
- `graph_rasterize_partial_opaque` plus the one in `graph_rasterize_remaining_opaque` — and the work
96
- is almost all in the first. A site with nothing to do still pays its ladder: sixteen traverse
97
- dispatches at ~3 µs, sixteen prepares, collect, seed, the scan and the remainder, about **80 µs**.
98
- The cut is already the largest line in the frame, over a third of it.
99
-
100
- ## 1. Why a shadow view needs its own cut
101
-
102
- **A cut is a function of what projects into the view it is drawn into, and a caster's projection into
103
- a shadow map has nothing to do with its projection onto the screen.** A small object high above the
104
- observer is a speck to the camera — or outside its frustum altogether — and throws a large shadow on
105
- the ground in front of it. That shadow needs the caster's fine levels; the camera's cut holds its
106
- coarse ones where the camera sees it at all, and nothing where the camera does not. No property of
107
- the camera's view bounds what a shadow view needs, so a shadow view gets its own cut exactly as the
108
- main view does, with its own projection and its own threshold in the units of its own render target.
109
-
110
- It follows that **a shadow view is a streaming client in its own right.** Its demand is not a subset
111
- of the camera's — the speck in the sky wants pages the camera will never ask for — so its wants reach
112
- residency on the same terms as the main view's (§2.4), and residency, which is already scene-wide,
113
- arbitrates between them.
114
-
115
- **This is what the references do.** Unreal's Nanite renders every virtual-shadow-map view with its
116
- own culling and LOD, against that view's resolution; Bevy's meshlet renderer runs a full cull and
117
- raster per shadow view, and its LOD test reads the bound view's own projection and viewport. Neither
118
- reuses the camera's cut. The one reference that does, NVIDIA's `vk_lod_clusters`, traces its shadow
119
- rays against the camera's cut and documents the cost: off-screen instances cast from a coarser level,
120
- and their shadows change when they come into view.
121
-
122
- **Self-shadowing is measured, not argued.** A surface that is both receiver and caster is drawn at
123
- the camera's level in the G-buffer and at the shadow view's level in the map. The receiver bias in
124
- `chunk_sample_shadowmap_atlas_for_light` is a normal offset and a slope-scaled depth offset in texels,
125
- over a 5×5 filter; whether that absorbs the mismatch is S3's measurement (§4), and a new bias term is
126
- added only if it fails.
127
-
128
- ## 2. What changes
129
-
130
- ### 2.1 The engine owns the VG frame *(rev 3)*
131
-
132
- The renderer opens the frame for a scene with virtual geometry enabled, **before the shadow block and
133
- outside its `if`** — so a frame with shadows off still opens and closes — and schedules the readback
134
- and closes the frame inside the `Shading` graph's recording, after the last phase and before
135
- `encodeGraph`, in a `finally` so a throwing phase cannot leave it open. `VGeoFrameStart` and
136
- `VGeoFrameEnd` are deleted from **all three** hosts — `vgeo_scene`, `vgeo_runtime` and `caldera`. That
137
- closes REVIEW_LEDGER U1: `GPUSceneContext` constructs the runtime, residency and `VGeoFeedbackLoop`
138
- today, and nothing in the engine drives them. The frame is opened *for* the view being rendered, which
139
- takes slot 0 (§2.4); that is the frame's own view, not a kind of view.
140
-
141
- The feedback buffer stops being a graph transient. It becomes a buffer the runtime owns
142
- (`STORAGE | COPY_SRC | COPY_DST`), re-made when its layout grows, and **imported** into each graph that
143
- cuts via `FrameGraph.import_resource` — a write to an imported resource marks the pass side-effecting,
144
- so nothing is culled. The seam asks the runtime for the graph's handle (`feedback_in(graph)`), and the
145
- first graph of a frame to import it records a pass that **clears the counter and touch words in-stream**
146
- (`clearBuffer`, no upload; the want records need no clearing). M13's hazard — a handle from one graph
147
- named in another — then cannot be written. Ordering between graphs needs nothing: `encodeGraph`
148
- compiles and executes each graph into the frame's one encoder as it is handed over, and there is one
149
- submit per render.
150
-
151
- `GraphicsEngine.render_to_sampler` renders eight times per call and each is a frame, which is the
152
- right semantics — each is a render of a view — and it advances the residency's protect clock eight
153
- times; noted, not changed.
154
-
155
- ### 2.2 The cut takes an orthographic projection *(rev 3)*
156
-
157
- **One derivation, from the projection matrix the view actually draws with** — no camera-type branch in
158
- the seam, and no widths the old setter could not take:
159
-
160
- k = ½ · max(width · |P[0]|, height · |P[5]|) w = −P[11]
161
-
162
- For this engine's infinite reverse-Z perspective that is `height / (2 tan(fov/2))` and `w = 1`; for its
163
- reverse-Z orthographic, `height / (top − bottom)` on the finer axis and `w = 0`. Anything else asserts.
164
- `vg_project` divides by `w · d + (1 − w)` and tests **that** for `≤ 0`, so an orthographic camera whose
165
- transform sits inside a root sphere — a cascade's centre usually does — is not taken as an eye inside
166
- the sphere.
167
-
168
- The weight lives **in the instance record** (`VGEO_INSTANCE_OFFSET.DISTANCE_WEIGHT`), not only in the
169
- params, so the traversal never reads a per-view uniform for it. The collect's scale term is
170
- `k · σmax / (w · σmin + 1 − w)`: σmax/σmin in perspective, as today, and σmax alone orthographically,
171
- where no distance cancels a scale. `vgeo_select_cut` gets the same term.
172
-
173
- **The proof has to discriminate the scale term, not just the formula** *(rev 3)*: the oracle has no
174
- instance transform, so agreement alone would check the GPU against the same formula it was given. S1
175
- adds a direct collect check at a uniform scale of 7 (σmax = 7 against σmax/σmin = 1) and a metamorphic
176
- one — an instance scaled by s in an orthographic view cuts exactly like an unscaled one at s times the
177
- extent — plus the orthographic frustum held against `construct_frustum_planes`.
178
-
179
- Bevy's `lod_error_is_imperceptible` has this shape, and its orthographic arm is where its bug was: PR
180
- #19318 fixed a metric that had left shadows visibly faceted.
181
-
182
- ### 2.3 τ is the runtime's, scaled per view *(rev 3)*
183
-
184
- Revision 2 made τ an absolute per-view value. That splits the one quality lever — the playgrounds'
185
- slider sets `runtime.tau`, and an absolute shadow τ of 2 becomes *finer* than the main view the moment
186
- the slider goes to 4 — and the reference it cited is relative. So a view carries **`tau_scale`**,
187
- default 1, and cuts at `runtime.tau × tau_scale`. Whoever owns a view may set it; nothing in the runtime
188
- knows what kind of view it is. S4 measures before anything but 1 is set.
189
-
190
- **Shadow views default to 2** *(after S4)*: the shadow context sets it on every view it owns
191
- (`SHADOWMAP_VGEO_TAU_SCALE`), and the runtime still knows nothing of what kind of view it cuts for.
192
- At 2 the cascades draw 39% of the clusters they do at 1, and false light against a fine reference
193
- rises 2.3–4.7× (§7.2).
194
-
195
- ### 2.4 Feedback is per view *(rev 3)*
196
-
197
- Each view that cuts in a frame takes a **slot**. Per slot, the layout carries a header — the want
198
- counter and the stats block — and a want slice; the touch bits after the headers stay shared, since a
199
- page any view draws from is a page in use. The view's params hold its offsets (`stats_base`,
200
- `want_count_offset`, `want_base`, `want_capacity`), so this is parameters, not bindings: every kernel
201
- that writes feedback already binds `params`, and the traverse is at its tenth storage buffer with no
202
- eleventh. `want_capacity` keeps its meaning — pages one frame may ask for across every view — and is
203
- divided evenly among the slots, so the readback does not grow with the view count.
204
-
205
- - **Slots.** The frame's own view is slot 0. Other views take the next slot the first time they cut
206
- in the frame. The layout is sized to a **high-water mark** of slots used, like `max_iterations`: a
207
- view beyond it shares the last slot for that frame, and the next frame has room. The mark is capped,
208
- so a frame that draws many point lights for the first time — first draws bypass the shadow budget —
209
- cannot grow the buffer without bound.
210
- - **Wants: one stamp tag per (frame, slot).** The stamp was taken *before* the capacity check, so a
211
- full shadow slice burned the stamp of every page it could not write and the main view, recording
212
- later, could not ask for it that frame; and the recorded priority was the first writer's. With a
213
- tag per view, each view dedups its own wants and records them in its own slice; a page two views want
214
- appears twice, and `VGeoResidency` already collapses the second (the page is pending or resident by
215
- then).
216
- - **Priority is error over the view's own τ**, so a want at twice its view's threshold ranks the same
217
- whether that view counts screen pixels or texels. Eviction stays LRU over touches; refusals per slot
218
- are what S4 measures.
219
- - **Escalation is per view.** `max_iterations` moves from the runtime onto `VGeoViewCut`, starting
220
- from the runtime's and raised from that view's own slot. One far cascade or one cube face of a point light that
221
- needed more wavefronts must not double every site's ladder for the life of the scene. The readback
222
- closes over the slot → view table with the layout, since it lands frames later and the set of views
223
- changes.
224
- - **Stats** are per slot; `VGeoFeedbackLoop.stats` is slot 0's, which is what the panels show, and each
225
- view's last reading is on its `VGeoViewCut`.
226
-
227
- ### 2.5 What does not change
228
-
229
- The seam's call sites, the record type, the HZB meshlet filter, the draw, the shadow atlas, the
230
- cascade fit and the caster range.
231
-
232
- **Cached local maps** *(rev 3)*. A spot or point map drawn once is kept until `shadowmap_update_score`
233
- re-selects it. Its first draw is forced at creation, when little but the roots is resident, and between
234
- redraws it touches nothing, so the LRU evicts exactly the pages only it needs. Left alone it would keep
235
- a root-level cut indefinitely. So a map whose own views' last reading was **clamped**, and under which
236
- pages have installed since, is re-selected like an invalid one. That keys on the map's own incomplete
237
- cut rather than on every LOD change, which keeps it clear of an invalidation storm when pages stream in.
238
-
239
- ### 2.6 Room for proxies — not built
240
-
241
- A proxy is a fixed, cheap stand-in for a whole geometry — Nanite's fallback mesh — drawn **instead of**
242
- a cut for a view that does not justify one. If it comes, it comes as a per-view choice, and this work
243
- keeps the two places it would need open:
244
-
245
- - **The choice is made per (instance, view).** Today that is the seam, which already receives the view:
246
- it would either cut or append the instances' proxy meshlets.
247
- - **A proxy's meshlets would hang off the VG geometry row, never off `VirtualGeometry.meshlets`.**
248
- `meshlets.count` is zero for a virtual geometry, and that is what keeps static expansion from
249
- drawing a VG instance in every view; filling it with a proxy would draw the proxy *and* the cut
250
- everywhere.
251
-
252
- That room is for **raster** views *(rev 3)*: ray-traced shadows read `geometries.blas`, built from
253
- static meshlets, so a proxy for RT needs the BLAS builder to take an input from the VG row as well.
254
-
255
- ## 3. Cost *(rev 3)*
256
-
257
- **Sites** *(before S5)*. A view cuts once per active opaque bucket in phase one plus once in phase two (standard
258
- path: cascades and spots, ~3 sites each), and once per active bucket in the fast path (a point light's cube faces:
259
- two active buckets make 12 sites for a cube light and 8 for a tetrahedral one). S5 made it two per
260
- standard view and one per face (§7.3). An empty site is ~80 µs at
261
- `max_iterations` 16 and ~225 µs at the ceiling of 64.
262
-
263
- **Work.** The three cascades are 1740² + 2 × 1440² ≈ 7.3 Mpx against the 0.92 Mpx main view that §0.3
264
- measured, and a cut is a function of pixels, so "a cascade costs what the camera's cut does" is a floor
265
- rather than an estimate. S4 measures it, with the raster cost of the extra clusters beside it.
266
-
267
- 1. **One cut per view** — CUT_PLAN §7 6b, decided and not done, and **extended to the fast path**:
268
- filter instances once, cut once, sort the meshlets by bucket, then loop the buckets for raster
269
- alone. That is two sites per standard view and one per cube face.
270
- 2. **One ladder for the cascades — withdrawn.** It would have run one descent over all three
271
- cascades. They are not one frustum and not nested ones: each is a box fitted to its own slice of
272
- the view frustum, misaligned with the others and overlapping only at the blend margins, so a shared
273
- descent shares no traversal work. What it would share is dispatch overhead — 0.2–0.3 ms after S5
274
- (§7.4) — for a change inside the traverse.
275
-
276
- ## 4. Order, and what each step leaves proved *(rev 3)*
277
-
278
- **Correctness first, on by default, then cost.** VG shadows are not optional, so they land for every
279
- scene with virtual geometry with no switch to turn them off. Each step is red-then-green against a spec
280
- or a scripted headless measurement.
281
-
282
- - **S1 — orthographic projection (§2.2).** Agreement spec: orthographic views against the oracle; the
283
- σmax collect check; the metamorphic scale check; the orthographic frustum. `VGeoViewCut` derives k and
284
- w from the projection matrix, with its own spec. The seam takes it from the view's camera.
285
- - **S2 — the engine owns the frame; feedback is per view (§2.1, §2.3, §2.4).** This is the step that
286
- turns shadow cuts on. Emulator tier: two views in one run write their own stats and want slices, a
287
- full slice does not stop another view asking for the same page, and priority is over τ. Recording
288
- tier: the seam on a second graph within an open frame records its cut against the same imported
289
- buffer, and only the first graph clears it. Headless: all three playgrounds still stream.
290
- - **S3 — the shadows are right.** Proved against the tree before S2, where every one of these is red.
291
- TAA off and single frames, the shadow term measured against a reference with shadow views at a
292
- quarter of the τ: false-shadow and false-lit pixels on light-facing receivers.
293
- - a caster outside the camera frustum (behind the camera) casts into view;
294
- - spot, cube and tetrahedral point lights over VG, including a cached map converging (§2.5);
295
- - a yaw sweep over a still scene: frame-to-frame XOR of the shadow mask, VG against an ordinary
296
- mesh of the same size, since unstabilized cascades refit — and change `k` — every frame;
297
- - per slot: `QUEUE_DROPPED`, `INSTANCES_DROPPED`, `MESHLETS_DROPPED` all zero, `QUEUE_REMAINING`
298
- zero after escalation.
299
- - **S4 — measure.** Per view (the cut's scope and the `VGeoViewCut` label carry the view's name): cut
300
- time, drawn clusters, wants, refusals; spot, cube and tetra lights; a frame spawning N point lights;
301
- shadow views at `tau_scale` 1, 2, 4 — a default other than 1 only if S3's checks pass at it.
302
- - **S5 — one cut per view (6b), standard and fast paths.**
303
- - **S6 — one ladder for the cascades: withdrawn** (§3 item 2).
304
-
305
- ## 5. Not in this
306
-
307
- - **Proxies** (§2.6), beyond keeping the room.
308
- - **Non-opaque VG casters.** An alpha-tested VG instance draws nothing in the main view either
309
- (design §9); the shadow path's alpha-tested bucket inherits that.
310
- - **Ray-traced and SDF shadows** (`shadow/ray`, `shadow/sdf`). VG × RT is design §9's future work.
311
- - **Screen-space contact shadows** already see VG — they trace the depth buffer.
312
- - **Releasing VG on scene teardown** was a defect of its own. It was fixed separately:
313
- `GPUSceneContext.destroy()` releases what `enable_virtual_geometry` built (7a9e14fea, and
314
- REVIEW_LEDGER U1).
315
-
316
- ## 6. Adversarial review of revision 2 — the ledger
317
-
318
- Three reviewers, one each on the frame and feedback, the kernel, and the shadow path and cost; each
319
- finding checked against the code before it was taken.
320
-
321
- | # | finding | disposition |
322
- |---|---|---|
323
- | 1 | want stamp taken before the capacity check: a full shadow slice silences the main view's want for that page; first writer's priority wins (all three) | §2.4: stamp tag per (frame, slot); priority over the view's τ |
324
- | 2 | absolute per-view τ splits the one lever and inverts at a raised slider | §2.3: `tau_scale` |
325
- | 3 | one `max_iterations` for the frame lets one hungry view double every site forever | §2.4: per view |
326
- | 4 | S2 already turns shadows on, so S3 cannot be red after it; the software device cannot prove a readback | §4: S3 proved against the pre-S2 tree; slot writes proved at the emulator tier |
327
- | 5 | Caldera is a third host; a second `begin_frame` mid-render would split `frame_tag` | §2.1 |
328
- | 6 | the oracle has no instance transform, so agreement cannot catch a wrong scale term; the seam still reads `fov` | §2.2, S1: σmax and metamorphic checks; projection from the matrix |
329
- | 7 | `vg_project`'s eye-inside test must be on the denominator — a cascade's centre sits inside root spheres | §2.2 |
330
- | 8 | "no change inside the traverse" for a batched cut is false; cascades and local maps are in separate graphs; the multi-view collect moves the proxy choice | §3.2 scoped to cascades and called a traverse change; §2.6 restated per (instance, view) |
331
- | 9 | a point light's cube faces cut per bucket in the fast path, first draws bypass the budget, cascades are 7.3 Mpx | §3 |
332
- | 10 | 6b as written covers only the standard path | §3.1: and the fast path |
333
- | 11 | a cached local map keeps its first, root-level cut | §2.5: re-select a map whose cut was clamped once pages have installed |
334
- | 12 | the S3 probe cannot see speckle through TAA, never looks for leaks, sweeps the wrong τ, and misses off-frustum casters, local lights and stability | §4 S3 |
335
- | 13 | clearing by `writeBuffer` uploads the whole buffer, and the usage lacks `COPY_DST` | §2.1: in-stream clear of the counter and touch words |
336
- | 14 | `end_frame` needs a `finally`; the open has to sit outside the shadows-enabled `if` | §2.1 |
337
- | 15 | per-view attribution impossible: identical scope names, `VGeoViewCut` labelled before its view is | §4 S4 |
338
- | 16 | proxies as described are raster-only | §2.6 |
339
- | 17 | unstabilized cascades change `k` every frame, so shadow LOD may pop under camera motion | **measured, no action (§7.6)**: `k` moves and the cut follows, but the picture does not flicker beyond what a frozen-`k` cut — an ordinary mesh, to the map — does. `k` is not quantized |
340
- | 18 | a point light's cube faces render at a fixed 512² each and are then remapped into the light's octahedral atlas slot, 128² by default and sized by the resolution policy — so each face cuts for texels the slot does not keep | **withdrawn, not a VG issue.** It is the remap's resolution match between cube faces and the octahedral encoding, and a VG knob would only paper over it |
341
- | 19 | the orthographic far plane is finite and skipped: casters behind a slice's receivers refine and want pages | **withdrawn, measured (§7.5)**: a cascade's far plane is the far corner of its view slice in light space, and testing it culls nothing worth having |
342
- | 20 | the exact orthographic factor is the largest singular value of the projected 2×3 | declined: σmax is a conservative bound and exact for uniform scale |
343
-
344
- ## 7. What the steps found
345
-
346
- All of it is Zorah (§0.1's page, scene and viewpoints) on the 4090 in headless Chrome, at 1556×592.
347
- Costs come from `GPUProfileSession` at `TIMING`, as medians over 48 frames. **This GPU runs in two
348
- modes about 2× apart.** It switches between launches and now and then mid-run, so absolute numbers do
349
- not compare across runs, or even across the rows of one table. Every cost claimed here is a
350
- difference within a pair taken in one Chrome, and only pairs taken in one mode are quoted.
351
-
352
- ### 7.1 Shadows are cast (S3)
353
-
354
- The table is §0.1's: the fraction of pixels that go more than 16 luma darker when shadows are switched
355
- on, only VG in view, against the tree before S2.
356
-
357
- | view | before | after |
358
- |---|---:|---:|
359
- | authored | 0 | 3.99% |
360
- | oblique | 0 | 0.31% |
361
- | plan | 0 | 1.95% |
362
-
363
- - **A caster behind the camera casts into view.** From inside the throne room, 39% of the pixels
364
- darken.
365
- - **Local lights, with the sun off.** Pixels darkened: 4.5% under a cube point light, 4.1% under a
366
- tetrahedral one, 0.22% under a spot. In every slot `QUEUE_DROPPED`, `INSTANCES_DROPPED`,
367
- `MESHLETS_DROPPED` and `QUEUE_REMAINING` are zero.
368
- - **Cached maps (§2.5).** Twelve point lights spawned in one frame reach zero clamped maps within
369
- 14 s, with nothing else fetching.
370
- - **At rest, nothing moves.** Across consecutive settled frames, no pixel changes in the authored view.
371
- In the plan view, fewer than one pixel in 100,000 changes by more than 4 luma, and that holds with
372
- shadows on and off alike.
373
- - **Under camera motion, VG shadows flicker no more than ordinary geometry's** (§7.6).
374
- - **Self-shadowing (§1) needs no new bias.** The main view's `tau_scale` was swept 1 → 8, which makes
375
- the G-buffer up to 8× coarser than the maps.
376
- - Authored: the fraction of darker pixels holds at 6.98–6.99%, with no isolated acne.
377
- - Oblique: 0.79% at 1 and 0.89% at 2, then 1.17% at 4 and 2.29% at 8.
378
- - Isolated dark pixels do not grow anywhere in the sweep.
379
-
380
- So the existing bias absorbs about a 2× mismatch, and the default adds none beyond what the two
381
- projections make. Measured the other way round, against shadow views at τ × ¼, shadow views at τ × 1
382
- leave 0.003–0.012% false shadow and 0.006–0.154% false light.
383
-
384
- ### 7.2 What shadows cost, and τ (S4)
385
-
386
- **The first S4 run found a defect in S2**, fixed in 3f9cce3cc. The first frames were laid out for one
387
- slot, so the cascades shared slot 0 with the frame's view. The sum of their remainders escalated the
388
- main view to 32 wavefronts, for good. Now the floor is two slots, a view past the layout never shares
389
- slot 0, and a shared slot escalates nobody.
390
-
391
- **Where the time goes**, read within single frames so the modes cannot move it. The frame is the
392
- authored view with shadows on, after S5:
393
-
394
- - The cut is half of it: 2.67 of 5.27 ms.
395
- - Of that cut, the three cascades take 1.88 ms and the camera 0.74 ms.
396
- - Cascade 0 alone is 0.82 ms and draws 113k clusters, against the camera's 32k.
397
-
398
- Per-site ladders are the smaller part (§7.4).
399
-
400
- **`tau_scale` on the cascades** (`shadow_tau`, TAA and auto exposure off; the reference is every
401
- cascade at τ × ¼):
402
-
403
- | view | false light ×1 | ×2 | ×4 |
404
- |---|---:|---:|---:|
405
- | authored | 0.006% | 0.028% | 0.040% |
406
- | oblique | 0.154% | 0.349% | 0.454% |
407
- | plan | 0.153% | 0.400% | 0.530% |
408
-
409
- ×2 draws 36% of ×1's clusters in cascade 0 and cuts the depth raster by about 60%. Across the three
410
- views it also multiplies the false light by 2.3–4.7×, and false light is sunlit ground under a caster.
411
- **Shadow views default to 2** — Alex's call on these numbers. The clusters and raster are the saving,
412
- and the false light is the price.
413
-
414
- ### 7.3 One cut per view (S5)
415
-
416
- - **Standard path, phase one.** The HZB filter appends every opaque bucket to one positive set, as it
417
- already did the maybe set. That set is expanded, cut and meshlet-filtered once, then sorted by bucket
418
- and drawn a bucket at a time (`graph_meshlets_rasterize_by_bucket`).
419
- - **Fast path.** The opaque buckets are not neighbours in its bucket-sorted meshes, so it gathers them
420
- into one collection first (`shader_gather_bucket`). Each bucket lands after the gathered buckets
421
- below it, read off their headers, with no atomics.
422
- - **Both paths, and phase two,** sort only when there is more than one bucket to separate.
423
-
424
- Paired, before (3f9cce3cc) and after in one Chrome, ABBA twice. The ranges are over the pairs taken in
425
- the fast mode, and the slow mode's pairs save proportionally the same.
426
-
427
- | | cut sites | frame saved | cut saved |
428
- |---|---:|---:|---:|
429
- | authored, shadows on | 12 → 8 | 1.0–1.1 ms of 6.2 | 0.9–1.2 ms |
430
- | oblique, shadows on | 12 → 8 | 0.6–1.6 ms of 10.2 | 1.2–1.7 ms |
431
- | authored, shadows off | 3 → 2 | 0.3–1.0 ms | 0.2–0.7 ms |
432
- | oblique, shadows off | 3 → 2 | 0.02–0.13 ms | 0.09–0.11 ms |
433
-
434
- **Nothing else changed:**
435
-
436
- - The two trees' pictures differ by nothing at any threshold, in all four conditions.
437
- - Every view draws the same number of clusters.
438
- - Every counter of every face of a cube and a tetrahedral point light is the same, from drawn to
439
- culled-by-LOD.
440
-
441
- The second bucket's site disappears and the first barely grows, because nearly all of what the second
442
- site cost was its ladder. Cascade 0 goes from [807, 328, 71] µs to [820, 69] µs.
443
-
444
- ### 7.4 S6 is withdrawn
445
-
446
- After S5 each view is left with **one ladder per phase**, and an empty ladder costs 40–75 µs. That
447
- ladder is all a batched cascade cut could have shared: the cascades are disjoint boxes over separate
448
- slices of the view frustum (§3 item 2), so the descent's own work — where the cascades' cost is —
449
- would be the same.
450
-
451
- What would reduce that work is fewer clusters per cascade. Two levers exist:
452
-
453
- - `tau_scale` — now 2 on shadow views (§7.2).
454
- - Starting each view's descent from its previous frontier. `VGeoViewCut` already keeps a frontier
455
- (CUT_PLAN 6c), and the seam does not read it.
456
-
457
- Neither is proposed here.
458
-
459
- ### 7.5 A cascade's far plane culls nothing worth having
460
-
461
- Ledger 19 said the untested orthographic far plane lets casters behind a cascade's receivers refine.
462
- That was argued, not measured. A cascade's far plane is `extents.z1`, the far corner of its view
463
- slice in light space, so anything behind it sits below everything the slice covers.
464
-
465
- Measured with shadow views at `tau_scale` 2. The collect's fifth plane was swapped for orthographic
466
- views only, one Chrome launch per arm: the near plane as shipped, no plane, the far plane, then the
467
- near plane again.
468
-
469
- - **The near plane culls nothing either.** The caster range pushes it behind every caster by
470
- construction, so "no plane" matches "near" on every counter.
471
- - **So the far arm is the far plane's whole effect.** Across the three viewpoints and three cascades,
472
- every counter — drawn, items, pages, clusters tested, frustum culls — is identical in all four arms,
473
- but one: oblique cascade 0, which draws 65 clusters, draws 14.
474
- - **The pictures are identical to the pixel** in every arm.
475
-
476
- ### 7.6 Cascade refits do not make VG shadows flicker (ledger 17)
477
-
478
- Cascades are not stabilized, so every camera move refits them and changes each cascade's `k`. The
479
- question was whether VG's shadow cuts re-selecting on every refit make the shadows flicker more than
480
- an ordinary caster's.
481
-
482
- **Method.** A moving view camera would put its own motion into every frame-to-frame difference, so
483
- the view stands still and only the camera the cascades are fitted from moves. It yaws
484
- A·sin(2πk/48), one step per rendered frame, so every change in the picture comes from the refit.
485
-
486
- - **Two arms over the identical sequence.** In one, VG's shadow cuts are live. In the other, every
487
- shadow view's `k` is frozen at the unwobbled fit: `set_projection` becomes a no-op. A cut with
488
- `k` frozen is an ordinary mesh as far as the map is concerned, so live minus frozen is VG's own
489
- flicker. Freezing `k` is also exactly the remedy this ledger row named.
490
- - **Measurement conditions.** TAA and auto exposure are off, and shadow views are at `tau_scale` 2.
491
- Each frame is captured inside the render wrapper, so the two arms' phases line up. Metrics cover the
492
- central 80% of the screen. A second live arm reproduced the first to the pixel.
493
-
494
- Per frame, the fraction of pixels that change by more than 8 luma:
495
-
496
- | view | ±3°: live | ±3°: frozen | ±10°: live | ±10°: frozen |
497
- |---|---:|---:|---:|---:|
498
- | authored | 0 | 0 | 0.0002% | 0 |
499
- | oblique | 0.033% | 0.033% | — | — |
500
- | plan | 0.009% | 0.006% | 0.018% | 0.013% |
501
-
502
- Over the ±3° sweep, cascade 0's `k` runs 67–78 and it draws 35k–47k clusters, so the cut does follow
503
- the refit — and the picture barely notices. At ±10°, about 1.3° a frame and roughly a fast turn, VG's
504
- own share is 0.005% of the screen per frame.
505
-
506
- **The metric can see VG flicker when there is some.** As a positive control, with no wobble, every
507
- shadow view's `tau_scale` alternated between 2 and 16 each frame: cascade 0 drew 3.7k and 42k clusters
508
- in turn. That flickers 0.17% of the screen per frame in the authored view and 0.33% in the plan view.
509
-
510
- ## References
511
-
512
- Read 2026-09-28.
513
-
514
- - Epic, *Virtual Shadow Maps in Unreal Engine* — per-view Nanite rendering, one pass per light
515
- class, 17 directional clipmap levels, page caching and invalidation.
516
- https://dev.epicgames.com/documentation/en-us/unreal-engine/virtual-shadow-maps-in-unreal-engine
517
- - Epic, *Hardware Ray Tracing* — ray-traced shadows trace the Nanite fallback mesh.
518
- https://dev.epicgames.com/documentation/en-us/unreal-engine/hardware-ray-tracing-in-unreal-engine
519
- - Bevy, `crates/bevy_pbr/src/meshlet/cull_shared.wesl` (`lod_error_is_imperceptible`) and
520
- `visibility_buffer_raster_node.rs` (one full cull and raster per shadow view).
521
- https://github.com/bevyengine/bevy/tree/main/crates/bevy_pbr/src/meshlet
522
- - Bevy PR #19318, the orthographic LOD metric fix. https://github.com/bevyengine/bevy/pull/19318
523
- - jms55, *Virtual Geometry in Bevy 0.15* — "render all views at once" listed as shadow performance
524
- work. https://jms55.github.io/posts/2024-11-14-virtual-geometry-bevy-0-15/
525
- - NVIDIA `vk_lod_clusters` — one traversal from the camera, shadow rays traced against it; and
526
- `vk_tessellated_clusters`' README on off-screen shadows changing when instances become visible.
527
- https://github.com/nvpro-samples/vk_lod_clusters · https://github.com/nvpro-samples/vk_tessellated_clusters
528
- - **Not found in any primary source:** whether and how Nanite's shadow views issue streaming
529
- requests. §2.4 does not rest on it.
@@ -1,3 +0,0 @@
1
- # consider using higher order interpolation for tetrahedral meshes to smooth out boundary artifacts:
2
-
3
- * ["Hardware Adaptive High-Order Interpolation for Real-Time Graphics"](https://dqlin.xyz/pubs/2021-hpg-HOI/) by Daqi Lin et al.