@woosh/meep-engine 3.60.4 → 3.60.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/CHANGELOG.md +2602 -0
  2. package/CONTRIBUTING.md +7 -0
  3. package/MIGRATION_2_TO_3.md +136 -0
  4. package/SECURITY.md +4 -0
  5. package/editor/EditorCameraNavigation.d.ts +2 -1
  6. package/editor/EditorCameraNavigation.d.ts.map +1 -1
  7. package/editor/EditorCameraNavigation.js +51 -57
  8. package/package.json +6 -2
  9. package/src/engine/EngineHarness.d.ts +3 -9
  10. package/src/engine/EngineHarness.d.ts.map +1 -1
  11. package/src/engine/EngineHarness.js +6 -8
  12. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
  13. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
  14. package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
  15. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
  16. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
  17. package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
  18. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
  19. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
  20. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
  21. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
  22. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  23. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
  24. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
  25. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  26. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
  27. package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
  28. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  29. package/src/engine/graphics/ecs/water/Water.js +8 -0
  30. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  31. package/src/engine/graphics3/WaterSystem.js +731 -734
  32. package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
  33. package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
  34. package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
  35. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
  36. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  37. package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
  38. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
  39. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
  40. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
  41. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
  42. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
  43. package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
  44. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
  45. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  46. package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
  47. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
  48. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
  49. package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
  50. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
  51. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
  52. package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
  53. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
  54. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
  55. package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
  56. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
  57. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
  58. package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
  59. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  60. package/src/shade/playground/water_refraction/README.md +5 -6
  61. package/src/shade/playground/water_refraction/index.html +1 -1
  62. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  63. package/src/shade/renderer/Renderer.js +7 -5
  64. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  65. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  66. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  67. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  68. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  69. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  70. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  71. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  72. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +2 -2
  73. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +2 -2
  74. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +2 -1
  75. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +18 -1
  77. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  78. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  79. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  80. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  81. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  82. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  83. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  84. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  85. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  86. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
  87. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +5 -5
  88. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  89. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +10 -10
  90. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
  91. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  92. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
  93. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  94. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  95. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  96. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  98. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  99. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  100. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  101. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  102. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  103. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  104. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  105. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  106. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  107. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  108. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  109. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  110. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  111. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  112. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  113. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  114. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  115. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  116. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  117. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  118. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  119. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  120. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  121. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  122. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  123. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  124. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  125. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  126. package/src/shade/renderer/material/GPUMaterialManager.d.ts +7 -0
  127. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  128. package/src/shade/renderer/material/GPUMaterialManager.js +11 -0
  129. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  130. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  131. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  132. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  133. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  134. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  135. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  136. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  137. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  138. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  139. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  140. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  141. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  142. package/src/shade/renderer/water/shader_water.js +22 -13
  143. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  144. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  145. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  146. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  147. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  148. package/editor/REWORK_PLAN_2026_08.md +0 -692
  149. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  150. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  151. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  152. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  153. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  154. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  155. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  156. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  157. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  158. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  159. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  160. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  161. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  162. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  163. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  164. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  165. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  166. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  167. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  168. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  169. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  170. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  171. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  172. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  173. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  174. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  175. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  176. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  177. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  178. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  179. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  180. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  181. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  182. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  183. package/src/REVIEW_2026_08_06/generation.md +0 -176
  184. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  185. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  186. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  187. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  188. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  189. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  190. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  191. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  192. package/src/REVIEW_2026_08_06/view.md +0 -667
  193. package/src/REVIEW_2026_08_06.md +0 -610
  194. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  195. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  196. package/src/core/bvh8/NOTES.md +0 -63
  197. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  198. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  199. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  200. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  201. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  202. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  203. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  204. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  205. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  206. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  207. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  208. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  209. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  210. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  211. package/src/engine/REVIEW_2026_07_03.md +0 -211
  212. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  213. package/src/engine/control/first-person/DESIGN.md +0 -658
  214. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  215. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  216. package/src/engine/control/first-person/TODO.md +0 -160
  217. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  218. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  219. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  220. package/src/engine/ecs/ik/PLAN.md +0 -179
  221. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  222. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  223. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  224. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  225. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  226. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  227. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  228. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  229. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  230. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  231. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  232. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  233. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  234. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  235. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  236. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  237. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  238. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  239. package/src/engine/physics/cloth/PLAN.md +0 -3056
  240. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  241. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  242. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  243. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  244. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  245. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  246. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  247. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  248. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  249. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  250. package/src/format/image/avif/DECISIONS.md +0 -545
  251. package/src/format/image/jpeg/NOTES.md +0 -3
  252. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  253. package/src/generation/COORDINATES.md +0 -54
  254. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  255. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  256. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  257. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  258. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  259. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  260. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  261. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  262. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  263. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  264. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  265. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  266. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  267. package/src/shade/RENDERER_CONTRACT.md +0 -697
  268. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  269. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  270. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  271. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  272. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  273. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  274. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  275. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  276. package/src/shade/renderer/NOTES.md +0 -78
  277. package/src/shade/renderer/PLAN.md +0 -8
  278. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  279. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  280. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  281. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  282. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  283. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  284. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  285. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  286. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  287. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  288. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  289. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  290. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  291. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  292. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  293. package/src/shade/renderer/camera/orbital/README.md +0 -81
  294. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  295. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  296. package/src/shade/renderer/camera/orbital/index.js +0 -9
  297. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  298. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  299. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  300. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  301. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  302. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  303. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  304. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  305. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  306. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
  307. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  308. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  309. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  310. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  311. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  312. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  313. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  314. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
  315. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  316. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
  317. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  318. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  319. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  320. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  321. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  322. package/src/shade/renderer/hiz/NOTES.md +0 -3
  323. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  324. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  325. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  326. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  327. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  328. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  329. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  330. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  331. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  332. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
  333. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  334. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  335. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  336. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  337. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  338. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
  339. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  340. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  341. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  342. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  343. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  344. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  345. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  346. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  347. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  348. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  349. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  350. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  351. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
  352. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
  353. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  354. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  355. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  356. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  357. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  358. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  359. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  360. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  361. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  362. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
  363. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  364. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  365. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  366. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  367. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  368. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  369. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  370. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  371. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  372. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  373. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
  374. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  375. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  376. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  377. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  378. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  379. package/src/shade/wgsl/NOTES.md +0 -4
@@ -1,661 +0,0 @@
1
- # Overlapping walkable surfaces in the navmesh build — investigation & proposal
2
-
3
- Status: **INVESTIGATION** · 2026-08-25 · one fix landed (D1), everything else deliberately not
4
- Tests: [`navmesh_overlapping_surfaces.spec.js`](navmesh_overlapping_surfaces.spec.js) — 20 tests: 5 controls run, 15 `test.skip`.
5
- The 15 assert what the build should do and all fail today. They are skipped, not deleted: the behaviour
6
- is still wrong and still worth stating, but a permanently-red suite hides the next real regression (CI
7
- gates on `npm run test:ci`, and the coverage deploy depends on it). Delete the `.skip` to work on one.
8
- Fix: [`navmesh_concave_obstacle.spec.js`](navmesh_concave_obstacle.spec.js) — 6 tests, green.
9
-
10
- > **What was executed.** Of everything below, exactly one thing was changed: **D1**, obstacles carved
11
- > as their convex hull (§5.1). It was the only finding that is a defect in shipping behaviour with a
12
- > self-contained fix and a single caller. A, B, C and D2 are documented and left alone — see §9 for
13
- > why, and §10 for the adversarial review of the change that did land.
14
-
15
- ---
16
-
17
- ## 0. TL;DR
18
-
19
- Three failure modes were reported against `NavigationMesh.build`. **All three reproduce.** They look
20
- like one problem and are actually three, each needing a different kind of answer:
21
-
22
- | | reported as | reproduces | class of problem | fixable by a tolerance? |
23
- | --- | --- | --- | --- | --- |
24
- | **A** | stacked coplanar floors, the middle one should win | yes | **semantics** — the build has no notion of walkable-vs-walkable occlusion | no |
25
- | **B** | coplanar lateral overlap goes non-traversable / disconnected | yes, both | **topology** — overlapping sheets are never unioned | no (verified) |
26
- | **C** | split ridge / shallow wedge fails to weld | yes, worse than predicted | **tolerance** — a dead band between two constants | yes |
27
-
28
- Two further defects surfaced while investigating:
29
-
30
- * **D1** — overhead obstacles are carved as their **convex hull**, so an L-shaped, ring-shaped or
31
- merely adjacent obstacle over-carves. A doorway frame removes the doorway. **Fixed** — §5.1, §10.
32
- * **D2** — island bridging inserts **90° vertical faces** that the climb-angle filter would have
33
- rejected, and `bt_mesh_compute_face_islands` then reports connectivity that `find_path` does not
34
- honour.
35
-
36
- The single sentence that covers all of it:
37
-
38
- > The build has a **repair** stage sized for float noise, and no **reconciliation** stage sized for
39
- > authoring error. Everything downstream assumes the source was already a manifold.
40
-
41
- Recommendation is in §9. Short version: **Stage 0 + Stage 1 are worth doing now; Stage 2 behind a
42
- flag; Stage 3 probably never** — and the adversarial review in §8 is the part worth reading before
43
- agreeing with any of it.
44
-
45
- ---
46
-
47
- ## 1. What the build does today
48
-
49
- `NavigationMesh.build` → [`navmesh_build_topology`](build/navmesh_build_topology.js), in order:
50
-
51
- | # | pass | purpose |
52
- | --- | --- | --- |
53
- | 1 | walkable-angle filter + degenerate drop | keep faces within `agent_max_climb_angle` of `up` |
54
- | 2 | `bt_mesh_kill_short_edges(1e-6)` | remove slivers thinner than the weld can represent |
55
- | 3 | `bt_merge_vertices_by_distance(1e-6)` | fuse coincident positions |
56
- | 4 | `bt_mesh_fuse_duplicate_edges` | give the fused vertices a shared *edge* |
57
- | 5 | `bt_mesh_resolve_t_junctions(1e-4)` + re-weld/fuse | a corner sitting mid-edge of a bigger neighbour |
58
- | 6 | per-island `bt_mesh_face_island_erode(agent_radius)` | inset the outer boundary |
59
- | 7 | `clip_soup_against_overhangs` | subtract r-dilated obstacle footprints |
60
- | 8 | `bt_mesh_bridge_islands` *(opt-in)* | span steps / gaps |
61
- | 9 | `bt_mesh_split_pinched_vertices`, `bt_mesh_fill_small_holes`, `bt_mesh_simplify_by_error` | cleanup |
62
-
63
- Passes 2–5 are the whole of the input reconciliation, and the tolerance ladder they run on is:
64
-
65
- | tolerance | value | where | what it is *for* |
66
- | --- | --- | --- | --- |
67
- | weld | `1e-6`, **raised to the float32 step at the mesh's coordinate magnitude** | [navmesh_build_topology.js:69](build/navmesh_build_topology.js:69) | "reunite the same corner reached by different arithmetic — not to simplify anything" (its own docstring) |
68
- | T-junction | `1e-4` | [:197](build/navmesh_build_topology.js:197), [:250](build/navmesh_build_topology.js:250) | a corner mid-edge of a larger neighbour |
69
- | T-junction **endpoint exclusion** | `tolerance / ‖edge‖` | [bt_mesh_resolve_t_junctions.js:197](../../../core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js:197) | don't "split" an edge at a point that *is* its endpoint |
70
- | carve height band | `1e-3` | [clip_soup_against_overhangs.js:44](build/clip_soup_against_overhangs.js:44) | |
71
- | obstacle grouping quantum | `1e-3` | [clip_soup_against_overhangs.js:57](build/clip_soup_against_overhangs.js:57) | |
72
- | simplify | `0.05 × agent_radius` | [navmesh_build_topology.js:52](build/navmesh_build_topology.js:52) | |
73
-
74
- Note what is *absent* from that ladder: any number that means **"how far apart may two surfaces be and
75
- still be the same surface?"** The largest reconciliation tolerance in the build is `1e-4` — a tenth of a
76
- millimetre, several orders of magnitude below what any authoring tool guarantees. That absence is Case C.
77
-
78
- ---
79
-
80
- ## 2. Case A — stacked coplanar floors
81
-
82
- ### Setup
83
-
84
- Three identical 10×10 floors, `agent_height = 1.6`, gaps of 0.10. Soup order deliberately puts the
85
- winner second, so an implementation that kept "the first" or "the last" would pick a loser:
86
-
87
- ```
88
- y = 0.20 <-- soup triangle #2 the only standable surface
89
- y = 0.10 <-- soup triangle #1 0.10 of headroom
90
- y = 0.00 <-- soup triangle #3 0.20 of headroom
91
- ```
92
-
93
- ### Observed
94
-
95
- | metric | observed | correct |
96
- | --- | --- | --- |
97
- | islands | 3 | 1 |
98
- | surface area | 300 | 100 |
99
- | heights present | `[0, 0.1, 0.2]` | `[0.2]` |
100
- | cover at one XZ point | 3 | 1 |
101
- | faces with < `agent_height` headroom | 4 | 0 |
102
- | 10 layers → | 10 islands, area 1000 | 1 island, area 100 |
103
-
104
- ### Root cause
105
-
106
- [`clip_soup_against_overhangs.js:286`](build/clip_soup_against_overhangs.js:286):
107
-
108
- ```js
109
- if (fn[0] * upx + fn[1] * upy + fn[2] * upz >= -HEIGHT_EPS) continue; // not downward-facing
110
- ```
111
-
112
- Obstacles are **only** the downward-facing source faces. An upward-facing floor never occludes
113
- anything below it, so `agent_height` is never consulted for walkable-vs-walkable stacking. Erosion
114
- (pass 6) then runs *per island*, so with `agent_radius > 0` all three layers merely shrink.
115
-
116
- ### What is *not* wrong
117
-
118
- The worry that an arbitrary tie-break might land on the first or last triangle **does not
119
- materialise**. All six soup permutations produce byte-identical island counts, areas and height sets.
120
- The failure is not that the wrong layer is chosen — it is that **no layer is dropped at all**. This is
121
- locked in as a green control so it stays true after any fix.
122
-
123
- ### The control that proves the diagnosis
124
-
125
- Add a downward-facing face to the top slab (i.e. a closed slab rather than a single-sided plane) and
126
- the build gets it exactly right: 1 island, area 100, only `y = 0.20` survives. **The topmost does win,
127
- once the build can see the stack.** So the machinery is correct; only its input predicate is too narrow.
128
-
129
- ### Prior art
130
-
131
- Recast solves this in `rcFilterWalkableLowHeightSpans` — a span with less than `walkableHeight` of
132
- clearance to the span above it is removed, regardless of which surface produced either span. The
133
- proposal in §7.3 is that filter, done on polygons instead of voxels.
134
-
135
- ---
136
-
137
- ## 3. Case B — coplanar lateral overlap
138
-
139
- ### Setup
140
-
141
- Two floors at `y = 0` whose footprints overlap. Two variants, because the two behave differently:
142
-
143
- ```
144
- B1 "edge overlap" B2 "corner overlap"
145
- +--------+ +--------+
146
- | A | | A |
147
- | +--+-----+ | +--+-----+
148
- | |##| B | | |##| B |
149
- +-----+--+-----+ +-----+--+ |
150
- | B |
151
- +--------+
152
- ```
153
-
154
- In **B1** both of B's left corners land *exactly on* A's boundary edges. In **B2** the outlines cross
155
- edge-to-edge with no vertex at the crossing.
156
-
157
- ### Observed
158
-
159
- | config | islands | A→B path | note |
160
- | --- | --- | --- | --- |
161
- | B1, `r = 0` | 1 | **15.84** vs 12 straight | joined only at the mesh corner `(10, 0, 10)`; a 32% detour |
162
- | B1, `r = 0.4` | 2 | **none** | |
163
- | B2, `r = 0` | 2 | **none** | zero erosion involved |
164
- | control: abutting, no overlap | 1 | 16.0 = straight | |
165
-
166
- Surface area is 200 against a true union of 160; the overlap carries two sheets everywhere.
167
-
168
- ### Root cause
169
-
170
- Nothing in the pipeline unions coplanar sheets. Each keeps its own boundary running through the
171
- other's interior, and **whether the two connect at all is an accident of geometry**: B1 happens to
172
- present a stray vertex on a boundary edge, which `bt_mesh_resolve_t_junctions` can split on; B2
173
- presents an edge-edge crossing, which it cannot. B2 is the far more common modelling mistake.
174
-
175
- ### Erosion turns the seam into a wall — verified directly
176
-
177
- Coverage sampled across `x` at `z = 2.3` for B1:
178
-
179
- ```
180
- r=0 0.5:1 … 5.5:1 6:2 6.5:2 … 9.5:2 10:2 10.5:1 … 15.5:1
181
- r=0.4 0.5:1 … 5.5:1 6:0 6.5:2 … 9.5:2 10:0 10.5:1 … 15.5:1
182
- ^^^ ^^^^
183
- ```
184
-
185
- Two zero-coverage walls, at exactly `x = 6` and `x = 10` — the two interior boundary lines. Erosion
186
- insets from **every** boundary edge, including the ones buried inside the other sheet, so it carves a
187
- wall along each and severs the single corner join that held B1 together.
188
-
189
- ### No tolerance fixes this
190
-
191
- Pre-welding the source at 1e-2 and 5e-2 leaves B2 at 2 islands with no path. The defect is genuine
192
- interior overlap, not near-coincident vertices; welding has nothing to bite on.
193
-
194
- ---
195
-
196
- ## 4. Case C — split ridge / shallow wedge
197
-
198
- ### Setup
199
-
200
- Two slabs that used to share a ridge at `x = 0`, separated into two pairs of vertices, each pushed
201
- `δ` **into** the other, and tilted by 0.02 over a 10-unit span (~0.1°) into a convex roof or concave
202
- valley. The ridge lines end up `2δ` apart.
203
-
204
- ### Observed (at the world origin, `r = 0`)
205
-
206
- | δ | islands | path across the ridge |
207
- | --- | --- | --- |
208
- | 0 (truly shared) | 1 | **15.997**, turning at `x ≈ 0` — correct |
209
- | 1e-7, 1e-6 | 1 | correct |
210
- | **1e-5** | **2** | **none** |
211
- | 1e-4, 1e-3, 1e-2 | 1 | **18.87** vs 16 — detour to `z = 10` |
212
- | 1e-3, `r = 0.4` | **2** | **none** |
213
-
214
- Concave behaves identically to convex; so does an asymmetric variant where the two halves are tilted
215
- by different amounts.
216
-
217
- So the prediction ("welding fails, the ridge becomes impassable") is right, and there are actually
218
- **two** distinct failures:
219
-
220
- 1. **The dead band.** A separation past the weld but inside the T-junction tolerance is joined by
221
- neither pass. [bt_mesh_resolve_t_junctions.js:197](../../../core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js:197) skips any candidate near the edge's own endpoints:
222
-
223
- ```js
224
- const t_margin = tolerance / Math.sqrt(ab_sq);
225
- …
226
- if (t <= t_margin || t >= 1 - t_margin) continue; // "that IS the endpoint" — so, skip
227
- ```
228
-
229
- Correct reasoning for *splitting* — you cannot split an edge at its own endpoint. But the right
230
- action for a distinct vertex sitting a hair off an endpoint is to **weld** it there, and nothing does.
231
-
232
- 2. **Above the dead band, the join is cosmetic.** At δ ≥ 1e-4 the resolver joins the slabs, but only
233
- over a `2δ`-long stub at each *end* of the ridge. The ridge itself is still a crack, so the path
234
- runs out to the mesh edge and back — and any positive agent radius erodes the stub away entirely.
235
-
236
- ### The dead band moves with world position — the sharpest finding
237
-
238
- `bt_merge_vertices_by_distance` floors its tolerance at the float32 step for the mesh's coordinate
239
- magnitude (see [`bt_mesh_vertex_merge_distance.js`](../../../core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js)). The
240
- T-junction tolerance is a flat `1e-4`. So the band where neither applies is
241
- `float32_step(‖coord‖) < 2δ < 1e-4`, and it **shrinks as geometry moves away from the origin**:
242
-
243
- | origin x | float32 step | δ=1e-6 | δ=1e-5 | δ=1e-4 | δ=1e-3 |
244
- | --- | --- | --- | --- | --- | --- |
245
- | 0 | 2.1e-6 | direct | **NO PATH** | detour | detour |
246
- | 100 | 2.1e-5 | direct | direct | detour | detour |
247
- | 2 000 | 4.1e-4 | direct | direct | direct | detour |
248
- | 10 000 | 2.1e-3 | direct | direct | direct | **direct** |
249
-
250
- **The same geometry builds more correctly the further it sits from the origin.** At 10 000 units the
251
- weld floor exceeds the T-junction tolerance, the band is empty, and every δ tested joins cleanly. This
252
- is float32 quantisation doing, by accident, the reconciliation job that no pass does on purpose.
253
-
254
- ### What does and does not fix it
255
-
256
- | attempt | δ=1e-5 | δ=1e-3 |
257
- | --- | --- | --- |
258
- | weld the *source* at 1e-4 | **fixed**, direct crossing | no |
259
- | weld the *source* at 1e-2 | fixed | **fixed**, direct crossing |
260
- | `bt_mesh_bridge_islands` (step 0.01…0.5) | **fixed**, direct crossing | **no effect** |
261
- | bridging with `r = 0.4` | no | no |
262
-
263
- Two things worth extracting:
264
-
265
- * **Welding above `2δ` fixes Case C completely** — direct crossing, correct length. It is a pure
266
- tolerance problem.
267
- * **Bridging is island-gated.** At δ=1e-3 the mesh is already nominally one island (via the end
268
- stubs), so bridging sees nothing to bridge and does nothing. A *partial* connection defeats it.
269
- * **Erosion manufactures a `2r` gap.** Erosion (pass 6) runs *before* bridging (pass 8) and insets
270
- each island by `r`, so two islands that were touching end up `2r` apart. With `r = 0.4`, bridging
271
- only reconnects them once `max_step_distance ≥ 0.9`; at 0.5 it cannot, no matter the geometry.
272
-
273
- ---
274
-
275
- ## 5. Two further defects found while investigating
276
-
277
- ### D1 — overhead obstacles are carved as their convex hull
278
-
279
- [`clip_soup_against_overhangs`](build/clip_soup_against_overhangs.js) groups overhead triangles that
280
- share a (1 mm-quantised) vertex into one obstacle and takes **the convex hull of the group**. Anything
281
- concave, holed, or merely adjacent over-carves:
282
-
283
- | obstacle | probe | covered? | should be |
284
- | --- | --- | --- | --- |
285
- | L-shaped ceiling | `(12, 12)` — strictly in the notch | **no** | yes |
286
- | L-shaped ceiling | `(14, 11)` | **no** | yes |
287
- | ring / frame ceiling | `(10, 10)` — the middle of the hole | **no** | yes |
288
- | two 2×2 ceilings touching at a corner | `(9.5, 11)` — between them | **no** | yes |
289
- | control: single convex box | `(10, 10)` under it / `(5, 5)` clear | no / yes | correct |
290
-
291
- A doorway frame is a ring. A balcony is a ring. A row of touching pillars is one group. All of them
292
- remove walkable area that has nothing above it.
293
-
294
- **Fixed** in `fix(navmesh): an obstacle is carved as itself, not as its convex hull`. Minkowski dilation distributes over
295
- union — `(A ∪ B) ⊕ D = (A ⊕ D) ∪ (B ⊕ D)` — so subtracting each overhead triangle's *own* dilated
296
- footprint, sequentially from the running piece set, yields exactly the dilated true footprint of an
297
- arbitrarily concave obstacle. No grouping, no hull, no union-find, no 1 mm quantum. It costs more clip
298
- operations (one per overhead triangle rather than one per obstacle) and is the main perf question.
299
-
300
- Sequential subtraction matters: it keeps the result a *partition* of the triangle, so adjacent dilated
301
- footprints never produce edge-edge crossings — i.e. it does not reintroduce Case B inside the carve.
302
-
303
- The landed fix keeps the hull as a **fast path**, taken when the group's triangles tile it (compare the
304
- hull's area against the triangles' own). Every convex obstacle — tessellated or not — therefore takes
305
- the route it always did, face for face; only concave groups pay. Measured, 60×60 floor grid:
306
-
307
- | scene | before | after |
308
- | --- | --- | --- |
309
- | convex obstacles | 833 ms / 185 faces | 786 ms / **185** faces |
310
- | 24 L-shaped obstacles | 1268 ms / 523 faces | 1743 ms / 1216 faces |
311
- | tessellated ring overhang | 761 ms / 21 faces | **2343 ms** / 70 faces |
312
-
313
- Concave obstacles cost up to ~3× the build and leave 2–3× the faces, because they are no longer
314
- deleting the walkable area around themselves — the extra faces are recovered navmesh.
315
-
316
- ### D2 — bridging inserts faces the climb filter would have rejected
317
-
318
- On the bridged 3-layer stack: 22 faces, of which **16 are steeper than `agent_max_climb_angle`, worst
319
- case 90°**. Nothing re-filters after bridging. `bt_mesh_compute_face_islands` reports **1 island**
320
- while `find_path` returns **no path** from the bottom layer to the top — island count and path
321
- reachability disagree, because the walk crosses vertical faces the query will not.
322
-
323
- This matters beyond the bug itself: **island count is not a sound proxy for reachability** once
324
- bridging is enabled, which affects how the tests in §7 should assert.
325
-
326
- ---
327
-
328
- ## 6. Diagnosis
329
-
330
- | | what it really is | what would fix it | what would *not* |
331
- | --- | --- | --- | --- |
332
- | **A** | the occluder predicate is too narrow | add walkable surfaces to the occluder set | any tolerance |
333
- | **B** | no coplanar union exists | overlap resolution (clip or arrange) | any tolerance (verified) |
334
- | **C** | two tolerances with a hole between them, and neither is navigation-scale | close the hole; give the build one explicit reconciliation tolerance | bridging (island-gated); erosion order makes it worse |
335
- | **D1** | convex hull over a possibly-concave footprint | per-triangle dilate-and-subtract | — |
336
- | **D2** | bridging output is never re-filtered | re-apply the climb filter after bridging | — |
337
-
338
- A, B and C are not variants of one bug. They are the three ways an "already a manifold" precondition
339
- can be violated, and each is violated at a different layer of the stack.
340
-
341
- ---
342
-
343
- ## 7. Proposal
344
-
345
- Staged so each stage stands alone and can be stopped after.
346
-
347
- ### Stage 0 — make it visible (no behaviour change)
348
-
349
- Add a build-time **reconciliation report**: counts of (i) walkable faces with less than `agent_height`
350
- of walkable surface above them, (ii) pairs of walkable faces overlapping in the `up`-projection,
351
- (iii) boundary-vertex pairs separated by more than the weld and less than a navigation-scale
352
- tolerance, (iv) resulting island count. Surface it through the existing
353
- [`bt_mesh_validate`](../../../core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js) channel or
354
- a returned struct.
355
-
356
- *Why first:* every one of these defects is currently silent. A level with a duplicated floor builds
357
- "successfully" and ships. This also gives the later stages their regression signal.
358
-
359
- **Effort:** ~1 day. **Risk:** none. **Un-skips:** nothing, by design.
360
-
361
- ### Stage 1 — close the dead band, and name the tolerance
362
-
363
- Two parts.
364
-
365
- **1a. Weld, don't skip.** In `bt_mesh_resolve_t_junctions`, when a candidate falls within `t_margin`
366
- of an endpoint but is a *distinct vertex at a distinct position*, weld it onto that endpoint instead
367
- of skipping it. Same manifold guards as the split path.
368
-
369
- **1b. Give the build an explicit reconciliation tolerance.** Replace the implicit ladder with one
370
- named parameter — how far apart two surfaces may be and still be the same surface — derived the way
371
- the codebase already knows how ([`bt_mesh_relative_merge_distance`](../../../core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js)),
372
- floored by float32 resolution, defaulted to something navigation-scale, and used by **a
373
- boundary-only stitch pass** (boundary vertices to boundary vertices, height difference also within
374
- tolerance) followed by fuse + T-junction resolve at the same number.
375
-
376
- Boundary-only is the point: interior tessellation is untouched, so nothing thin collapses.
377
-
378
- **Effort:** 1a ~half a day; 1b ~2–3 days with specs. **Risk:** see §8.1 — non-trivial.
379
- **Un-skips:** all 6 Case C tests, at any δ below the tolerance.
380
-
381
- ### Stage 2 — one occlusion pass (fixes A *and* D1)
382
-
383
- Replace `clip_soup_against_overhangs`'s grouping+hull with per-triangle dilate-and-subtract (D1), and
384
- widen the occluder set to include **walkable surfaces above other walkable surfaces** (A):
385
-
386
- 1. Project all source faces to `(u, v)`; BVH over their extents (the pass already builds one).
387
- 2. For each walkable triangle `T`, gather occluders whose `(u, v)` extent overlaps.
388
- 3. For each occluder `S`, compute the height difference over the *overlap polygon* — both are planar,
389
- so the difference is linear there; evaluate at the overlap's vertices for an exact min/max, rather
390
- than the current mean-height approximation.
391
- 4. If `S` sits above `T` by less than `agent_height`, subtract `S`'s footprint (dilated by
392
- `agent_radius`) from `T` using the existing `subtract_footprint`, sequentially over the running
393
- piece set.
394
-
395
- Occlusion is tested against the **original** face set, not the surviving one, so the result is
396
- order-independent by construction — which is what the green control in §2 asserts.
397
-
398
- **Effort:** ~1 week. **Risk:** see §8.2. **Un-skips:** all 7 Case A tests.
399
-
400
- ### Stage 3 — coplanar overlap resolution (fixes B)
401
-
402
- Same pass, additional branch: when the height difference over the overlap is *below* the
403
- reconciliation tolerance, the two are the same layer, and one must yield — subtract one from the other
404
- so the result is a partition, then let weld/fuse/T-junction join the conforming seams.
405
-
406
- The winner must be chosen by a **deterministic geometric key** (e.g. higher first, tie-broken on
407
- `(u, v)` centroid order), never by soup order.
408
-
409
- **Effort:** ~1 week on top of Stage 2, plus hardening. **Risk:** highest — see §8.3.
410
- **Un-skips:** all 5 Case B tests.
411
-
412
- ### Summary
413
-
414
- | stage | fixes | effort | risk | un-skips |
415
- | --- | --- | --- | --- | --- |
416
- | 0 report | — | ~1d | none | 0 |
417
- | 1 dead band + tolerance | C | ~3d | medium | 6 |
418
- | 2 occlusion pass | A, D1 | ~1w | medium-high | 7 |
419
- | 3 coplanar resolution | B | ~1w+ | high | 5 |
420
- | (D2 re-filter after bridging) | D2 | ~half a day | low | 0 (needs its own test) |
421
-
422
- ---
423
-
424
- ## 8. Adversarial review of the proposal
425
-
426
- *Written against my own proposal. Where I could not resolve an objection, it stays unresolved.*
427
-
428
- ### 8.1 Against Stage 1
429
-
430
- **The endpoint exclusion is not a bug, and 1a may break its actual invariant.** The docstring is
431
- explicit that vertices near an endpoint "are already as connected as the split could make them" and
432
- that the alternative is a non-manifold edge. Turning a skip into a *mutation* changes a no-op into
433
- geometry movement. **Blast radius:** `bt_mesh_resolve_t_junctions` has a second production caller,
434
- [`atlas_mesh_weld_colocals.js:35`](../../../core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js:35),
435
- which passes `max_distance * 100` — a *much* larger tolerance, so 1a would weld far more aggressively
436
- there than in the navmesh. That caller was not investigated. **1a must be opt-in per call, or it is a
437
- UV-atlas regression waiting to happen.**
438
-
439
- **1a has a concrete correctness hazard.** The function's own comment says the vertex BVH never needs
440
- rebuilding "because splitting only ever cuts an edge at a vertex that is already in it" — i.e. splits
441
- only *add*. Welding *removes* vertices, leaving stale leaves in a tree that is built once and reused
442
- across the `changed` loop. That is exactly the class of bug the existing "skip unlinked vertices"
443
- guard was added for. This needs either a rebuild or a liveness check on every candidate.
444
-
445
- **1a fixes a hole and leaves a lumpy surface.** After it, the effective weld tolerance is `1e-4` *near
446
- boundary-edge endpoints* and `1e-6` everywhere else. Two vertices 1e-5 apart that are not near any
447
- edge endpoint still will not weld. That is a strictly weirder tolerance model than the one we have,
448
- and it is not obviously better to have an irregular tolerance than a known gap.
449
-
450
- **1b's tolerance is unanswerable in the abstract, and the doc does not answer it.** Too small and
451
- δ=1e-3 stays broken; too large and genuine narrow features get welded shut. I asserted "a 2 cm slot
452
- is navigationally irrelevant for a 0.4 m agent" — but the navmesh is also used for area queries and
453
- nearest-surface snapping, not only for path width, so that argument is weaker than it sounds. **No
454
- number is proposed here on purpose; picking one without a corpus of real levels would be guessing.**
455
-
456
- **1b amplifies an existing order-dependence.** `MANIFOLD_COMPARISON` records that meep's vertex weld
457
- is order-dependent where manifold's is deterministic. Raising the tolerance makes *which* vertex wins
458
- a merge matter more often and over larger distances. Given that order-independence is exactly the
459
- property the Case A control locks in, making a different part of the build *more* order-dependent is
460
- an uncomfortable trade.
461
-
462
- **Boundary-only stitching does not generalise.** It works on the wedge because both ridges are single
463
- full-depth boundary edges with matching subdivision. If the two sides are tessellated differently, the
464
- stitch pairs vertices badly and the T-junction pass has to clean up behind it at the same tolerance —
465
- untested, and the interaction of "weld at T, then split at T" is not obviously convergent.
466
-
467
- **And the honest one:** float32 quantisation is currently doing this job by accident (§4), and it does
468
- it *better as coordinates grow*. Stage 1 replaces an accident with a policy — but if any level relies
469
- on the accidental behaviour at large coordinates, Stage 1 changes its navmesh.
470
-
471
- ### 8.2 Against Stage 2
472
-
473
- **The biggest hazard is semantic, not geometric.** The build cannot distinguish collision geometry
474
- from render geometry. Today only downward faces occlude, which is conservative. After Stage 2 **any
475
- upward-facing surface culls what is under it** — including one-sided water planes, decals, projected
476
- shadow-catchers, and skirts. A level that builds fine today could lose large amounts of walkable area,
477
- and the failure is silent. This alone argues Stage 2 should be **opt-in** for at least one release,
478
- which weakens its value considerably: a fix nobody turns on fixes nothing.
479
-
480
- **"Cull the lower" conflicts with `agent_max_step_height`.** If two floors are 5 cm apart and the
481
- agent can step 30 cm, is the lower one an unreachable ghost (cull) or a step the agent may stand on
482
- (keep and connect)? Recast answers this with two separate constants and a span-merge before the
483
- height filter. The proposal has only the height filter, so a low step under a low ceiling gets culled
484
- where Recast would merge it. **This is a genuine gap in the design as written.**
485
-
486
- **The perf story is asserted, not measured.** Today the carve is `O(m log k)` with `k` = obstacle
487
- *groups* — small. Stage 2 makes it `O(m log m)` with `m` = walkable triangles, and every triangle is
488
- both subject and clipper. Measured baseline: a 60×60 grid (7 216 source triangles, 8 obstacles) builds
489
- in **~984 ms** today, roughly linear in source triangles. Stage 2 plus D1's per-triangle footprints
490
- adds clip work proportional to actual overlap; on clean input that is near zero, but on the pathological
491
- input this whole document is about — a fully duplicated floor — it is quadratic. **No bound is
492
- proposed. That is a real hole.**
493
-
494
- **D1's fix could be a regression against a test that currently passes.**
495
- [`navmesh_obstacle_outline.spec.js`](navmesh_obstacle_outline.spec.js) guards that the hole boundary is
496
- a *faithful* agent-radius offset. The `(A∪B)⊕D = (A⊕D)∪(B⊕D)` argument says per-triangle dilation is
497
- exactly equivalent — but that identity is about the *sets*, and the implementation approximates each
498
- corner arc with `CORNER_SEGMENT_ANGLE` segments. Many small dilated triangles produce many more arc
499
- approximations than one dilated hull, and the error does not obviously cancel. **Needs measuring
500
- against that spec before believing the identity argument.**
501
-
502
- **Reusing `subtract_footprint` triangle-against-triangle is unverified.** It is currently only ever
503
- called with dilated, many-sided, rounded footprints. `push_outside_piece`'s seam-conformance logic is
504
- written in terms of "the footprint edge's own endpoints", which for a bare triangle are its corners —
505
- plausible, but the function is not exported and was never exercised that way. Any estimate that
506
- assumes free reuse is optimistic.
507
-
508
- ### 8.3 Against Stage 3
509
-
510
- **The project already decided against this.** `MANIFOLD_COMPARISON_2026_08_01.md` ranks 2D polygon
511
- boolean as **P4 #11** — "Big… only if the domain demands it" — and notes manifold spends ~70 KB and
512
- years of corpus hardening on it. Stage 3 is a hand-rolled subset of exactly that, written to fix an
513
- input error. **Proposing it here is in tension with a decision the repo already made deliberately, and
514
- this document does not present new evidence strong enough to overturn it.**
515
-
516
- **Clipping is not union, and the difference shows up at the seam.** Subtracting T from S leaves a seam
517
- that must conform for weld/fuse to join it. That is precisely the T-junction problem, at a tolerance
518
- that Stage 1 has just been fiddled with. Two half-fixes interacting at the same seam is how the
519
- current dead band came to exist in the first place.
520
-
521
- **Piece blow-up is worse here than in Stage 2.** Every overlapping triangle pair can split a piece
522
- into up to `fp_n` pieces, and coplanar overlap means *every* triangle of one sheet overlaps several of
523
- the other. A duplicated 10 k-triangle floor is not a contrived input — it is the exact bug being fixed.
524
-
525
- **The winner rule is arbitrary and will leak.** "Higher first, tie-broken on centroid order" is
526
- deterministic but geometrically meaningless for exactly-coplanar input, and any tie-break on
527
- floating-point centroids is one ULP away from flipping. Determinism under *rebuild* is easy;
528
- determinism under *tiny geometry edits* is what level designers actually notice.
529
-
530
- ### 8.4 Against the investigation itself
531
-
532
- **The tests encode choices, not just facts.** "The topmost layer wins" and "area equals the union" are
533
- *my* reading of correct. A project could legitimately decide that stacked single-sided planes are
534
- caller error and should assert instead. Fifteen skipped tests look like fifteen bugs; some fraction of
535
- them are really unstated-requirement disagreements.
536
-
537
- **Two metrics are shakier than they look.** `coverCount` is edge-sensitive — three of my early probe
538
- points landed exactly on triangle diagonals and inflated the count; the spec now samples off-edge, but
539
- the metric stays fragile. And per §5/D2, **island count is not reachability** once bridging is on, so
540
- the Case A and Case C island assertions are proxies, not proofs; the path assertions are the real ones.
541
-
542
- **Path length as a correctness proxy has a blind spot.** `toBeCloseTo(16, 1)` would pass a path that
543
- crosses the ridge in the wrong place but happens to be the right length. The control test also asserts
544
- the turn is *on* the ridge; the other Case C tests do not.
545
-
546
- **What would falsify most of this document:** if real source geometry for this project is always closed
547
- solids from a modelling package, Case A is unreachable in practice (the §2 control shows closed slabs
548
- work), D1 becomes the most valuable finding here, and Stages 2–3 are largely wasted effort. **I do not
549
- know which it is, and that question dominates the value of everything after Stage 1.**
550
-
551
- ---
552
-
553
- ## 9. Recommendation
554
-
555
- *Status against each item is marked. Only item 5 was executed.*
556
-
557
- 1. ☐ **Do Stage 0.** It is cheap, risk-free, and it answers the question that decides everything else:
558
- *how often does real geometry actually hit these?* Run it over the project's actual levels.
559
- 2. ☐ **Do the D2 re-filter** (~half a day) — bridging emitting 90° faces is unambiguous.
560
- 3. ☐ **Do Stage 1a only if it can be made opt-in per call site**, given the atlas caller, and only with
561
- the BVH staleness hazard addressed. Otherwise hold.
562
- 4. ☐ **Hold Stage 1b** until Stage 0 produces a distribution of real separations. Picking the tolerance
563
- without that is guessing, and this document says so rather than guessing.
564
- 5. ☑ **DONE — Split D1 out of Stage 2 and do it on its own.** It is a real, currently-shipping correctness bug
565
- with a small, self-contained fix and one caller. It does not need the occlusion widening.
566
- 6. ☐ **Hold Stage 2's occlusion widening** behind the answer to §8.4 — closed solids or not.
567
- 7. ☐ **Do not do Stage 3** on this evidence. If coplanar overlap turns out to be common, the honest
568
- answer is the one `MANIFOLD_COMPARISON` already gives: a real 2D boolean, or reject the input.
569
-
570
- The 15 tests are `test.skip`, not red. They document behaviour that is currently wrong without
571
- standing between CI and the next real regression, and three of the four decisions above are
572
- "measure first".
573
-
574
- ---
575
-
576
- ## Appendix A — reproducing
577
-
578
- ```bash
579
- npx vitest run src/engine/navigation/mesh/navmesh_overlapping_surfaces.spec.js --reporter=verbose
580
- ```
581
-
582
- 5 controls pass, 15 are skipped. Delete a `.skip` to see the case it describes fail; the value observed
583
- today is recorded next to each expectation. `src/engine/navigation` is otherwise green.
584
-
585
- ## Appendix B — measurements
586
-
587
- Build scale (flat grid + 8 box obstacles, `r = 0.4`, `h = 1.6`):
588
-
589
- | grid | source tris | navmesh faces | build |
590
- | --- | --- | --- | --- |
591
- | 10×10 | 216 | 24 | 100 ms |
592
- | 20×20 | 816 | 130 | 216 ms |
593
- | 40×40 | 3 216 | 167 | 494 ms |
594
- | 60×60 | 7 216 | 185 | 984 ms |
595
-
596
- Erosion / bridging interaction (Case C, δ=1e-5, `r = 0.4`):
597
-
598
- | `max_step_distance` | islands | path |
599
- | --- | --- | --- |
600
- | 0.5 | 2 | none |
601
- | 0.9 | 1 | found (detour) |
602
- | 1.5 | 1 | found (detour) |
603
-
604
- — i.e. reconnection requires `max_step_distance ≳ 2r`, because erosion created that gap.
605
-
606
- ---
607
-
608
- ## 10. Adversarial review of the change that landed
609
-
610
- *The D1 fix only. Written against it, after it was written.*
611
-
612
- ### What was attacked, and what held
613
-
614
- | probe | result |
615
- | --- | --- |
616
- | convex obstacle, 2 triangles vs. tessellated into 32 | identical face count and area — the fast path is genuinely untouched |
617
- | source triangle order shuffled 4 ways | identical faces / area / islands every time — deterministic |
618
- | non-default `up` (+X), scene properly rotated | identical faces / area / islands to the +Y build |
619
- | obstacle tilted so its skirt acquires a downward normal | same carve as the flat lid, no fragmentation |
620
- | duplicated obstacle arm | carved correctly — duplication pushes the group to the per-triangle route |
621
- | whole repo suite | 13 925 pass, 0 fail (the 15 Case A/B/C tests are skipped) |
622
-
623
- Two of my own probes were wrong before the code was: a "rotation" that was actually a reflection (it
624
- flips every winding, turning floors into ceilings), and a filler triangle wound upward, which made it
625
- walkable surface rather than an obstacle. Both looked like dramatic failures of the fix. Neither was.
626
-
627
- ### What did not hold
628
-
629
- **The tiling test can be defeated, and I built the input that does it.** L-shaped arms of 16 + 12 = 28
630
- under a hull of 46; add 18 of *duplicate* overhead area lying inside that hull and `member_area` reaches
631
- 46 without the union growing at all. The guard accepts the hull and over-carves: area 354 where 372 is
632
- correct. Confirmed by construction, not by argument.
633
-
634
- It is left unfixed and documented in the code. No test on areas alone can catch it — `member == hull`
635
- exactly is what the fooling input produces, so distinguishing it needs a real union. The cost of being
636
- fooled is the hull carve this always did, i.e. no worse than before the change, and every non-exact
637
- duplication falls to the per-triangle route, which is correct. Fixing it properly means the 2D boolean
638
- that §8.3 argues against.
639
-
640
- ### What remains unverified
641
-
642
- * **The perf figure is one machine, one shape of scene.** ~3× on a large tessellated concave overhang
643
- is measured; how often real levels contain one is not known. This is the same question Stage 0 exists
644
- to answer.
645
- * **Fragmentation is not bounded.** 24 L-shaped obstacles go from 523 to 1216 output faces. Most of
646
- that is recovered walkable area, but some is extra seams from subtracting many small footprints
647
- instead of one large one, and `bt_mesh_simplify_by_error` runs at only `0.05 × agent_radius`. No
648
- measurement separates the two.
649
- * **A sub-`AREA_EPS` behaviour change.** `push_footprint` drops footprints under 1e-9 of area, where
650
- the hull path previously kept any hull with ≥ 3 vertices. That is an obstacle tens of micrometres
651
- across, and the reason for dropping it is that its winding is not reliable enough to dilate. Real,
652
- documented, not believed reachable.
653
- * **The `up`-agnosticism rests on one probe.** No spec in the tree exercises a non-default `up` through
654
- the obstacle carve; `NavigationMesh.spec.js:695` is the only place `up` is overridden at all.
655
-
656
- ### The honest summary
657
-
658
- The change fixes a real defect, is deterministic, leaves the convex path bit-identical, and costs build
659
- time on exactly the scenes that were previously wrong. Its guard is a heuristic with a constructible
660
- counter-example whose consequence is the old behaviour. That is a reasonable trade for a conservative
661
- change; it would not be a reasonable trade for a load-bearing one.