@woosh/meep-engine 3.26.0 → 3.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/package.json +1 -1
  2. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +542 -0
  3. package/src/core/binary/lz4/LZ4_LEVEL.d.ts +33 -0
  4. package/src/core/binary/lz4/LZ4_LEVEL.d.ts.map +1 -0
  5. package/src/core/binary/lz4/LZ4_LEVEL.js +32 -0
  6. package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts +33 -0
  7. package/src/core/binary/lz4/lz4_compress_block_at_level.d.ts.map +1 -0
  8. package/src/core/binary/lz4/lz4_compress_block_at_level.js +47 -0
  9. package/src/core/binary/lz4/lz4_compress_block_hc.d.ts +40 -0
  10. package/src/core/binary/lz4/lz4_compress_block_hc.d.ts.map +1 -0
  11. package/src/core/binary/lz4/lz4_compress_block_hc.js +1382 -0
  12. package/src/core/binary/lz4/lz4_decompress_block.d.ts +2 -1
  13. package/src/core/binary/lz4/lz4_decompress_block.d.ts.map +1 -1
  14. package/src/core/binary/lz4/lz4_decompress_block.js +31 -6
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +14 -7
  16. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  17. package/src/core/geom/3d/shape/HeightMapShape3D.js +87 -78
  18. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  19. package/src/shade/playground/vgeo_runtime/README.md +167 -22
  20. package/src/shade/playground/vgeo_runtime/index.html +112 -62
  21. package/src/shade/playground/vgeo_runtime/main.js +660 -83
  22. package/src/shade/playground/vgeo_viewer/sample_asset.d.ts.map +1 -1
  23. package/src/shade/playground/vgeo_viewer/sample_asset.js +19 -2
  24. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +11 -0
  25. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
  26. package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +16 -8
  27. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
  28. package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +16 -11
  29. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +73 -5
  30. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +26 -15
  31. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +5 -3
  32. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +18 -0
  33. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  34. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +20 -0
  35. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
  36. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +56 -9
  37. package/src/shade/renderer/geometry/virtual/build/vgeo_build.d.ts.map +1 -1
  38. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -0
  39. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +22 -0
  40. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  41. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +24 -0
  42. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts +12 -0
  43. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.d.ts.map +1 -1
  44. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +13 -0
  45. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts +8 -1
  46. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.d.ts.map +1 -1
  47. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +11 -3
  48. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts +9 -0
  49. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.d.ts.map +1 -1
  50. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +10 -1
  51. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts +18 -0
  52. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.d.ts.map +1 -0
  53. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_ENTRY_COUNT.js +17 -0
  54. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts +8 -0
  55. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.d.ts.map +1 -0
  56. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_PARENT_REF.js +7 -0
  57. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.d.ts +3 -0
  58. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -0
  59. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts +14 -0
  60. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.d.ts.map +1 -0
  61. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_ENTRY_SIZE.js +13 -0
  62. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts +11 -0
  63. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.d.ts.map +1 -0
  64. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PARENT_PAGE_ENTRY_SIZE.js +10 -0
  65. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +20 -0
  66. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
  67. package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +20 -2
  68. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +4 -0
  69. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
  70. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +4 -0
  71. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +29 -3
  72. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
  73. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +81 -122
  74. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +40 -0
  75. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +44 -0
  77. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts +69 -0
  78. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.d.ts.map +1 -0
  79. package/src/shade/renderer/geometry/virtual/format/read/VGeoPageFetcherCore.js +236 -0
  80. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +14 -0
  81. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
  82. package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +15 -0
  83. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts +31 -0
  84. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.d.ts.map +1 -0
  85. package/src/shade/renderer/geometry/virtual/format/read/vgeo_byte_source_from_descriptor.js +32 -0
  86. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +10 -0
  87. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
  88. package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +21 -1
  89. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts +15 -0
  90. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.d.ts.map +1 -0
  91. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_transfer.js +84 -0
  92. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts +22 -0
  93. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.d.ts.map +1 -0
  94. package/src/shade/renderer/geometry/virtual/format/read/vgeo_page_worker_serve.js +96 -0
  95. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  96. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +55 -0
  97. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  98. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +202 -13
  99. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  100. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +147 -10
  101. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +637 -0
  102. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +33 -36
  103. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -1
  104. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +109 -193
  105. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +58 -28
  106. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -1
  107. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +77 -105
  108. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +37 -0
  109. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +105 -22
  110. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +548 -0
  111. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +29 -50
  112. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -1
  113. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +47 -489
  114. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts +84 -0
  115. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.d.ts.map +1 -0
  116. package/src/shade/renderer/geometry/virtual/runtime/VGeoPageFetcher.js +393 -0
  117. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts +234 -0
  118. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -0
  119. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +1363 -0
  120. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts +81 -0
  121. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.d.ts.map +1 -0
  122. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidencyOptions.js +89 -0
  123. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +53 -34
  124. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -1
  125. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +407 -124
  126. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts +100 -0
  127. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.d.ts.map +1 -0
  128. package/src/shade/renderer/geometry/virtual/runtime/VGeoViewCut.js +219 -0
  129. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts +17 -0
  130. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.d.ts.map +1 -0
  131. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_EXPAND.js +47 -0
  132. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts +20 -0
  133. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.d.ts.map +1 -0
  134. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_COARSEN.js +19 -0
  135. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -1
  136. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +30 -12
  137. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -1
  138. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +15 -0
  139. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -1
  140. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +18 -1
  141. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -1
  142. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +5 -15
  143. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts +3 -0
  144. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.d.ts.map +1 -0
  145. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_remainder.js +77 -0
  146. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts +3 -0
  147. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.d.ts.map +1 -0
  148. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_reseed.js +186 -0
  149. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -1
  150. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +20 -8
  151. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -1
  152. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +580 -188
  153. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +15 -18
  154. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -1
  155. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +19 -28
  156. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -1
  157. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +45 -25
  158. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +1 -0
  159. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +11 -0
  160. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +1 -1
  161. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +0 -1
  162. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +12 -10
  163. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +1 -0
  164. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +8 -0
  165. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +7 -2
  166. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -1
  167. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +8 -3
  168. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +2 -0
  169. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +19 -0
  170. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -1
  171. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +1 -1
  172. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts +5 -0
  173. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.d.ts.map +1 -0
  174. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_OFFSET.js +22 -0
  175. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts +14 -0
  176. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.d.ts.map +1 -0
  177. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PARENT_ROW_WORDS.js +13 -0
  178. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts +17 -0
  179. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.d.ts.map +1 -0
  180. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_PARENT_BIT.js +16 -0
  181. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts +15 -0
  182. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.d.ts.map +1 -0
  183. package/src/shade/renderer/geometry/virtual/runtime/make_vgeo_page_worker.js +16 -0
  184. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts +23 -0
  185. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.d.ts.map +1 -0
  186. package/src/shade/renderer/geometry/virtual/runtime/vgeo_frontier_words.js +28 -0
  187. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts +2 -0
  188. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.d.ts.map +1 -0
  189. package/src/shade/renderer/geometry/virtual/runtime/vgeo_page.worker.js +13 -0
  190. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +10 -5
  191. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -1
  192. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +15 -6
  193. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +6 -1
  194. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -1
  195. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +38 -1
  196. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.d.ts.map +1 -1
  197. package/src/shade/renderer/global_illumination/sharc/shader/shader_sharc_evict.js +4 -3
  198. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  199. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  200. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +6 -3
  201. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  202. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +6 -3
  203. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  204. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  205. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -4
  206. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +2 -0
  207. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
  208. package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
  209. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.d.ts.map +1 -1
  210. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_mesh_filter_2way.js +3 -5
  211. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.d.ts.map +1 -1
  212. package/src/shade/renderer/rasterize/cull/hzb/shader_hzb_meshlet_filter_1way_full.js +3 -4
  213. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.d.ts.map +1 -1
  214. package/src/shade/renderer/rasterize/expand/bucket/meshlet/shader_meshlets_extract_bucket.js +3 -4
  215. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.d.ts.map +1 -1
  216. package/src/shade/renderer/rasterize/expand/pass0/pass_mesh_cull.js +7 -7
  217. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
  218. package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +83 -78
  219. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  220. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +7 -1
  221. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  222. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +7 -1
  223. package/src/shade/renderer/scene/GPUSceneContext.d.ts +22 -2
  224. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  225. package/src/shade/renderer/scene/GPUSceneContext.js +59 -4
  226. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
  227. package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +92 -16
  228. package/src/shade/renderer/view/GPUViewContext.d.ts +5 -0
  229. package/src/shade/renderer/view/GPUViewContext.d.ts.map +1 -1
  230. package/src/shade/renderer/view/GPUViewContext.js +21 -0
  231. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  232. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +74 -3
  233. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +0 -723
  234. package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +0 -50
  235. package/src/shade/renderer/shader/chunk/jobs/NOTES.md +0 -47
  236. package/src/shade/renderer/shader/chunk/jobs/chunk_job_system.js +0 -102
@@ -1,10 +1,14 @@
1
1
  import { assert } from "../../../../../core/assert.js";
2
2
  import { graph_read_buffer } from "../../../buffer/graph_read_buffer.js";
3
- import { VGeoPageWant } from "../format/read/VGeoPageWant.js";
4
3
  import { VGEO_CUT_STAT_COUNT } from "./layout/VGEO_CUT_STAT_COUNT.js";
5
4
  import { VGEO_FEEDBACK_OFFSET } from "./layout/VGEO_FEEDBACK_OFFSET.js";
6
- import { VGEO_PAGE_ROW_OFFSET } from "./layout/VGEO_PAGE_ROW_OFFSET.js";
7
- import { VGEO_WANT_WORDS } from "./layout/VGEO_WANT_WORDS.js";
5
+
6
+ /**
7
+ * @typedef {import("../../../../../engine/graphics/render/frame_graph/FrameGraph.js").FrameGraph} FrameGraph
8
+ * @typedef {import("./GPUVirtualGeometryRuntime.js").GPUVirtualGeometryRuntime} GPUVirtualGeometryRuntime
9
+ * @typedef {import("./VGeoResidency.js").VGeoResidency} VGeoResidency
10
+ * @typedef {import("./cut/vgeo_cut_feedback_layout.js").vgeo_cut_feedback_layout} vgeo_cut_feedback_layout
11
+ */
8
12
 
9
13
  /**
10
14
  * The other half of the loop: what the cut saw, coming back to the host.
@@ -14,20 +18,11 @@ import { VGEO_WANT_WORDS } from "./layout/VGEO_WANT_WORDS.js";
14
18
  * feedback is skipped entirely** when saturated. A loop that insisted on a reading every frame
15
19
  * would stall the frame it was reading, which is the failure the cap exists to prevent.
16
20
  *
17
- * What comes back is two lists and a counter block:
18
- *
19
- * - **touches** — page rows the cut drew from. They go to the VRAM pool's LRU and to
20
- * `VGeoResidencyManager`'s, which is design §6's touch-LRU at both tiers. Not last-*wanted*:
21
- * stamping at fetch time freezes the stamp of exactly what is on screen and evicts it.
22
- * - **wants** — pages a clamped cluster could not reach, each naming the page it was discovered
23
- * *through*. That second half is what resolves the byte range without a directory, and it is
24
- * also the page format §10 asks to be pinned while its child is in flight.
25
- *
26
- * A want resolves to one of two very different things, and telling them apart is this class's real
27
- * work. A page already in RAM is a **VRAM install** and no fetch at all — the bytes are here and
28
- * the cut simply has not been given them. A page not in RAM is a fetch, handed to the residency
29
- * manager with its priority so that it competes globally against every other asset's wants rather
30
- * than within whichever reader asked first.
21
+ * This class is the transport and nothing else: it schedules the readback while the graph is still
22
+ * being built, closes the frame's own layout and frame number over it, keeps the counters the
23
+ * kernel wrote, and hands the words to {@link VGeoResidency.apply}. What a touch or a want means —
24
+ * which tier it lands on, what it costs, what is fetched and what is refused — is the residency's,
25
+ * in one place for both tiers (RUNTIME_PLAN_2026_09_14 §4.7).
31
26
  *
32
27
  * @author Alex Goldring
33
28
  * @copyright Company Named Limited (c) 2026
@@ -39,7 +34,7 @@ export class VGeoFeedbackLoop {
39
34
  #runtime;
40
35
 
41
36
  /**
42
- * @type {VGeoResidencyManager}
37
+ * @type {VGeoResidency}
43
38
  */
44
39
  #residency;
45
40
 
@@ -53,11 +48,6 @@ export class VGeoFeedbackLoop {
53
48
  */
54
49
  #max_in_flight;
55
50
 
56
- /**
57
- * @type {number}
58
- */
59
- #install_budget_bytes;
60
-
61
51
  /**
62
52
  * @type {number}
63
53
  */
@@ -74,121 +64,27 @@ export class VGeoFeedbackLoop {
74
64
  */
75
65
  #stats = new Uint32Array(VGEO_CUT_STAT_COUNT);
76
66
 
77
- /**
78
- * @type {number}
79
- */
80
- #last_want_count = 0;
81
-
82
- /**
83
- * @type {number}
84
- */
85
- #last_touch_count = 0;
86
-
87
- /**
88
- * Page indices this loop has asked for and not yet heard back about, per reader.
89
- *
90
- * A page stays wanted for as long as it is missing, so the same want arrives every frame until
91
- * the fetch lands. Without this the shared `ConcurrencyGate` accumulates one closure per frame
92
- * per wanted page and the RAM budget is charged once per frame for the same bytes.
93
- *
94
- * @type {Map<VGeoContainerReader, Set<number>>}
95
- */
96
- #in_flight_pages = new Map();
97
-
98
- /**
99
- * One per batch of fetches still running, so {@link abort} can stop all of them.
100
- * @type {Set<AbortController>}
101
- */
102
- #controllers = new Set();
103
-
104
- /**
105
- * @type {number}
106
- */
107
- #max_wants_per_frame;
108
-
109
- /**
110
- * @type {number}
111
- */
112
- #deduped_wants = 0;
113
-
114
- /**
115
- * @type {number}
116
- */
117
- #dropped_wants = 0;
118
-
119
- /**
120
- * @type {number}
121
- */
122
- #failed_batches = 0;
123
-
124
- /**
125
- * Touches and wants naming a page row that was re-used between the cut and this reading.
126
- * @type {number}
127
- */
128
- #stale_records = 0;
129
-
130
67
  /**
131
68
  * @param {Object} options
132
69
  * @param {GPUVirtualGeometryRuntime} options.runtime
133
- * @param {VGeoResidencyManager} options.residency the RAM tier's budget owner
134
- * @param {number} [options.max_in_flight] readbacks that may be outstanding at once
135
- * @param {number} [options.install_budget_bytes] decoded page bytes promoted to VRAM per frame
136
- * @param {number} [options.max_wants_per_frame] pages one frame may newly ask to fetch, across
137
- * every reader — see {@link #dispatch_frame}, which is where the "across every reader" is
138
- * enforced; the rest are dropped and asked for again next frame, which is what a want is
139
- * for
70
+ * @param {VGeoResidency} options.residency both tiers' owner
71
+ * @param {number} [options.max_in_flight] readbacks that may be outstanding at once; defaults
72
+ * to the residency's option
140
73
  */
141
- constructor({
142
- runtime,
143
- residency,
144
- max_in_flight = 2,
145
- install_budget_bytes = 8 * 1024 * 1024,
146
- max_wants_per_frame = 32,
147
- }) {
74
+ constructor({ runtime, residency, max_in_flight = undefined }) {
148
75
  assert.defined(runtime, 'runtime');
149
76
  assert.defined(residency, 'residency');
150
77
 
151
78
  this.#runtime = runtime;
152
79
  this.#residency = residency;
153
- this.#max_in_flight = max_in_flight;
154
- this.#install_budget_bytes = install_budget_bytes;
155
- this.#max_wants_per_frame = max_wants_per_frame;
156
- }
157
-
158
- /**
159
- * Feedback records naming a page row that had been re-used by the time the reading landed.
160
- * @returns {number}
161
- */
162
- get stale_record_count() {
163
- return this.#stale_records;
164
- }
165
-
166
- /**
167
- * Wants this loop did not re-submit because the page was already being fetched.
168
- * @returns {number}
169
- */
170
- get deduped_want_count() {
171
- return this.#deduped_wants;
80
+ this.#max_in_flight = max_in_flight ?? residency.options.max_readbacks_in_flight;
172
81
  }
173
82
 
174
83
  /**
175
- * Wants past this frame's fetch limit. Not a failure — the next cut asks again.
176
- * @returns {number}
84
+ * @returns {VGeoResidency}
177
85
  */
178
- get dropped_want_count() {
179
- return this.#dropped_wants;
180
- }
181
-
182
- /**
183
- * Batches every page of which the reader quarantined, plus any that rejected outright.
184
- *
185
- * A reader that has stopped answering is the one streaming failure a host can do something
186
- * about, and it is the one thing a per-page quarantine cannot say on its own.
187
- *
188
- * @returns {number}
189
- */
190
- get failed_batch_count() {
191
- return this.#failed_batches;
86
+ get residency() {
87
+ return this.#residency;
192
88
  }
193
89
 
194
90
  /**
@@ -221,14 +117,22 @@ export class VGeoFeedbackLoop {
221
117
  * @returns {number}
222
118
  */
223
119
  get last_want_count() {
224
- return this.#last_want_count;
120
+ return this.#residency.last_want_count;
225
121
  }
226
122
 
227
123
  /**
228
124
  * @returns {number}
229
125
  */
230
126
  get last_touch_count() {
231
- return this.#last_touch_count;
127
+ return this.#residency.last_touch_count;
128
+ }
129
+
130
+ /**
131
+ * Records naming a page row that was refilled between the cut and the reading.
132
+ * @returns {number}
133
+ */
134
+ get stale_record_count() {
135
+ return this.#residency.stale_record_count;
232
136
  }
233
137
 
234
138
  /**
@@ -240,20 +144,6 @@ export class VGeoFeedbackLoop {
240
144
  read(graph) {
241
145
  assert.defined(graph, 'graph');
242
146
 
243
- /*
244
- The RAM tier's clock, moved here rather than left to the host. `advance_frame` is what
245
- carries the protect window forward, and without it `evict_to_budget` protects everything
246
- and never drops a page — so the RAM tier is unbounded. Its only non-spec caller was the
247
- playground (REVIEW_LEDGER M12), which is to say that in any real host the second tier of a
248
- three-tier design was doing nothing at all.
249
-
250
- Before the early returns below on purpose: a frame whose feedback is skipped is still a
251
- frame, and a clock that only advanced on the frames the readback kept up with would make
252
- the protect window a function of readback latency.
253
- */
254
- this.#residency.advance_frame();
255
- this.#residency.evict_to_budget();
256
-
257
147
  const feedback = this.#runtime.feedback;
258
148
 
259
149
  if (feedback === -1) {
@@ -268,27 +158,27 @@ export class VGeoFeedbackLoop {
268
158
 
269
159
  /*
270
160
  Closed over, not looked up again when the reading lands. `begin_frame` recomputes the
271
- layout every frame out of the page-row high-water mark and the lookup word count, both of
272
- which move as pages install — so a readback that lands a frame or two later and asked the
273
- runtime for "the" layout would decode this frame's bytes with a later frame's bases, and
274
- read wants and touches out of the middle of the stamp region (REVIEW_LEDGER C5).
161
+ layout every frame out of the page-row high-water mark, which moves as pages install — so a
162
+ readback that lands a frame or two later and asked the runtime for "the" layout would
163
+ decode this frame's bytes with a later frame's bases (REVIEW_LEDGER C5).
275
164
  */
276
165
  const layout = this.#runtime.feedback_layout;
277
166
 
278
167
  /*
279
- And the generation of every page row that exists right now. A row is reused the moment it
280
- is freed and this reading is at least a frame behind the cut that produced it, so a touch
281
- or a want naming row 7 may be naming the page that *was* in row 7. Comparing the generation
282
- is what tells the two apart (REVIEW_LEDGER M10).
168
+ And the frame this reading is about. A row is reused the moment it is freed and this
169
+ reading is at least a frame behind the cut that produced it, so a touch or a want naming
170
+ row 7 may be naming the page that *was* in row 7 (REVIEW_LEDGER M10). The pool stamps every
171
+ row with the frame whose flush publishes its occupant, and the residency compares that per
172
+ record against this number.
283
173
  */
284
- const generations = this.#snapshot_generations();
174
+ const reading_frame = this.#runtime.pool.frame;
285
175
 
286
176
  this.#in_flight++;
287
177
 
288
178
  graph_read_buffer({ graph, buffer: feedback, offset: 0, size: layout.word_count * 4 })
289
179
  .then(reference => {
290
180
  try {
291
- this.#apply(new Uint32Array(reference.getValue()), layout, generations);
181
+ this.#apply(new Uint32Array(reference.getValue()), layout, reading_frame);
292
182
  } finally {
293
183
  reference.release();
294
184
  }
@@ -306,365 +196,33 @@ export class VGeoFeedbackLoop {
306
196
  return true;
307
197
  }
308
198
 
309
- /**
310
- * @returns {Uint32Array} one generation per page row that could appear in this frame's feedback
311
- */
312
- #snapshot_generations() {
313
- const tables = this.#runtime.pool.tables;
314
-
315
- const count = this.#runtime.pool.page_row_capacity;
316
-
317
- const generations = new Uint32Array(count);
318
-
319
- for (let row = 0; row < count; row++) {
320
- generations[row] = tables.generation_of(row);
321
- }
322
-
323
- return generations;
324
- }
325
-
326
- /**
327
- * Whether a page row still holds the page it held when the cut ran.
328
- *
329
- * @param {number} page_row
330
- * @param {Uint32Array} generations
331
- * @returns {boolean}
332
- */
333
- #still_the_same_page(page_row, generations) {
334
- if (page_row >= generations.length) {
335
- // a row that did not exist when the graph was built cannot be one this cut named
336
- return false;
337
- }
338
-
339
- return this.#runtime.pool.tables.generation_of(page_row) === generations[page_row];
340
- }
341
-
342
199
  /**
343
200
  * @param {Uint32Array} words
344
201
  * @param {ReturnType<vgeo_cut_feedback_layout>} layout the frame's own, not the current one
345
- * @param {Uint32Array} generations per page row, as of the frame this reading is about
202
+ * @param {number} reading_frame the frame this reading is about
346
203
  */
347
- #apply(words, layout, generations) {
348
- const pool = this.#runtime.pool;
349
-
204
+ #apply(words, layout, reading_frame) {
350
205
  for (let i = 0; i < VGEO_CUT_STAT_COUNT; i++) {
351
206
  this.#stats[i] = words[VGEO_FEEDBACK_OFFSET.STATS + i];
352
207
  }
353
208
 
354
- /*
355
- Clamped, because the count is an `atomicAdd` that ran past the capacity the kernel then
356
- refused to write at. Reading it unclamped would read the words after the list, which are
357
- whatever the pool's transient buffer held before this frame.
358
- */
359
- const touch_count = Math.min(
360
- words[VGEO_FEEDBACK_OFFSET.TOUCH_COUNT], layout.word_count - layout.touch_base
361
- );
362
-
363
- this.#last_touch_count = touch_count;
364
- this.#last_want_count = Math.min(
365
- words[VGEO_FEEDBACK_OFFSET.WANT_COUNT],
366
- Math.floor((layout.touch_base - layout.want_base) / VGEO_WANT_WORDS)
367
- );
368
-
369
- for (let i = 0; i < touch_count; i++) {
370
- const page_row = words[layout.touch_base + i];
371
-
372
- const entry = pool.entry_of(page_row);
373
-
374
- if (entry === undefined) {
375
- // evicted between the cut and the reading; the stamp it would have taken is moot
376
- continue;
377
- }
378
-
379
- if (!this.#still_the_same_page(page_row, generations)) {
380
- /*
381
- Evicted *and refilled* in between. Stamping now would tell both tiers' LRU that a
382
- page nobody has drawn is the hottest thing in the cache, and it would do it at
383
- exactly the moment a page was streaming in.
384
- */
385
- this.#stale_records++;
386
-
387
- continue;
388
- }
389
-
390
- pool.touch(page_row);
391
-
392
- this.#residency.touch(
393
- entry.reader, pool.tables.page_field(page_row, VGEO_PAGE_ROW_OFFSET.PAGE_INDEX)
394
- );
395
- }
396
-
397
- /**
398
- * Wants that need bytes, per reader — the residency manager takes them a reader at a time
399
- * because a reader is what owns a fetch, and orders them globally because a priority in
400
- * pixels of error is comparable across assets by construction.
401
- * @type {Map<VGeoContainerReader, VGeoPageWant[]>}
402
- */
403
- const fetches = new Map();
404
-
405
- let promoted = 0;
406
-
407
- for (let i = 0; i < this.#last_want_count; i++) {
408
- const base = layout.want_base + i * VGEO_WANT_WORDS;
409
-
410
- const source_row = words[base];
411
- const child_ref = words[base + 1];
412
- const child_page_index = words[base + 2];
413
- const priority = new Float32Array(new Uint32Array([words[base + 3]]).buffer)[0];
414
-
415
- const entry = pool.entry_of(source_row);
416
-
417
- if (entry === undefined) {
418
- continue;
419
- }
420
-
421
- if (!this.#still_the_same_page(source_row, generations)) {
422
- /*
423
- The row was reused between the cut and this reading, so the page index below and
424
- the byte range the child table would give come from two different pages — a fetch
425
- aimed at a page nobody asked for, charged to the RAM budget, at the priority of a
426
- cluster in an unrelated part of the asset.
427
- */
428
- this.#stale_records++;
429
-
430
- continue;
431
- }
432
-
433
- const reader = entry.reader;
434
-
435
- const source_index = pool.tables.page_field(source_row, VGEO_PAGE_ROW_OFFSET.PAGE_INDEX);
436
-
437
- const source = reader.page(source_index);
438
-
439
- if (source === undefined || child_ref >= source.child_page_index.length) {
440
- continue;
441
- }
442
-
443
- if (reader.is_resident(child_page_index)) {
444
- /*
445
- The bytes are already in RAM, so this is not a fetch at all — the cut is clamping
446
- because the page has not been promoted to VRAM yet. Budgeted per frame, because a
447
- camera that suddenly wants a hundred pages should get them over several frames
448
- rather than in one hitch.
449
- */
450
- if (promoted < this.#install_budget_bytes) {
451
- if (pool.install(reader, child_page_index) !== -1) {
452
- promoted += source.child_decoded_size[child_ref];
453
- }
454
- }
455
-
456
- continue;
457
- }
458
-
459
- const want = new VGeoPageWant();
460
-
461
- want.page_index = child_page_index;
462
- want.frame_offset = source.child_frame_offset[child_ref];
463
- want.fetch_size = source.child_fetch_size[child_ref];
464
- want.decoded_size = source.child_decoded_size[child_ref];
465
- want.priority = priority;
466
- want.reference_count = 1;
467
-
468
- const list = fetches.get(reader) ?? [];
469
-
470
- list.push(want);
471
-
472
- fetches.set(reader, list);
473
- }
474
-
475
- this.#dispatch_frame(fetches);
209
+ this.#residency.apply(words, layout, reading_frame);
476
210
  }
477
211
 
478
212
  /**
479
- * One frame's fetches: deduped against what is in flight, ranked across every reader, and cut
480
- * to the frame's limit.
481
- *
482
- * **Across every reader, which is what the option has always said and not what it did.**
483
- * `max_wants_per_frame` was applied inside {@link #dispatch_wants}, once per reader, so a frame
484
- * seeing eight streaming assets issued eight times the limit — the shape
485
- * `VGeoResidencyManager`'s own doc records being measured at 48 concurrent range requests
486
- * against a cap of 6, and the reason its gate is shared. One frame, one budget.
487
- *
488
- * Ranked across readers for the same reason the gate is shared: a want's priority is pixels of
489
- * projected error past the threshold, which is comparable across assets by construction. So the
490
- * frame's budget goes to the worst coarseness on screen wherever it came from, rather than to
491
- * whichever reader this frame's touch list happened to name first.
492
- *
493
- * @param {Map<VGeoContainerReader, VGeoPageWant[]>} fetches
494
- */
495
- #dispatch_frame(fetches) {
496
- /**
497
- * @type {{reader: VGeoContainerReader, want: VGeoPageWant}[]}
498
- */
499
- const fresh = [];
500
-
501
- for (const [reader, wants] of fetches) {
502
- const in_flight = this.#in_flight_of(reader);
503
-
504
- for (const want of wants) {
505
- if (in_flight.has(want.page_index)) {
506
- this.#deduped_wants++;
507
-
508
- continue;
509
- }
510
-
511
- fresh.push({ reader, want });
512
- }
513
- }
514
-
515
- if (fresh.length === 0) {
516
- return;
517
- }
518
-
519
- fresh.sort((a, b) => b.want.priority - a.want.priority);
520
-
521
- const batch = fresh.slice(0, this.#max_wants_per_frame);
522
-
523
- this.#dropped_wants += fresh.length - batch.length;
524
-
525
- /**
526
- * Back into per-reader lists, because a reader is what owns a fetch.
527
- * @type {Map<VGeoContainerReader, VGeoPageWant[]>}
528
- */
529
- const by_reader = new Map();
530
-
531
- for (const { reader, want } of batch) {
532
- const list = by_reader.get(reader) ?? [];
533
-
534
- list.push(want);
535
-
536
- by_reader.set(reader, list);
537
- }
538
-
539
- for (const [reader, wants] of by_reader) {
540
- this.#dispatch_wants(reader, wants);
541
- }
542
- }
543
-
544
- /**
545
- * @param {VGeoContainerReader} reader
546
- * @returns {Set<number>} page indices this loop has asked that reader for and not heard back
547
- */
548
- #in_flight_of(reader) {
549
- let in_flight = this.#in_flight_pages.get(reader);
550
-
551
- if (in_flight === undefined) {
552
- in_flight = new Set();
553
-
554
- this.#in_flight_pages.set(reader, in_flight);
555
- }
556
-
557
- return in_flight;
558
- }
559
-
560
- /**
561
- * Hand one reader's share of this frame's batch to the residency manager.
562
- *
563
- * This was `install_wants(...).catch(() => {})` and nothing else, which costs three things that
564
- * compound rather than three separate ones. A page still wanted while its fetch is in flight is
565
- * re-submitted **every frame**, so the shared `ConcurrencyGate` accumulates one stale closure
566
- * per frame per wanted page and the RAM budget is charged for the same page over and over. A
567
- * batch that is still running when the next arrives has no way to be told to stop. And an empty
568
- * catch turns a reader that has quarantined every page it was asked for into silence
569
- * (REVIEW_LEDGER M11).
570
- *
571
- * So: a page already in flight is not asked for again, the whole batch carries one
572
- * `AbortSignal` that {@link abort} can pull, and a failure is counted and reported once rather
573
- * than swallowed once per frame. The dedup and the frame's limit are {@link #dispatch_frame}'s,
574
- * one level up, because both are properties of the frame rather than of a reader.
575
- *
576
- * @param {VGeoContainerReader} reader
577
- * @param {VGeoPageWant[]} batch already deduped and inside this frame's limit
578
- */
579
- #dispatch_wants(reader, batch) {
580
- const in_flight = this.#in_flight_of(reader);
581
-
582
- for (const want of batch) {
583
- in_flight.add(want.page_index);
584
- }
585
-
586
- const controller = new AbortController();
587
-
588
- this.#controllers.add(controller);
589
-
590
- this.#residency.install_wants(reader, batch, batch.length, controller.signal)
591
- .then(() => {
592
- if (controller.signal.aborted) {
593
- return;
594
- }
595
-
596
- /*
597
- A failed fetch does not come back as a rejection. `VGeoResidencyManager` catches it
598
- per page — the cut stands without any one page, which is the whole point of a
599
- clamped cut — so a `.catch` here sees only a programming error, and the campaign
600
- closed M11 with the reporting on exactly that branch. A reader that has quarantined
601
- every page asked of it was therefore still silent (re-review 2026-09-12).
602
-
603
- The reader's quarantine is where the fact lives, so that is what is read. Every
604
- page of the batch quarantined is an asset that cannot be streamed at all, which is
605
- the condition worth one line in the console; a page or two is ordinary attrition
606
- and is on `VGeoResidencyManager.quarantined_page_count` for whoever is watching.
607
- */
608
- let quarantined = 0;
609
-
610
- for (const want of batch) {
611
- if (reader.is_quarantined(want.page_index)) {
612
- quarantined++;
613
- }
614
- }
615
-
616
- if (quarantined < batch.length) {
617
- return;
618
- }
619
-
620
- this.#failed_batches++;
621
-
622
- // once per batch, not once per frame per page: the dedup one level up is what makes
623
- // that difference, and a reader that has quarantined everything asked of it is a
624
- // fact somebody has to be able to see
625
- console.warn(
626
- `vgeo: every one of ${batch.length} page fetches was quarantined`
627
- + ` by ${reader.name ?? 'a reader'}`
628
- );
629
- })
630
- .catch(error => {
631
- if (controller.signal.aborted) {
632
- return;
633
- }
634
-
635
- this.#failed_batches++;
636
-
637
- console.warn('vgeo: a batch of page fetches failed', error);
638
- })
639
- .finally(() => {
640
- this.#controllers.delete(controller);
641
-
642
- for (const want of batch) {
643
- in_flight.delete(want.page_index);
644
- }
645
- });
646
- }
647
-
648
- /**
649
- * Stop every fetch this loop started.
213
+ * Stop every fetch the residency has on the wire.
650
214
  *
651
215
  * @returns {void}
652
216
  */
653
217
  abort() {
654
- for (const controller of Array.from(this.#controllers)) {
655
- controller.abort();
656
- }
657
-
658
- this.#controllers.clear();
659
- this.#in_flight_pages.clear();
218
+ this.#residency.abort();
660
219
  }
661
220
 
662
221
  /**
663
222
  * Give up on this loop: the same as {@link abort}, under the name a host tears things down by.
664
223
  *
665
224
  * The loop owns no device resource, so there is nothing else to release — what it owns is
666
- * outstanding *work*, and the reason to have this verb at all is that `abort` reads like a
667
- * transient control ("stop what is in flight, carry on") while a teardown is final.
225
+ * outstanding *work*.
668
226
  *
669
227
  * @returns {void}
670
228
  */