@woosh/meep-engine 3.29.0 → 3.30.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 (226) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +1 -1
  4. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  5. package/src/core/binary/BinaryBuffer.js +14 -2
  6. package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
  7. package/src/core/bvh2/bvh3/BVH.js +14 -1
  8. package/src/core/cache/FrequencySketch.d.ts +3 -1
  9. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  10. package/src/core/cache/FrequencySketch.js +10 -4
  11. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
  12. package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
  13. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
  14. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
  15. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
  16. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +48 -18
  17. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  18. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +262 -70
  19. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
  20. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
  21. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
  22. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
  23. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
  24. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
  25. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
  26. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
  27. package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
  28. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
  29. package/src/core/math/lookup/ParameterLookupTable.js +10 -5
  30. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  31. package/src/core/math/noise/sdnoise.js +16 -10
  32. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  33. package/src/core/model/object/ImmutableObjectPool.js +17 -7
  34. package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
  35. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  36. package/src/core/process/executor/ConcurrentExecutor.js +95 -1
  37. package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
  38. package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
  39. package/src/core/process/task/task_find_dependency_cycle.js +81 -0
  40. package/src/core/wfc/WFCCellChoice.d.ts +17 -0
  41. package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
  42. package/src/core/wfc/WFCCellChoice.js +36 -0
  43. package/src/core/wfc/WFCLattice.d.ts +105 -0
  44. package/src/core/wfc/WFCLattice.d.ts.map +1 -0
  45. package/src/core/wfc/WFCLattice.js +235 -0
  46. package/src/core/wfc/WFCRuleTable.d.ts +172 -0
  47. package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
  48. package/src/core/wfc/WFCRuleTable.js +334 -0
  49. package/src/core/wfc/WFCState.d.ts +273 -0
  50. package/src/core/wfc/WFCState.d.ts.map +1 -0
  51. package/src/core/wfc/WFCState.js +552 -0
  52. package/src/core/wfc/WFCStatus.d.ts +11 -0
  53. package/src/core/wfc/WFCStatus.d.ts.map +1 -0
  54. package/src/core/wfc/WFCStatus.js +28 -0
  55. package/src/core/wfc/WFCTileWeights.d.ts +48 -0
  56. package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
  57. package/src/core/wfc/WFCTileWeights.js +86 -0
  58. package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
  59. package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
  60. package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
  61. package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
  62. package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
  63. package/src/core/wfc/wfc_estimate_bytes.js +47 -0
  64. package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
  65. package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
  66. package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
  67. package/src/core/wfc/wfc_solve.d.ts +21 -0
  68. package/src/core/wfc/wfc_solve.d.ts.map +1 -0
  69. package/src/core/wfc/wfc_solve.js +154 -0
  70. package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
  71. package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
  72. package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
  73. package/src/core/wfc/wfc_state_assign.d.ts +11 -0
  74. package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
  75. package/src/core/wfc/wfc_state_assign.js +36 -0
  76. package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
  77. package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
  78. package/src/core/wfc/wfc_state_ban_bits.js +81 -0
  79. package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
  80. package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
  81. package/src/core/wfc/wfc_state_collapse.js +73 -0
  82. package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
  83. package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
  84. package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
  85. package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
  86. package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
  87. package/src/core/wfc/wfc_state_propagate.js +155 -0
  88. package/src/core/wfc/wfc_state_read.d.ts +11 -0
  89. package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
  90. package/src/core/wfc/wfc_state_read.js +38 -0
  91. package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
  92. package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
  93. package/src/core/wfc/wfc_state_restrict.js +39 -0
  94. package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
  95. package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
  96. package/src/core/wfc/wfc_state_set_eligible.js +49 -0
  97. package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
  98. package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
  99. package/src/core/wfc/wfc_state_undo_to.js +84 -0
  100. package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
  101. package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
  102. package/src/core/wfc/wfc_words_per_cell.js +16 -0
  103. package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
  104. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  105. package/src/engine/ecs/EntityComponentDataset.js +37 -14
  106. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
  107. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
  108. package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
  109. package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
  110. package/src/engine/ecs/guid/UUID.js +45 -17
  111. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
  112. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
  113. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  114. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
  115. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
  116. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
  117. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  118. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
  119. package/src/engine/interpolation/Interpoland.d.ts +7 -1
  120. package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
  121. package/src/engine/interpolation/Interpoland.js +8 -1
  122. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  123. package/src/engine/interpolation/InterpolationSystem.js +3 -0
  124. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
  125. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
  126. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
  127. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
  128. package/src/engine/interpolation/pose_interpoland.js +2 -0
  129. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  130. package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
  131. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  132. package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
  133. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  134. package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
  135. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
  136. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
  137. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
  138. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
  139. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
  140. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
  141. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
  142. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
  143. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
  144. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
  145. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
  146. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
  147. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
  148. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  149. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  150. package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
  151. package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
  152. package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
  153. package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
  154. package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
  155. package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
  156. package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
  157. package/src/format/image/avif/av1/util/round2.d.ts +19 -2
  158. package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
  159. package/src/format/image/avif/av1/util/round2.js +51 -27
  160. package/src/format/image/avif/heif/parse_avif_file.js +24 -0
  161. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  162. package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
  163. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +46 -3
  164. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +91 -1
  165. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +0 -13
  166. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  167. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +0 -15
  168. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +5 -5
  169. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +11 -1
  170. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -1
  171. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +11 -1
  172. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +18 -9
  173. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
  174. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +20 -26
  175. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +1 -3
  176. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +2 -5
  178. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -1
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
  181. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
  182. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
  183. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
  184. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
  185. package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
  186. package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
  187. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  188. package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
  189. package/src/shade/renderer/particles/DESIGN.md +14 -5
  190. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
  191. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  192. package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
  193. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  194. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
  195. package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
  196. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  197. package/src/shade/renderer/particles/graph_particles.js +8 -2
  198. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  199. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
  200. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
  202. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
  203. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
  204. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
  205. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
  206. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  207. package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
  208. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
  209. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
  210. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
  211. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  212. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
  213. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  214. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
  215. package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
  216. package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
  217. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
  218. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
  219. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
  220. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
  221. package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
  222. package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
  223. package/src/view/tooltip/DomTooltipManager.js +22 -0
  224. package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
  225. package/src/view/tooltip/DomTooltipObserver.js +13 -3
  226. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.js +0 -45
@@ -7,8 +7,9 @@
7
7
  * guard is not cosmetic: `1 << -1` is `1 << 31` under JavaScript's shift-count masking, which would
8
8
  * silently produce garbage.
9
9
  *
10
- * `x` must fit in a signed 32-bit integer, which every intermediate the specification feeds to this
11
- * function does.
10
+ * `x + 2^(n-1)` must fit in a signed 32-bit integer — the rounding term is added before the shift, so
11
+ * the bound is slightly tighter than `x` itself. Most intermediates the specification feeds this do;
12
+ * the self-guided restoration box filter's do not, and use {@link round2_wide} instead.
12
13
  *
13
14
  * @param {number} x
14
15
  * @param {number} n number of bits to shift away, `>= 0`
@@ -19,4 +20,20 @@
19
20
  * @copyright Company Named Limited (c) 2026
20
21
  */
21
22
  export function round2(x: number, n: number): number;
23
+ /**
24
+ * {@link round2} for operands that run past a signed 32-bit integer.
25
+ *
26
+ * `>>` applies ToInt32 to its left operand, so the shifted form silently wraps above 2^31 and comes
27
+ * back negative — the self-guided restoration box filter's `p * s` reaches 3.8e9 on an ordinary
28
+ * black/white edge, where `round2` returned -484 against an exact 3612. Every JavaScript number is
29
+ * already a double, so the product itself is exact to 2^53; only the shift needs replacing with a
30
+ * floor division, which is what the specification's Round2 says in the first place. Same shape as
31
+ * `round2_12` in the inverse transform, which hit this earlier.
32
+ *
33
+ * @param {number} x
34
+ * @param {number} n number of bits to shift away, `>= 0`
35
+ * @returns {number}
36
+ * @see AV1 spec 4.7 "Mathematical functions"
37
+ */
38
+ export function round2_wide(x: number, n: number): number;
22
39
  //# sourceMappingURL=round2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"round2.d.ts","sourceRoot":"","sources":["../../../../../../../src/format/image/avif/av1/util/round2.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,0BARW,MAAM,KACN,MAAM,GACJ,MAAM,CAYlB"}
1
+ {"version":3,"file":"round2.d.ts","sourceRoot":"","sources":["../../../../../../../src/format/image/avif/av1/util/round2.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,0BARW,MAAM,KACN,MAAM,GACJ,MAAM,CAYlB;AAED;;;;;;;;;;;;;;GAcG;AACH,+BALW,MAAM,KACN,MAAM,GACJ,MAAM,CASlB"}
@@ -1,27 +1,51 @@
1
- /**
2
- * `Round2( x, n )` from the AV1 specification: divide by `2^n` and round half up, rounding towards
3
- * negative infinity on the halfway case for negative inputs.
4
- *
5
- * The specification defines this with real division and a floor; JavaScript's `>>` is an arithmetic
6
- * shift, which is exactly that floor, so the integer form transliterates directly. The `n === 0`
7
- * guard is not cosmetic: `1 << -1` is `1 << 31` under JavaScript's shift-count masking, which would
8
- * silently produce garbage.
9
- *
10
- * `x` must fit in a signed 32-bit integer, which every intermediate the specification feeds to this
11
- * function does.
12
- *
13
- * @param {number} x
14
- * @param {number} n number of bits to shift away, `>= 0`
15
- * @returns {number}
16
- * @see AV1 spec 4.7 "Mathematical functions"
17
- *
18
- * @author Alex Goldring
19
- * @copyright Company Named Limited (c) 2026
20
- */
21
- export function round2(x, n) {
22
- if (n === 0) {
23
- return x;
24
- }
25
-
26
- return (x + (1 << (n - 1))) >> n;
27
- }
1
+ /**
2
+ * `Round2( x, n )` from the AV1 specification: divide by `2^n` and round half up, rounding towards
3
+ * negative infinity on the halfway case for negative inputs.
4
+ *
5
+ * The specification defines this with real division and a floor; JavaScript's `>>` is an arithmetic
6
+ * shift, which is exactly that floor, so the integer form transliterates directly. The `n === 0`
7
+ * guard is not cosmetic: `1 << -1` is `1 << 31` under JavaScript's shift-count masking, which would
8
+ * silently produce garbage.
9
+ *
10
+ * `x + 2^(n-1)` must fit in a signed 32-bit integer — the rounding term is added before the shift, so
11
+ * the bound is slightly tighter than `x` itself. Most intermediates the specification feeds this do;
12
+ * the self-guided restoration box filter's do not, and use {@link round2_wide} instead.
13
+ *
14
+ * @param {number} x
15
+ * @param {number} n number of bits to shift away, `>= 0`
16
+ * @returns {number}
17
+ * @see AV1 spec 4.7 "Mathematical functions"
18
+ *
19
+ * @author Alex Goldring
20
+ * @copyright Company Named Limited (c) 2026
21
+ */
22
+ export function round2(x, n) {
23
+ if (n === 0) {
24
+ return x;
25
+ }
26
+
27
+ return (x + (1 << (n - 1))) >> n;
28
+ }
29
+
30
+ /**
31
+ * {@link round2} for operands that run past a signed 32-bit integer.
32
+ *
33
+ * `>>` applies ToInt32 to its left operand, so the shifted form silently wraps above 2^31 and comes
34
+ * back negative — the self-guided restoration box filter's `p * s` reaches 3.8e9 on an ordinary
35
+ * black/white edge, where `round2` returned -484 against an exact 3612. Every JavaScript number is
36
+ * already a double, so the product itself is exact to 2^53; only the shift needs replacing with a
37
+ * floor division, which is what the specification's Round2 says in the first place. Same shape as
38
+ * `round2_12` in the inverse transform, which hit this earlier.
39
+ *
40
+ * @param {number} x
41
+ * @param {number} n number of bits to shift away, `>= 0`
42
+ * @returns {number}
43
+ * @see AV1 spec 4.7 "Mathematical functions"
44
+ */
45
+ export function round2_wide(x, n) {
46
+ if (n === 0) {
47
+ return x;
48
+ }
49
+
50
+ return Math.floor((x + 2 ** (n - 1)) / 2 ** n);
51
+ }
@@ -170,6 +170,22 @@ function read_iloc(file, cursor) {
170
170
 
171
171
  check_count(item_count, "iloc");
172
172
 
173
+ /**
174
+ * What one extent record costs in the file. `uint_of_size(0)` returns 0 without consuming a
175
+ * byte, so with all three widths at zero an extent is free in the file and an `ItemExtent` in
176
+ * memory — 644 bytes asked for 6.5 million of them, and a full `item_count` asks for 4.3
177
+ * billion, which is an OOM. Every other width is self-limiting, because the cursor throws when
178
+ * it runs out; this one is not, so it is refused outright. An extent that carries neither an
179
+ * offset nor a length describes nothing anyway.
180
+ */
181
+ const extent_record_bytes = index_size + offset_size + length_size;
182
+
183
+ /**
184
+ * Across the whole box, not per item. `check_count` bounds each `extent_count` on its own, and
185
+ * nothing bounded the product — which is the other half of the same arithmetic.
186
+ */
187
+ let total_extent_count = 0;
188
+
173
189
  for (let i = 0; i < item_count; i++) {
174
190
  const id = version < 2 ? cursor.u16() : cursor.u32();
175
191
 
@@ -186,6 +202,14 @@ function read_iloc(file, cursor) {
186
202
 
187
203
  check_count(extent_count, "iloc extents");
188
204
 
205
+ if (extent_count > 0 && extent_record_bytes === 0) {
206
+ throw new Error(`iloc declares ${extent_count} extents of zero width, which cannot be represented`);
207
+ }
208
+
209
+ total_extent_count += extent_count;
210
+
211
+ check_count(total_extent_count, "iloc extents in total");
212
+
189
213
  for (let j = 0; j < extent_count; j++) {
190
214
  if (index_size > 0) {
191
215
  cursor.uint_of_size(index_size);
@@ -1 +1 @@
1
- {"version":3,"file":"GPUTypedTable.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/buffer/table/GPUTypedTable.js"],"names":[],"mappings":"AAwEA;;;GAGG;AACH;IAyKI;;;OAGG;IACH,wBAFW,uBAAuB,EASjC;IAlLD;;OAEG;IACH,YAFU,uBAAuB,CAEtB;IAEX;;;;OAIG;IACH,OAFU,IAAI,MAAM,EAAC,eAAe,CAAC,CAEnB;IAElB;;;;OAIG;IACH,uBAFU,YAAY,CAE6D;IAEnF;;;;;;;;;;;OAWG;IACH,4BAFU,YAAY,CAE6B;IAEnD;;;;;OAKG;IACH,qBAFU,YAAY,CAEsB;IAiF5C;;;;;OAKG;IACH,iBAFU,MAAM,CAEe;IAE/B;;;;OAIG;IACH,WAFU,MAAM,CAES;IAmBzB;;;;;;OAMG;IACH,oBAFU,MAAM,CAEkB;IAElC;;;OAGG;IACH,kBAFU,cAAc,GAAC,SAAS,CAEjB;IAejB;;;;;OAKG;IACH,WAHW,CAAC,GACC,MAAM,CAQlB;IAED;;;;;;;OAOG;IACH,kBAJW,CAAC,UACD,eAAe,GACb,MAAM,CAQlB;IAED;;;;;OAKG;IACH,sBAFW,MAAM,QAsChB;IAED;;;;OAIG;IACH,0BAIC;IAED;;;;;;OAMG;IACH,mBAHW,MAAM,GACJ,MAAM,CAUlB;IAsBD;;;;;;;;;;OAUG;IACH,cAuBC;IAmGD;;;;;;OAMG;IACH,mBAJW,MAAM,GACJ,CAAC,GAAC,SAAS,CA4BvB;IAgDD;;;OAGG;IACH,cAsBC;IA+BD;;;;OAIG;IACH,mBAHW,MAAM,SACN,CAAC,QAyEX;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0BAJW,MAAM,SACN,CAAC,UACD,eAAe,QA4DzB;IAED;;;;;;;;;;;;;OAaG;IACH,yBALW,MAAM,eACN,MAAM,oBAEN,UAAU,QAqBpB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,yBANW,MAAM,eACN,MAAM,UACN,WAAW,GAAC,eAAe,kBAC3B,MAAM,gBACN,MAAM,QA+DhB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,0BAJW,MAAM,SACN,CAAC,UACD,eAAe,QA6GzB;IA0KD;;;OAGG;IACH,8BASC;IAED;;;OAGG;IACH,2BAYC;IAED;;OAEG;IACH,oBAEC;IAED;;;;;;;;;;OAUG;IACH,+BAEC;IAED;;;;;;;;;;OAUG;IACH,mCAJW,WAAW,UACX,MAAM,GACL,MAAM,CAoBjB;IAED;;;;;;;;;;;;OAYG;IACH,kCAMC;IAED;;;;;;;;;;;;;;OAcG;IACH,mCAHW,MAAM,GACL,MAAM,CAKjB;IAED,mBAEC;;CACJ;sCAp1CY,OAAO,8BAA8B,EAAE,uBAAuB;6BAC9D,OAAO,qBAAqB,EAAE,cAAc;yBAC5C,OAAO,iCAAiC,EAAE,UAAU;8BACpD,OAAO,mCAAmC,EAAE,eAAe;gCAPxC,sBAAsB;6BAhBzB,yCAAyC;uBAC/C,mCAAmC"}
1
+ {"version":3,"file":"GPUTypedTable.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/buffer/table/GPUTypedTable.js"],"names":[],"mappings":"AAwEA;;;GAGG;AACH;IAyKI;;;OAGG;IACH,wBAFW,uBAAuB,EASjC;IAlLD;;OAEG;IACH,YAFU,uBAAuB,CAEtB;IAEX;;;;OAIG;IACH,OAFU,IAAI,MAAM,EAAC,eAAe,CAAC,CAEnB;IAElB;;;;OAIG;IACH,uBAFU,YAAY,CAE6D;IAEnF;;;;;;;;;;;OAWG;IACH,4BAFU,YAAY,CAE6B;IAEnD;;;;;OAKG;IACH,qBAFU,YAAY,CAEsB;IAiF5C;;;;;OAKG;IACH,iBAFU,MAAM,CAEe;IAE/B;;;;OAIG;IACH,WAFU,MAAM,CAES;IAmBzB;;;;;;OAMG;IACH,oBAFU,MAAM,CAEkB;IAElC;;;OAGG;IACH,kBAFU,cAAc,GAAC,SAAS,CAEjB;IAejB;;;;;OAKG;IACH,WAHW,CAAC,GACC,MAAM,CAQlB;IAED;;;;;;;OAOG;IACH,kBAJW,CAAC,UACD,eAAe,GACb,MAAM,CAQlB;IAED;;;;;OAKG;IACH,sBAFW,MAAM,QAsChB;IAED;;;;OAIG;IACH,0BAIC;IAED;;;;;;OAMG;IACH,mBAHW,MAAM,GACJ,MAAM,CAUlB;IAsBD;;;;;;;;;;OAUG;IACH,cAuBC;IAmGD;;;;;;OAMG;IACH,mBAJW,MAAM,GACJ,CAAC,GAAC,SAAS,CA0CvB;IAgDD;;;OAGG;IACH,cAsBC;IA+BD;;;;OAIG;IACH,mBAHW,MAAM,SACN,CAAC,QAyEX;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0BAJW,MAAM,SACN,CAAC,UACD,eAAe,QA4DzB;IAED;;;;;;;;;;;;;OAaG;IACH,yBALW,MAAM,eACN,MAAM,oBAEN,UAAU,QAqBpB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,yBANW,MAAM,eACN,MAAM,UACN,WAAW,GAAC,eAAe,kBAC3B,MAAM,gBACN,MAAM,QA+DhB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,0BAJW,MAAM,SACN,CAAC,UACD,eAAe,QA6GzB;IA0KD;;;OAGG;IACH,8BASC;IAED;;;OAGG;IACH,2BAYC;IAED;;OAEG;IACH,oBAEC;IAED;;;;;;;;;;OAUG;IACH,+BAEC;IAED;;;;;;;;;;OAUG;IACH,mCAJW,WAAW,UACX,MAAM,GACL,MAAM,CAoBjB;IAED;;;;;;;;;;;;OAYG;IACH,kCAMC;IAED;;;;;;;;;;;;;;OAcG;IACH,mCAHW,MAAM,GACL,MAAM,CAKjB;IAED,mBAEC;;CACJ;sCAl2CY,OAAO,8BAA8B,EAAE,uBAAuB;6BAC9D,OAAO,qBAAqB,EAAE,cAAc;yBAC5C,OAAO,iCAAiC,EAAE,UAAU;8BACpD,OAAO,mCAAmC,EAAE,eAAe;gCAPxC,sBAAsB;6BAhBzB,yCAAyC;uBAC/C,mCAAmC"}
@@ -519,6 +519,20 @@ export class GPUTypedTable {
519
519
  * @throws {Error} if the element is not CPU-resident and not in the upload buffer
520
520
  */
521
521
  get(element_index) {
522
+ if (!this.occupancy.get(element_index)) {
523
+ /**
524
+ * A freed row is not a record. {@link remove} zero-fills the shadow bytes and clears the
525
+ * bit but leaves the page allocated, so reading the bytes back gave a fully-formed
526
+ * record of zeros — against the `@returns {T|undefined}` one line above, which callers
527
+ * have been written to. Zero is a valid row index, so a caller walking a linked chain
528
+ * out of such a record walks to row 0 and back to row 0, forever.
529
+ *
530
+ * The occupancy bit is already the authority `#resolve` and `debug_table_read_all` read;
531
+ * the marshalling paths that deliberately ignore it do not come through here.
532
+ */
533
+ return undefined;
534
+ }
535
+
522
536
  const page_index = Math.floor(element_index / this.descriptor.elements_per_page);
523
537
  const page = this.pages.get(page_index);
524
538
 
@@ -223,9 +223,7 @@ Distilled from the pixel-error musings
223
223
  Pinned:
224
224
 
225
225
  - **Error is an object-space distance from the *source* surface**, not a density heuristic
226
- and not a sum of per-step deviations. It is measured before faceless vertices are cleaned
227
- away, so that a patch consumed whole — which leaves nothing behind to be measured — still
228
- costs what it cost. Vertex quadrics are built once from the source
226
+ and not a sum of per-step deviations. Vertex quadrics are built once from the source
229
227
  triangles and carried through the entire build: a survivor absorbs its victim's quadric at
230
228
  every collapse, at every round, so a vertex many rounds up still holds the planes of every
231
229
  source triangle folded into it, and evaluating that quadric at its current position
@@ -234,6 +232,51 @@ Pinned:
234
232
  `evaluate` is a sum of squared distances whose scale grows with the number of planes in the
235
233
  quadric, and its root is not a length. It remains a calibrated measure rather than a proven
236
234
  Hausdorff bound (blind to tangential drift), hence the sampled-Hausdorff gate in §3.
235
+ - **It is read at the collapses, per endpoint, and kept as a running maximum** — not as one
236
+ sweep of the surviving vertices at the end of a pass. The weight that makes the number a
237
+ length is also a divisor that grows every time a vertex absorbs another, so a vertex that
238
+ destroyed a feature and then spent the rest of the pass swallowing surface it sits exactly
239
+ on has that feature divided by all of it. The dilution therefore deepens with the DAG, and
240
+ understates most at the coarse levels where the cut spends its budget: measured on a unit
241
+ spike at the centre of a pinned grid, the same spike came back at `0.64` on a 7-vertex grid
242
+ and at `0.040` on a 33-vertex one. Read at each collapse the same spike reads `0.65`–`0.71`
243
+ at every size. Reading the two endpoints apart rather than summed is the same argument one
244
+ collapse at a time — the sum's reading is the weighted mean of the two, which averages a
245
+ small victim's residual into a large survivor's untouched history.
246
+
247
+ Nothing is given up by no longer sweeping the survivors, and it is a domination rather than
248
+ a coincidence. What the sweep reads off a survivor is its *merged* quadric at the placement
249
+ its last collapse chose, and a merged quadric's reading is the weight-weighted mean of its two
250
+ parts' readings at the same point — so it can never exceed the larger of the two already taken
251
+ there. Every value the sweep could report is one this maximum has seen or beaten, and the
252
+ maximum holds the earlier and sharper readings besides. A patch consumed whole, which leaves
253
+ behind a faceless vertex the cleanup then sweeps away, is likewise read at the collapse that
254
+ consumed it.
255
+
256
+ Each pass therefore reports what *its own* collapses cost, and a caller carrying quadrics
257
+ across passes carries the maximum across them too (`vgeo_build_group_step` across its
258
+ attempts, and the child floor below across levels). A vertex that arrives already deviating
259
+ needs no re-reading: that reading is what the pass which placed it there already returned.
260
+
261
+ Superseded: measuring after the pass, "before faceless vertices are cleaned away, so that a
262
+ patch consumed whole still costs what it cost". The measurement point was right about the
263
+ faceless vertex and wrong about everything the sweep threw away to get there.
264
+ - **A plane weighs the surface behind it, as a multiple of the mesh's mean triangle.** The
265
+ mean the error is read as has to be an average over the *surface*; over a plane count it is
266
+ an average over how the surface was cut, and a finely tessellated patch outvotes a coarse
267
+ one many times its size. Cutting one face in two along its own plane is the clean statement
268
+ of the defect — it adds no surface, and the metric heard the same plane twice. Weights are
269
+ ratios rather than areas because everything they are stated against is a ratio (the boundary
270
+ constraint is three *faces*; the attribute quadric summed with these weighs one per
271
+ triangle), and a slot holding square metres would make all of it depend on whether the asset
272
+ was authored in millimetres or kilometres. The reported error is a mean, which divides the
273
+ weights out again, so normalizing costs nothing there; it buys back every calibration.
274
+
275
+ This changes the quadric **matrix**, so it changes the placement solve and not only the
276
+ number — a behavioural change to simplification, landed on its own so it can be attributed.
277
+ `VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS` was calibrated against the diluted reading and is
278
+ **not** re-tuned here; it needs re-tuning against this one, separately.
279
+
237
280
  - **Composition across levels does not happen**, and that is the point of carrying quadrics.
238
281
  The measured value is already deviation from the source; a step takes `max(measured, max(e
239
282
  of child groups))`, where the floor exists only to impose monotonicity — the accumulated
@@ -1,4 +1,94 @@
1
- # Wedges — plan, 2026-09-18
1
+ # Wedges — plan, 2026-09-18 — **HALF OF IT REVERTED 2026-09-19**
2
+
3
+ > **The partition stayed. The crease angle went.** This document was written as one idea and it is
4
+ > two, and only one of them survived being measured in pixels.
5
+ >
6
+ > **What went: inferring a hard edge from an angle.** `VGEO_CREASE_ANGLE`, 45 degrees, applied
7
+ > where the builder has to *synthesize* a normal because the source authored none. It made a
8
+ > POSITION-only cube ship three normals at each corner instead of one that is 54.7 degrees wrong for
9
+ > every face using it, which is a real improvement to look at. Measured against a true LOD 0
10
+ > reference it bought nothing on any object tested, and on high-frequency relief it *cost*: the
11
+ > attribute quadric's least-squares fit lands near the neighbourhood's mean normal, which is the
12
+ > right filtered answer for a Lambertian surface, and bucketing each tile face on its own returns
13
+ > that one face's normal exactly instead. It was also most of the price — on the pebble-scatter
14
+ > geometry the wedge build cost +3.37% of asset bytes over pre-attribute with the threshold in and
15
+ > +0.97% without it.
16
+ >
17
+ > **What stayed: carrying the shading frame a source actually authored.** `BtWedgeSet`,
18
+ > `vgeo_mesh_wedges`, and the simplifier's wedge bookkeeping. The partition is decided by
19
+ > `loop_data.record_equal()` — byte equality of what the source gave — so it needs no threshold and
20
+ > makes no judgement about content. On a UE5 courtyard gate arch (188,180 triangles, authored
21
+ > normals and UVs), taking it out costs, at **equal drawn cluster count**, mean luminance against
22
+ > that build's own LOD 0:
23
+ >
24
+ > | ue_arch | τ=0.5 | τ=1 | τ=2 | τ=4 |
25
+ > |---|---|---|---|---|
26
+ > | without the partition | +1.18 | +6.50 | +6.91 | +8.82 |
27
+ > | *same build, second page load* | −0.06 | +0.07 | +0.06 | +0.06 |
28
+ >
29
+ > That is 20–27% more error against a noise floor of 0.07, and it reproduces. At τ=0.5, 1 and 2 the
30
+ > build without the partition is not even trading: it draws **more** clusters *and* errs more. Only
31
+ > τ=4 is a real trade (−20% clusters for +27% error), and interpolated it sits on the same curve.
32
+ >
33
+ > **It is a normals result, not a UV one.** The playground binds one untextured grey
34
+ > `StandardShadeMaterial` and samples no maps, so nothing in that table can come from texture
35
+ > coordinates. Seams are carried by the same mechanism and that is worth having, but it is a
36
+ > different question and it is not what was measured here.
37
+ >
38
+ > **And it helps where the frame is synthesized too**, now that the threshold is gone. Corners still
39
+ > group, by connectivity rather than by angle: at a mesh boundary or a non-manifold fin there is no
40
+ > second face to average with, so each side keeps its own normal instead of inventing a join the
41
+ > mesh does not make. On the pebble scatter that costs +0.45% of bytes over having no partition at
42
+ > all, and buys — again at equal drawn clusters, against pre-attribute —
43
+ >
44
+ > | mesh 1400 | τ=0.5 | τ=1 | τ=2 | τ=4 |
45
+ > |---|---|---|---|---|
46
+ > | no partition | +1.55 | +4.87 | +5.42 | +7.08 |
47
+ > | partition, no threshold | +4.95 | **+4.03** | **−2.51** | **−5.30** |
48
+ >
49
+ > Pareto-dominant at τ=1, 2 and 4 — fewer clusters *and* less error — and at the two coarse cuts it
50
+ > beats pre-attribute outright. The finest cut is worse, and that is the honest caveat.
51
+ >
52
+ > **The split is proved by byte identity, not only by pixels.** Built with the threshold removed,
53
+ > the gate arch is byte-identical to the full wedge build (the whole win preserved, nothing else
54
+ > changed), and a UE5 throne-room floor is byte-identical to a build with no wedge code at all.
55
+ >
56
+ > **What this does *not* claim.** Not that the N-dimensional attribute quadrics are themselves a
57
+ > pixel win — measured the same way they are a wash-to-worse on three of five objects, and that
58
+ > deserves its own investigation. Not that Zorah's normals are fine: on a POSITION-only source every
59
+ > normal is synthesized by `vgeo_normalize_source`, area-weighted, after the weld, nothing pinned,
60
+ > so a hard edge cannot exist at *any* level including the finest — that is the flat-looking tiled
61
+ > surface, it is upstream of everything here, and the UE5 rebuild is what fixes it. And not that the
62
+ > cut is free: on that floor, pre-attribute draws 80 clusters where both attribute builds draw 513
63
+ > at the same τ, a 6x difference nobody asked for.
64
+ >
65
+ > **The pixel harness is the other thing worth keeping** (`container_diff.mjs`, scratchpad pattern).
66
+ > Four ways it lied before it worked, each of which had already produced a confident wrong answer:
67
+ >
68
+ > 1. **TAA.** Two captures of one static camera differ by mean 4.0 with *not one pixel identical*.
69
+ > Pinning the jitter is necessary and insufficient — the resolve un-jitters against its own copy,
70
+ > so one run settles to 0.004 and an identical one plateaus at 0.153 forever. Average 16 frames.
71
+ > 2. **Dilution.** The object covered 1–2% of the viewport, so every mean read 0.2–0.7 with p95 of
72
+ > exactly 0.0. Mask to lit pixels and print the coverage.
73
+ > 3. **The stats panel**, which prints the file name and byte counts and so differs per build by
74
+ > construction — and the crop keeping it out was slicing through the object.
75
+ > 4. **Cross-run image diffs are worthless.** The same build in two page loads, with identical
76
+ > `drawn` counts, differs by mean 15–17. Only a build's *own* LOD-0 curve is comparable.
77
+ >
78
+ > A whole scene has no reachable LOD 0 either: below about τ=0.25 the residency budget picks the cut
79
+ > and at τ→0 the renderer stops producing frames, so single containers are the only honest reference.
80
+ >
81
+ > **Compare at equal drawn clusters, never at equal τ**, which is a request and not a price — the
82
+ > wedge build drew 1,085 where pre-attribute drew 772 at the same number on the slider.
83
+ >
84
+ > **Choosing what to measure was half the work.** Three rounds of picking objects out of
85
+ > `object_bounds` gave a pebble scatter, a bare tree and a set of beams, all nearly pure silhouette,
86
+ > and the difference image is then an outline with black interiors — which says nothing about
87
+ > shading, since attribute quadrics trade geometric accuracy for attribute accuracy. Manifest rows
88
+ > are `(mesh, primitive)`; carry the primitive or you build a different object, as a row claiming
89
+ > 289k triangles did when it built 2.04M. The UE5 rebuild's meshes have *names*, which is worth more
90
+ > than any heuristic over bounds.
91
+
2
92
 
3
93
  `ATTRIBUTE_PLAN_2026_09_18.md` §7, taken up. That plan fixed *how a coarse vertex is given its
4
94
  shading values*; this one fixes the fact that a vertex can only hold **one set of them**, so every
@@ -81,19 +81,6 @@ export class VGeoBuildOptions {
81
81
  * @type {number}
82
82
  */
83
83
  weld_ratio: number;
84
- /**
85
- * How far two triangles may turn across a shared edge before a **synthesized** normal is split
86
- * there rather than averaged, in degrees. See {@link VGEO_CREASE_ANGLE}, which is where the
87
- * default and the measurement behind it live.
88
- *
89
- * A build option rather than only a constant because it is the one number in this work that is a
90
- * judgement about content rather than a consequence of arithmetic: a source of hard-surface
91
- * props and a source of organic scans want different answers, and a conversion should be able to
92
- * say which it has.
93
- *
94
- * @type {number}
95
- */
96
- crease_angle: number;
97
84
  /**
98
85
  * Decoded bytes per page, as a **hard cap**: no page of the asset exceeds it, and the container
99
86
  * says so with a feature bit a runtime can require (`VGEO_FORMAT.md` §11.14). That is what makes
@@ -1 +1 @@
1
- {"version":3,"file":"VGeoBuildOptions.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH;IACI;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAFU,MAAM,CAEW;IAE3B;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAFU,MAAM,CAEmB;IAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,YAFU,MAAM,CAEE;IAElB;;;;;;;;;;;OAWG;IACH,cAFU,MAAM,CAEiB;IAEjC;;;;;;;;;;;;;;OAcG;IACH,mBAFU,MAAM,CAE2B;IAE3C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,yBAFU,MAAM,EAAE,CAEqD;IAEvE;;OAEG;IACH,eAFU,gBAAgB,CAEa;CAC1C;iCAjJgC,6BAA6B"}
1
+ {"version":3,"file":"VGeoBuildOptions.d.ts","sourceRoot":"","sources":["../../../../../../../src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH;IACI;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAFU,MAAM,CAEW;IAE3B;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAFU,MAAM,CAEmB;IAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,YAFU,MAAM,CAEE;IAElB;;;;;;;;;;;;;;OAcG;IACH,mBAFU,MAAM,CAE2B;IAE3C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,yBAFU,MAAM,EAAE,CAEqD;IAEvE;;OAEG;IACH,eAFU,gBAAgB,CAEa;CAC1C;iCAnIgC,6BAA6B"}
@@ -1,4 +1,3 @@
1
- import { VGEO_CREASE_ANGLE } from "./mesh/VGEO_CREASE_ANGLE.js";
2
1
  import { LZ4_LEVEL } from "../../../../../core/binary/lz4/LZ4_LEVEL.js";
3
2
  import { VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS } from "./mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js";
4
3
  import { VGEO_CODEC_DEFLATE } from "../format/frame/VGEO_CODEC_DEFLATE.js";
@@ -91,20 +90,6 @@ export class VGeoBuildOptions {
91
90
  */
92
91
  weld_ratio = 1e-5;
93
92
 
94
- /**
95
- * How far two triangles may turn across a shared edge before a **synthesized** normal is split
96
- * there rather than averaged, in degrees. See {@link VGEO_CREASE_ANGLE}, which is where the
97
- * default and the measurement behind it live.
98
- *
99
- * A build option rather than only a constant because it is the one number in this work that is a
100
- * judgement about content rather than a consequence of arithmetic: a source of hard-surface
101
- * props and a source of organic scans want different answers, and a conversion should be able to
102
- * say which it has.
103
- *
104
- * @type {number}
105
- */
106
- crease_angle = VGEO_CREASE_ANGLE;
107
-
108
93
  /**
109
94
  * Decoded bytes per page, as a **hard cap**: no page of the asset exceeds it, and the container
110
95
  * says so with a feature bit a runtime can require (`VGEO_FORMAT.md` §11.14). That is what makes
@@ -248,11 +248,11 @@ export function vgeo_build_group_step(
248
248
  /**
249
249
  * Largest deviation any attempt measured, rather than the last one's.
250
250
  *
251
- * A vertex left behind by a patch that was consumed whole is faceless, and the simplifier reads
252
- * it before it sweeps it away precisely because it is the only thing still carrying the quadric
253
- * of what used to be there. It is gone by the next attempt, so the next attempt cannot see what
254
- * it cost. The surface only ever moves further from the source as this loop tightens, so the
255
- * running maximum is what the step actually did.
251
+ * Each call reports what *its own* collapses cost ({@link bt_mesh_simplify_with_quadrics}), and
252
+ * the attempts below are one simplification continued rather than several, so no single call's
253
+ * answer is the step's. Carrying the maximum across them is what makes it one. The surface only
254
+ * ever moves further from the source as this loop tightens, so a maximum is also the whole of
255
+ * what is needed — no attempt can undo what an earlier one charged for.
256
256
  * @type {number}
257
257
  */
258
258
  let deviation = 0;
@@ -13,12 +13,22 @@
13
13
  * A collapse that would smear a crease is charged for it, in units of length, at exactly the rate
14
14
  * this names.
15
15
  *
16
- * ## How it was chosen
16
+ * ## How it was chosen — and why that needs doing again
17
17
  *
18
18
  * Swept from `1e-9` — the term effectively off — to `1/2`, measuring how far the shipped normal field
19
19
  * sits from the source's and what happened to `self_error`, which is the number the runtime's cut is
20
20
  * driven by.
21
21
  *
22
+ * **The `self_error` column below is a reading this metric no longer takes.** It was swept when the
23
+ * error was one post-hoc sweep of the surviving vertices, over quadrics that weighed a plane count —
24
+ * a mean that diluted with DAG depth and tracked tessellation rather than surface. Both are fixed
25
+ * (`VIRTUAL_GEOMETRY_DESIGN.md` §4), which moved the number the sweep was reading and changed the
26
+ * matrix that decides where a collapse puts the vertex, which is what this ratio trades against. The
27
+ * *shape* of the finding is unlikely to have moved — above `1/64` it was not a trade on smooth
28
+ * content by an order of magnitude — but the cliff's position is not known any more, and `1/1024`
29
+ * stands here because it was left deliberately untouched rather than because it has been re-measured
30
+ * against the metric that now ships. Re-sweeping it is its own work.
31
+ *
22
32
  * On a 31,874-triangle Zorah primitive, the content the defect was found on:
23
33
  *
24
34
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,mDALU,MAAM,EAAE,CAOhB"}
1
+ {"version":3,"file":"VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AACH,mDALU,MAAM,EAAE,CAOhB"}
@@ -16,12 +16,22 @@ import { VGEO_ATTRIBUTES } from "../../format/attribute/VGEO_ATTRIBUTES.js";
16
16
  * A collapse that would smear a crease is charged for it, in units of length, at exactly the rate
17
17
  * this names.
18
18
  *
19
- * ## How it was chosen
19
+ * ## How it was chosen — and why that needs doing again
20
20
  *
21
21
  * Swept from `1e-9` — the term effectively off — to `1/2`, measuring how far the shipped normal field
22
22
  * sits from the source's and what happened to `self_error`, which is the number the runtime's cut is
23
23
  * driven by.
24
24
  *
25
+ * **The `self_error` column below is a reading this metric no longer takes.** It was swept when the
26
+ * error was one post-hoc sweep of the surviving vertices, over quadrics that weighed a plane count —
27
+ * a mean that diluted with DAG depth and tracked tessellation rather than surface. Both are fixed
28
+ * (`VIRTUAL_GEOMETRY_DESIGN.md` §4), which moved the number the sweep was reading and changed the
29
+ * matrix that decides where a collapse puts the vertex, which is what this ratio trades against. The
30
+ * *shape* of the finding is unlikely to have moved — above `1/64` it was not a trade on smooth
31
+ * content by an order of magnitude — but the cliff's position is not known any more, and `1/1024`
32
+ * stands here because it was left deliberately untouched rather than because it has been re-measured
33
+ * against the metric that now ships. Re-sweeping it is its own work.
34
+ *
25
35
  * On a 31,874-triangle Zorah primitive, the content the defect was found on:
26
36
  *
27
37
  * ```
@@ -1,16 +1,26 @@
1
1
  /**
2
- * Area-weighted normals of a mesh, split at its creases, written into its normal channel.
2
+ * Area-weighted normals of a mesh, written into its normal channel, one per corner.
3
3
  *
4
4
  * The geometry a mesh actually has is the answer for that mesh: carrying a surviving vertex's
5
5
  * original normal through a dozen collapses shades a coarse level by whichever vertices happened to
6
6
  * win, which is visibly wrong.
7
7
  *
8
- * **Per corner group, not per vertex.** Averaging every face around a vertex is right for a surface
9
- * and wrong for a corner. Two faces sharing an edge belong to the same group when the surface turns
10
- * less than {@link VGEO_CREASE_ANGLE} across it; groups are the connected components that leaves,
11
- * and each gets its own area-weighted average. A vertex on a smooth surface has exactly one, and
12
- * this is then the pass it has always been. A box corner has three, and the eight vertices of a cube
13
- * stop carrying a normal that is ninety degrees wrong for every face using it.
8
+ * **It does not invent a crease.** An earlier version of this split a vertex wherever the surface
9
+ * turned more than a threshold, so that a synthesized cube got three normals at each corner instead
10
+ * of one that is ninety degrees wrong for every face using it. That is a real improvement to look
11
+ * at and it did not survive measurement: against a true LOD 0 reference in pixels it bought nothing
12
+ * on any object tested, and on high-frequency relief it *cost*, because the attribute quadric's
13
+ * least-squares fit lands near the neighbourhood's mean normal -- the right filtered answer for a
14
+ * Lambertian surface -- and bucketing each tile face on its own returns that one face's normal
15
+ * exactly instead. So the threshold is gone, and with it the one number in this work that was a
16
+ * judgement about content rather than a consequence of arithmetic.
17
+ *
18
+ * **Corners still group, by connectivity rather than by angle.** Two corners join when a manifold
19
+ * edge joins their faces, so a manifold vertex has exactly one group and this is the pass it has
20
+ * always been, arithmetic included. What the per-corner shape is *for* is the case this pass is not
21
+ * reached in at all: a source that authored its own shading frame, whose distinct corner values the
22
+ * weld would otherwise destroy. That is where wedges are worth their bytes, and it needs no
23
+ * threshold to decide anything.
14
24
  *
15
25
  * Area weighting falls out of the arithmetic rather than being applied: Newell's normal *is* the
16
26
  * face normal scaled by twice the area, so summing the raw face normals is the area-weighted sum.
@@ -36,10 +46,9 @@
36
46
  * slot for every live corner by construction
37
47
  * @param {Set<number>} pinned vertex ids to leave exactly as they are; empty for a mesh nothing has
38
48
  * locked yet
39
- * @param {number} [crease_angle] degrees; see {@link VGEO_CREASE_ANGLE}
40
49
  *
41
50
  * @author Alex Goldring
42
51
  * @copyright Company Named Limited (c) 2026
43
52
  */
44
- export function vgeo_compute_vertex_normals(mesh: VirtualGeometryMesh, pinned: Set<number>, crease_angle?: number): void;
53
+ export function vgeo_compute_vertex_normals(mesh: VirtualGeometryMesh, pinned: Set<number>): void;
45
54
  //# sourceMappingURL=vgeo_compute_vertex_normals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vgeo_compute_vertex_normals.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,+EAPW,IAAI,MAAM,CAAC,iBAEX,MAAM,QA4MhB"}
1
+ {"version":3,"file":"vgeo_compute_vertex_normals.d.ts","sourceRoot":"","sources":["../../../../../../../../src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js"],"names":[],"mappings":"AAyBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,+EANW,IAAI,MAAM,CAAC,QA+LrB"}