@woosh/meep-engine 2.168.0 → 2.169.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 (295) hide show
  1. package/README.md +2 -2
  2. package/build/bundle-worker-image-decoder.js +1 -1
  3. package/package.json +1 -1
  4. package/src/REVIEW_2026_08_06/adv-core.md +511 -0
  5. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -0
  6. package/src/REVIEW_2026_08_06/adv-view.md +446 -0
  7. package/src/REVIEW_2026_08_06/api-core.md +296 -0
  8. package/src/REVIEW_2026_08_06/api-engine.md +195 -0
  9. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -0
  10. package/src/REVIEW_2026_08_06/core-binary.md +348 -0
  11. package/src/REVIEW_2026_08_06/core-collection.md +376 -0
  12. package/src/REVIEW_2026_08_06/core-math.md +221 -0
  13. package/src/REVIEW_2026_08_06/core-process.md +266 -0
  14. package/src/REVIEW_2026_08_06/core-science.md +223 -0
  15. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -0
  16. package/src/REVIEW_2026_08_06/engine-animation.md +167 -0
  17. package/src/REVIEW_2026_08_06/engine-asset.md +166 -0
  18. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -0
  19. package/src/REVIEW_2026_08_06/engine-input.md +197 -0
  20. package/src/REVIEW_2026_08_06/engine-misc.md +248 -0
  21. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -0
  22. package/src/REVIEW_2026_08_06/generation.md +176 -0
  23. package/src/REVIEW_2026_08_06/geom-2d.md +434 -0
  24. package/src/REVIEW_2026_08_06/geom-3d.md +117 -0
  25. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -0
  26. package/src/REVIEW_2026_08_06/gfx-render.md +740 -0
  27. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -0
  28. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -0
  29. package/src/REVIEW_2026_08_06/prior-highs.md +138 -0
  30. package/src/REVIEW_2026_08_06/test-gaps.md +364 -0
  31. package/src/REVIEW_2026_08_06/view.md +667 -0
  32. package/src/REVIEW_2026_08_06.md +571 -0
  33. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  34. package/src/core/binary/BinaryBuffer.js +12 -1
  35. package/src/core/binary/BitImage2.d.ts +5 -1
  36. package/src/core/binary/BitImage2.d.ts.map +1 -1
  37. package/src/core/binary/BitImage2.js +5 -1
  38. package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -1
  39. package/src/core/bvh8/build/NodeProxy.js +314 -308
  40. package/src/core/collection/list/List.d.ts +15 -0
  41. package/src/core/collection/list/List.d.ts.map +1 -1
  42. package/src/core/collection/list/List.js +34 -0
  43. package/src/core/collection/list/SortedListProjection.d.ts.map +1 -1
  44. package/src/core/collection/list/SortedListProjection.js +4 -1
  45. package/src/core/collection/map/BiMap.d.ts +25 -2
  46. package/src/core/collection/map/BiMap.d.ts.map +1 -1
  47. package/src/core/collection/map/BiMap.js +57 -3
  48. package/src/core/debug/matchers/IsAnything.d.ts.map +1 -1
  49. package/src/core/debug/matchers/IsAnything.js +5 -1
  50. package/src/core/events/signal/Signal.d.ts.map +1 -1
  51. package/src/core/events/signal/Signal.js +3 -1
  52. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -0
  53. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts +31 -0
  54. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts.map +1 -0
  55. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -0
  56. package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts +24 -2
  57. package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts.map +1 -1
  58. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -34
  59. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts +28 -0
  60. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts.map +1 -0
  61. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -0
  62. package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts +40 -0
  63. package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts.map +1 -0
  64. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -0
  65. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts +28 -0
  66. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts.map +1 -0
  67. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -0
  68. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts +6 -3
  69. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts.map +1 -1
  70. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +26 -13
  71. package/src/core/geom/2d/triangle/tri2_signed_area.d.ts +17 -1
  72. package/src/core/geom/2d/triangle/tri2_signed_area.d.ts.map +1 -1
  73. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -23
  74. package/src/core/geom/2d/v2_morton_encode.d.ts +20 -0
  75. package/src/core/geom/2d/v2_morton_encode.d.ts.map +1 -0
  76. package/src/core/geom/2d/v2_morton_encode.js +23 -0
  77. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts.map +1 -1
  78. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +4 -1
  79. package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts +27 -0
  80. package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts.map +1 -0
  81. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -0
  82. package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts +34 -0
  83. package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts.map +1 -0
  84. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -0
  85. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +8 -2
  86. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -1
  87. package/src/core/geom/3d/quaternion/quat3_multiply.js +8 -2
  88. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  89. package/src/core/geom/3d/shape/ConvexHullShape3D.js +32 -2
  90. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts +23 -6
  91. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts.map +1 -1
  92. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -124
  93. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts +17 -0
  94. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts.map +1 -0
  95. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -0
  96. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts +20 -0
  97. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts.map +1 -0
  98. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.js +40 -0
  99. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts +19 -0
  100. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts.map +1 -0
  101. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.js +34 -0
  102. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts +22 -0
  103. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts.map +1 -0
  104. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.js +37 -0
  105. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts +87 -0
  106. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts.map +1 -0
  107. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -0
  108. package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts +32 -0
  109. package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts.map +1 -1
  110. package/src/core/geom/3d/triangle/tri3_compute_signed_volume.js +66 -7
  111. package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts +22 -1
  112. package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts.map +1 -1
  113. package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.js +83 -38
  114. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -0
  115. package/src/core/geom/vec3/v3_cotangent.d.ts +6 -2
  116. package/src/core/geom/vec3/v3_cotangent.d.ts.map +1 -1
  117. package/src/core/geom/vec3/v3_cotangent.js +6 -2
  118. package/src/core/geom/vec3/v3_cross.d.ts +6 -2
  119. package/src/core/geom/vec3/v3_cross.d.ts.map +1 -1
  120. package/src/core/geom/vec3/v3_cross.js +6 -2
  121. package/src/core/geom/vec3/v3_multiply.d.ts +6 -2
  122. package/src/core/geom/vec3/v3_multiply.d.ts.map +1 -1
  123. package/src/core/geom/vec3/v3_multiply.js +6 -2
  124. package/src/core/geom/vec3/v3_subtract.d.ts +6 -2
  125. package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -1
  126. package/src/core/geom/vec3/v3_subtract.js +6 -2
  127. package/src/core/localization/Localization.js +1 -1
  128. package/src/core/math/random/randomSeed.d.ts +13 -0
  129. package/src/core/math/random/randomSeed.d.ts.map +1 -0
  130. package/src/core/math/random/randomSeed.js +16 -0
  131. package/src/core/math/spline/spline3_hermite_apply_transform.d.ts +50 -0
  132. package/src/core/math/spline/spline3_hermite_apply_transform.d.ts.map +1 -0
  133. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -0
  134. package/src/core/math/spline/spline3_hermite_reverse.d.ts +34 -0
  135. package/src/core/math/spline/spline3_hermite_reverse.d.ts.map +1 -0
  136. package/src/core/math/spline/spline3_hermite_reverse.js +49 -0
  137. package/src/core/math/statistics/hammersley_sequence.d.ts +17 -4
  138. package/src/core/math/statistics/hammersley_sequence.d.ts.map +1 -1
  139. package/src/core/math/statistics/hammersley_sequence.js +25 -16
  140. package/src/core/process/Future.d.ts.map +1 -1
  141. package/src/core/process/Future.js +12 -0
  142. package/src/core/process/PromiseWatcher.d.ts.map +1 -1
  143. package/src/core/process/PromiseWatcher.js +9 -2
  144. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  145. package/src/core/process/executor/ConcurrentExecutor.js +12 -2
  146. package/src/core/process/worker/WorkerProxy.d.ts.map +1 -1
  147. package/src/core/process/worker/WorkerProxy.js +5 -2
  148. package/src/core/process/worker/extractTransferables.d.ts.map +1 -1
  149. package/src/core/process/worker/extractTransferables.js +4 -1
  150. package/src/engine/Engine.d.ts +12 -12
  151. package/src/engine/Engine.d.ts.map +1 -1
  152. package/src/engine/Engine.js +149 -10
  153. package/src/engine/EngineHarness.d.ts.map +1 -1
  154. package/src/engine/EngineHarness.js +4 -1
  155. package/src/engine/asset/loaders/ArrayBufferLoader.d.ts.map +1 -1
  156. package/src/engine/asset/loaders/ArrayBufferLoader.js +7 -0
  157. package/src/engine/ecs/speaker/lines/sets/LineSetDescription.d.ts.map +1 -1
  158. package/src/engine/ecs/speaker/lines/sets/LineSetDescription.js +3 -1
  159. package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts.map +1 -1
  160. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +4 -1
  161. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +29 -0
  162. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
  163. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +84 -23
  164. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -0
  165. package/src/engine/graphics/GraphicsEngine.d.ts +56 -7
  166. package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
  167. package/src/engine/graphics/GraphicsEngine.js +196 -24
  168. package/src/engine/graphics/context/WebGLContextFailureReason.d.ts +9 -0
  169. package/src/engine/graphics/context/WebGLContextFailureReason.d.ts.map +1 -0
  170. package/src/engine/graphics/context/WebGLContextFailureReason.js +17 -0
  171. package/src/engine/graphics/context/WebGLContextMonitor.d.ts +106 -0
  172. package/src/engine/graphics/context/WebGLContextMonitor.d.ts.map +1 -0
  173. package/src/engine/graphics/context/WebGLContextMonitor.js +341 -0
  174. package/src/engine/graphics/context/WebGLContextState.d.ts +14 -0
  175. package/src/engine/graphics/context/WebGLContextState.d.ts.map +1 -0
  176. package/src/engine/graphics/context/WebGLContextState.js +24 -0
  177. package/src/engine/graphics/context/testWebGLContextLoss.d.ts +2 -0
  178. package/src/engine/graphics/context/testWebGLContextLoss.d.ts.map +1 -0
  179. package/src/engine/graphics/context/testWebGLContextLoss.js +696 -0
  180. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.d.ts.map +1 -1
  181. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -32
  182. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts +14 -0
  183. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts.map +1 -0
  184. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.js +41 -0
  185. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +1 -1
  186. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
  187. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +5 -0
  188. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +5 -1
  189. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
  190. package/src/engine/graphics/ecs/trail2d/Trail2D.js +15 -0
  191. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts +1 -1
  192. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
  193. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +2 -1
  194. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +4 -1
  195. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  196. package/src/engine/graphics/ecs/trail3d/Trail3D.js +488 -476
  197. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts +1 -1
  198. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
  199. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +280 -253
  200. package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.d.ts.map +1 -1
  201. package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +3 -2
  202. package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.d.ts.map +1 -1
  203. package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +3 -1
  204. package/src/engine/graphics/geometry/computeMeshSurfaceArea.js +1 -1
  205. package/src/engine/graphics/load_and_set_cubemap_v0.d.ts +5 -0
  206. package/src/engine/graphics/load_and_set_cubemap_v0.d.ts.map +1 -1
  207. package/src/engine/graphics/load_and_set_cubemap_v0.js +26 -21
  208. package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts.map +1 -1
  209. package/src/engine/graphics/particles/particular/group/ParticleGroup.js +19 -8
  210. package/src/engine/graphics/particles/particular/group/optimizeCommandQueue.js +1 -1
  211. package/src/engine/graphics/texture/atlas/AtlasLookupTexture.d.ts.map +1 -1
  212. package/src/engine/graphics/texture/atlas/AtlasLookupTexture.js +10 -3
  213. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  214. package/src/engine/graphics/texture/atlas/TextureAtlas.js +4 -1
  215. package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.d.ts.map +1 -1
  216. package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +11 -0
  217. package/src/engine/graphics/texture/cubemap/load_environment_map.d.ts.map +1 -1
  218. package/src/engine/graphics/texture/cubemap/load_environment_map.js +5 -2
  219. package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.d.ts.map +1 -1
  220. package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.js +26 -18
  221. package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts +15 -2
  222. package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts.map +1 -1
  223. package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.js +29 -3
  224. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts +11 -1
  225. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts.map +1 -1
  226. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +158 -171
  227. package/src/engine/graphics/texture/sampler/sampler2d_paint.js +1 -1
  228. package/src/engine/graphics/texture/sampler/util/bitSet2Sampler2D.js +2 -2
  229. package/src/engine/input/ecs/util/TopDownCameraControllerHelper.d.ts.map +1 -1
  230. package/src/engine/input/ecs/util/TopDownCameraControllerHelper.js +10 -3
  231. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts +14 -0
  232. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts.map +1 -0
  233. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.js +38 -0
  234. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  235. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +433 -425
  236. package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
  237. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  238. package/src/engine/physics/constraint/solve_constraints.js +38 -13
  239. package/src/engine/physics/contact/ManifoldStore.d.ts +15 -3
  240. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  241. package/src/engine/physics/contact/ManifoldStore.js +15 -3
  242. package/src/engine/physics/ecs/Joint.d.ts +7 -4
  243. package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
  244. package/src/engine/physics/ecs/Joint.js +7 -4
  245. package/src/engine/physics/ecs/PhysicsSystem.d.ts +14 -0
  246. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  247. package/src/engine/physics/ecs/PhysicsSystem.js +16 -0
  248. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  249. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  250. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +15 -7
  251. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  252. package/src/engine/physics/narrowphase/convex_convex_manifold.js +33 -10
  253. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +6 -2
  254. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
  255. package/src/engine/physics/narrowphase/refine_ray_concave.js +6 -2
  256. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +6 -2
  257. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  258. package/src/engine/physics/narrowphase/refine_ray_hit.js +6 -2
  259. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  260. package/src/engine/physics/queries/raycast.js +11 -4
  261. package/src/engine/save/storage/IndexedDBStorage.d.ts.map +1 -1
  262. package/src/engine/save/storage/IndexedDBStorage.js +21 -3
  263. package/src/engine/simulation/Ticker.d.ts +12 -0
  264. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  265. package/src/engine/simulation/Ticker.js +18 -0
  266. package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
  267. package/src/engine/sound/simulation/AcousticSimulator.js +9 -1
  268. package/src/engine/sound/simulation/core/VolumeField.d.ts +6 -2
  269. package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
  270. package/src/engine/sound/simulation/core/VolumeField.js +6 -2
  271. package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts +6 -2
  272. package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts.map +1 -1
  273. package/src/engine/sound/simulation/probe/AcousticProbeField.js +6 -2
  274. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts +6 -2
  275. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts.map +1 -1
  276. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +6 -2
  277. package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts +3 -1
  278. package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts.map +1 -1
  279. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +3 -1
  280. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  281. package/src/engine/ui/GUIEngine.js +8 -1
  282. package/src/generation/theme/TerrainTheme.d.ts.map +1 -1
  283. package/src/generation/theme/TerrainTheme.js +2 -1
  284. package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
  285. package/src/generation/theme/ThemeEngine.js +6 -5
  286. package/src/view/ViewGroup.d.ts.map +1 -1
  287. package/src/view/ViewGroup.js +9 -7
  288. package/src/view/graphics/WebGLContextFailureView.d.ts +19 -0
  289. package/src/view/graphics/WebGLContextFailureView.d.ts.map +1 -0
  290. package/src/view/graphics/WebGLContextFailureView.js +76 -0
  291. package/src/view/minimap/Minimap.d.ts +8 -0
  292. package/src/view/minimap/Minimap.d.ts.map +1 -1
  293. package/src/view/minimap/Minimap.js +16 -3
  294. package/src/view/minimap/dom/MinimapMarkerView.d.ts.map +1 -1
  295. package/src/view/minimap/dom/MinimapMarkerView.js +3 -1
@@ -0,0 +1,166 @@
1
+ # Correctness review — `meep/src/engine/asset/`
2
+
3
+ Scope: 81 non-spec source files under `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/`.
4
+ Existing suite state at review time: 18 suites / 119 tests pass, 1 suite skipped (`NativeImageDecoder.spec.js`).
5
+
6
+ ---
7
+
8
+ ### ASSET-1 Adam7 de-interlacing overflows its scanline buffer on pass 7
9
+ - **Severity**: CRITICAL
10
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/image/png/PNGReader.js:497` (allocation) and `:523` (offset)
11
+ - **Defect**: `interlaceAdam7` uses a double-buffered scanline (`line_offset = (y % 2) * passLineBytes`) but allocates only **one** line: `new Uint8Array(width * bytes_per_pixel)`. Pass 7 has `pass_width === width`, so `passLineBytes === width * bytes_per_pixel` — the whole buffer. Every odd-`y` scanline of pass 7 is therefore written entirely past the end of the typed array (writes silently dropped) and read back as `undefined` → stored as 0. The same odd-`y` region is also handed to the next line as `output_offset_previous`, so Up/Average/Paeth rows in pass 7 predict from zeros as well.
12
+ - **Failure scenario**: Any Adam7-interlaced PNG at least 4 pixels tall. Verified with a synthetic 2x4 8-bit grayscale interlaced PNG whose pixels are `[10,11, 20,21, 30,31, 40,41]`; `PNGReader.parse()` returns `[10,11, 20,21, 30,31, 0,0]`. Same test in RGB (colorType 2) returns the last row as `0,0,0, 0,0,0`. On a real interlaced PNG this blacks out output rows 3, 7, 11, 15, … (≈25% of the image) and corrupts more when non-`None` filters are used.
13
+ - **Fix**: allocate two lines — `new Uint8Array(2 * width * bytes_per_pixel)` (or `2 * max_pass_line_bytes`). Note pass 5 with an odd width also overruns by `bytes_per_pixel` today, and the same fix covers it.
14
+ - **Confidence**: Confirmed (reproduced by execution)
15
+
16
+ ---
17
+
18
+ ### ASSET-2 Fetch stream error during download hangs the load forever
19
+ - **Severity**: CRITICAL
20
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/ArrayBufferLoader.js:43-63`
21
+ - **Defect**: `observeResponseProgress`'s `pump()` does `reader.read().then(({done, value}) => {...})` with **no rejection handler**. If the underlying body stream errors mid-download (dropped connection, TLS reset, server abort, `net::ERR_INCOMPLETE_CHUNKED_ENCODING`), the read promise rejects; `controller.close()` / `controller.error()` are never called, so the wrapper `ReadableStream` stays open forever. `await response.arrayBuffer()` on line 154 never settles, so `load()` neither calls `success` nor `failure` and never rejects.
22
+ - **Failure scenario**: `assetManager.promise('data/models/big.glb', 'arraybuffer')`, server drops the connection after 3 MB of a 10 MB body → the `PendingAsset` stays in `request_map` and in `#pending_asset_active_load_set` forever. With a finite `load_concurrency` it also permanently consumes a concurrency slot. `AssetPreloader` never fires `levelFinished` for that level, so the loading screen never advances, and a graceful `AssetManager.shutdown()` never resolves (`#drain` waits on `request_map.size === 0`). Also produces an unhandled promise rejection. All PNG loads go through this loader (`ImageRGBADataLoader.__decode_via_worker`), so this is on the default image path.
23
+ - **Fix**: attach a rejection handler in `pump()` that calls `controller.error(e)` (and stop pumping), e.g. `reader.read().then(onChunk, e => controller.error(e))`. A `cancel(reason)` on the stream prototype that calls `reader.cancel(reason)` is also worth adding so an aborted consumer releases the reader.
24
+ - **Confidence**: Confirmed
25
+
26
+ ---
27
+
28
+ ### ASSET-3 Alpha-tested GLTF meshes never receive their custom depth material
29
+ - **Severity**: MAJOR
30
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/GLTFAssetLoader.js:260-272`
31
+ - **Defect**: The comment says a custom depth material is needed whenever `alphaTest !== 0`, and a `MeshDepthMaterial` is constructed unconditionally in that branch — but the assignment to `object.customDepthMaterial` sits inside `if (this.__enable_texture_cache)`. `__enable_texture_cache` is initialised to `false` (line 171) and there is no setter anywhere in the repo, so the branch is never taken. The freshly built `MeshDepthMaterial` is allocated and immediately dropped.
32
+ - **Failure scenario**: Load a GLTF whose foliage/decal material has `alphaTest = 0.5`. `object.customDepthMaterial` stays undefined → three renders the shadow pass with the default depth material, which ignores `alphaTest`+`map`, so a cut-out leaf plane casts a solid rectangular shadow. Every such mesh also allocates a throwaway `MeshDepthMaterial` per load.
33
+ - **Fix**: always assign, and only route through the cache when de-duplication is enabled:
34
+ `object.customDepthMaterial = this.__enable_texture_cache ? materialCache.acquire(depthMaterial) : depthMaterial;`
35
+ - **Confidence**: Confirmed
36
+
37
+ ---
38
+
39
+ ### ASSET-4 Texture loader rejects `jpeg` — including every `data:image/jpeg` URI
40
+ - **Severity**: MAJOR
41
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/texture/TextureAssetLoader.js:33-44`
42
+ - **Defect**: The dispatch switch handles only `'dds'`, `'png'`, `'jpg'`. The data-URI branch above it extracts the extension from the MIME subtype (`match[1]`), and the canonical MIME type for JPEG is `image/jpeg` — never `image/jpg`. So the one input format the data-URI branch exists to support cannot reach a loader. Plain files named `*.jpeg` fail the same way.
43
+ - **Failure scenario**: `assetManager.promise('data:image/jpeg;base64,/9j/4AAQSkZJRg...', 'texture')` → `computeFileExtension` returns `null` (base64 contains no `.`), the regex matches, `fileExtension = 'jpeg'`, the switch falls to `default` and throws `Unsupported texture file format: 'jpeg'`. The manager catches the synchronous throw and fails the request. Same for `assetManager.promise('data/ui/portrait.jpeg', 'texture')`.
44
+ - **Fix**: add `case 'jpeg':` (and `case 'webp':`, which `loadStandardImageTexture` handles fine via three's `TextureLoader`) alongside `case 'jpg':`.
45
+ - **Confidence**: Confirmed
46
+
47
+ ---
48
+
49
+ ### ASSET-5 Texture loaders violate the `progress(current, total)` contract
50
+ - **Severity**: MAJOR
51
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/texture/loadStandardImageTexture.js:37` and `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/texture/loadDDSTexture.js:36`
52
+ - **Defect**: Both pass the manager's `progress` callback straight into three.js's `onProgress` slot. three invokes `onProgress(progressEvent)` with a *single* `ProgressEvent` argument, but `AssetLoader.load`'s documented contract (and everything downstream) is `progress(current: number, total: number)`. `GLTFAssetLoader.load:443-445` gets this right (`progress(xhr.loaded, xhr.total)`); the texture loaders do not.
53
+ - **Failure scenario**: `assetManager.promise('data/t/rock.png', 'texture', { progress: (loaded, total) => bar.set(loaded / total) })` → `loaded` is a `ProgressEvent`, `total` is `undefined`, so `bar.set(NaN)`. Independently, `AssetManager.#dispatch_pending_asset`'s `progress` closure does `pendingAsset.progress.setValue(progressEvent)` / `setUpperLimit(undefined)` on a `BoundedValue`, which accepts them without validation (`BoundedValue.setValue` has no numeric guard) — any UI reading `PendingAsset.progress` gets a non-numeric value and a NaN ratio.
54
+ - **Fix**: wrap, as GLTFAssetLoader does: `e => progress(e.loaded, e.total)`.
55
+ - **Confidence**: Confirmed
56
+
57
+ ---
58
+
59
+ ### ASSET-6 `NativeImageDecoder.decodeSync` always throws — it reads the image before it has loaded
60
+ - **Severity**: MAJOR
61
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/image/codec/NativeImageDecoder.js:71-85`
62
+ - **Defect**: `decodeSync` sets `image.src = objectURL` and calls `make_decoded_data(image)` on the next line. Loading a blob URL into an `HTMLImageElement` is asynchronous — at that point `image.width`/`image.height` are still `0`. `decode()` sizes the canvas to `0x0` and calls `context.getImageData(0, 0, 0, 0)`, which per the HTML spec throws `IndexSizeError` when `sw` or `sh` is zero. There is no code path in which this method can return a decoded image.
63
+ - **Failure scenario**: `new NativeImageDecoder().decodeSync(pngBytes)` → `IndexSizeError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The source width is 0.` The only test for it (`NativeImageDecoder.spec.js:18`) is `test.skip`ped, and its expectation asserts a `channels: 4` field the implementation never produces — the method has never been executed.
64
+ - **Fix**: there is no synchronous path from encoded bytes to pixels in the DOM; either delete `decodeSync` or reimplement it over an already-decoded source (`ImageBitmap`/`ImageData`) rather than raw bytes.
65
+ - **Confidence**: Confirmed
66
+
67
+ ---
68
+
69
+ ### ASSET-7 Palette PNG: indices past the end of `tRNS` become fully transparent instead of opaque
70
+ - **Severity**: MAJOR
71
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/image/png/PNG.js:216-230` (specifically `:219-221`)
72
+ - **Defect**: `getPixel`, colour-type 3, does `a = this.transparency_lookup[pixels[i]]` with no bounds check. The PNG spec allows a `tRNS` chunk shorter than the palette; missing entries are fully opaque (255). Reading past the `Uint8Array` yields `undefined`, which a `Uint8Array` destination stores as **0** — fully transparent. `getUint8Data_case3` (`PNG.js:369-377`) implements the spec rule correctly, so the two readers of the same PNG disagree.
73
+ - **Failure scenario**: An indexed PNG with a 256-entry PLTE and a 2-entry tRNS (the common "index 0 and 1 are the transparent ones" encoding produced by pngquant/optipng). `png.getRGBA8Array()` → `getRGBA8Array_generic` → `getPixel`: every pixel using palette index ≥ 2 gets alpha 0, i.e. the whole opaque body of the image is returned invisible.
74
+ - **Fix**: mirror `getUint8Data_case3`: `const idx = pixels[i]; a = idx < this.transparency_lookup.length ? this.transparency_lookup[idx] : 255;`
75
+ - **Confidence**: Confirmed
76
+
77
+ ---
78
+
79
+ ### ASSET-8 `decodetEXt` / `decodeiEXt` parse the whole PNG file instead of the chunk
80
+ - **Severity**: MAJOR
81
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/image/png/PNGReader.js:260-267` and `:273-295`
82
+ - **Defect**: `PNGReader.readBytes` returns `new Uint8Array(buffer.data, buffer.position, length)` — a *view* into the whole-file `ArrayBuffer` with a non-zero `byteOffset`. Both text decoders then do `BinaryBuffer.fromArrayBuffer(chunk.buffer)`, which binds the **entire PNG file** and resets `position` to 0, discarding `chunk.byteOffset` entirely. `png_chunk_decode_zTXt`/`png_chunk_decode_iTXt` handle this correctly (they explicitly set `buffer.position = chunk.byteOffset`); these two do not. `decodetEXt` additionally computes the value length as `keyword.length - 1`, which has no relation to the value's actual length (it should be `chunk.length - keyword.length - 1`).
83
+ - **Failure scenario**: `reader.decodetEXt(chunk)` on any PNG → the keyword is read from byte 0 of the file, i.e. the PNG signature `\x89PNG\r\n\x1a\n…`, stopping at the first NUL; `png.text` is keyed by that garbage and holds a value sliced at an arbitrary length. Never the chunk's real keyword/value.
84
+ - **Fix**: bind the chunk's own range in both methods, as the `png_chunk_decode_*` helpers do (`fromArrayBuffer(chunk.buffer)` + `position = chunk.byteOffset`, bounded by `chunk.byteLength`), and compute the tEXt value length as `chunk.byteLength - (position - chunk.byteOffset)`.
85
+ - **Confidence**: Confirmed
86
+
87
+ ---
88
+
89
+ ### ASSET-9 Stale `Asset` value returned from `STATIC_ASSET_CACHE` after an asset is replaced
90
+ - **Severity**: MAJOR
91
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/STATIC_ASSET_CACHE.js:9-16` (with `Asset.equals`/`Asset.hash` at `Asset.js:45-63`)
92
+ - **Defect**: The cache is keyed by `Asset` using `invokeObjectEquals`/`invokeObjectHash`, and `Asset.equals` treats two *distinct* `Asset` objects as equal whenever their `description` matches (`AssetManager.#settle` stamps `value.description = assetDescription` on every load). So the cache key identifies the *description*, not the instance, and a replacement `Asset` for the same `(path, type)` collides with the old one's cached materialisation.
93
+ - **Failure scenario**: `assetManager.remove('data/models/hero.glb', 'model/gltf')` (or `insert()` with a rebuilt asset — `AssetManager.insert:664` explicitly supports replacement and only warns) followed by a fresh load produces a new `Asset` B with the same description as the evicted `Asset` A. `SGMeshSystem` (`engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js:144`) calls `STATIC_ASSET_CACHE.get(B)`, hits A's entry, and renders the *old* `Object3D` — the replacement is silently ignored until the cache evicts. Secondarily, every `Asset` whose `description` is still `null` hashes to 0, so all pre-settle assets pile into one bucket.
94
+ - **Fix**: key the cache by identity (a `Map`/`WeakMap` on the `Asset` instance) rather than by description equality, or purge the entry for a description on `AssetManager.remove`/`insert`.
95
+ - **Confidence**: Confirmed (mechanism); trigger requires an asset replacement flow
96
+
97
+ ---
98
+
99
+ ### ASSET-10 `guessAssetType` mis-slices any URL that does not contain `data/`
100
+ - **Severity**: MINOR
101
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/guessAssetType.js:12`
102
+ - **Defect**: `url.substring(url.indexOf('data/') + 'data/'.length)` — when `'data/'` is absent, `indexOf` returns `-1` and the expression becomes `substring(4)`, silently chopping the first four characters of the URL and then deriving the "asset directory" from the mangled remainder. There is no `-1` branch.
103
+ - **Failure scenario**: `guessAssetType('assets/models/hero.json')` → `indexOf` is `-1` → `substring(4)` = `'ts/models/hero.json'` → directory `'ts'` → returns `'json'` instead of `'three.js'`. `GameCreationScene.js:153` and `view/renderModel.js:77` feed the result straight into `assetManager.get({ type })`, so the model is handed to `JsonAssetLoader` and the caller receives a raw object where an `Object3D` factory was expected.
104
+ - **Fix**: `const i = url.indexOf(assetRootPath); let assetDirectory = i === -1 ? url : url.substring(i + assetRootPath.length);`
105
+ - **Confidence**: Confirmed
106
+
107
+ ---
108
+
109
+ ### ASSET-11 `textureMipmapEqual` compares height against width
110
+ - **Severity**: MINOR
111
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/material/computeTextureEquality.js:207`
112
+ - **Defect**: `if (a.height !== b.width) { return false; }` — copy-paste; should be `a.height !== b.height`.
113
+ - **Failure scenario**: Two byte-identical 512x256 compressed-texture mip levels: `a.width === b.width` passes, then `a.height (256) !== b.width (512)` returns `false`. `computeTextureEquality` therefore reports identical non-square textures as different, so `GLTFAssetLoader.__texture_cache` and `StaticMaterialCache.textureCache` never de-duplicate non-square DDS/compressed textures — every GLTF instance keeps its own GPU upload.
114
+ - **Fix**: `if (a.height !== b.height) { return false; }`
115
+ - **Confidence**: Confirmed
116
+
117
+ ---
118
+
119
+ ### ASSET-12 `computeImageBitmapHash` hashes the red channel `itemSize` times and ignores G/B/A
120
+ - **Severity**: MINOR
121
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/material/computeImageBitmapHash.js:31-36`
122
+ - **Defect**: The inner loop reads `sampler_data[address]` — `address` is never offset by the loop variable `i`, so channel 0 is folded into the hash four times and channels 1..3 are never read.
123
+ - **Failure scenario**: Two 8x8 `ImageBitmap`s that share the red channel but differ entirely in green/blue/alpha (e.g. a red-channel-packed roughness map vs. a metalness map baked from the same source) hash identically. Every such texture lands in the same `HashSet` bucket in `GLTFAssetLoader.__texture_cache` (capacity 2048), degrading the de-dup pass to a linear scan with a full `computeImageBitmapEquality` (GPU readback + array compare) per collision. The result stays correct because equality is exact; the cost is not.
124
+ - **Fix**: `const v = sampler_data[address + i];`
125
+ - **Confidence**: Confirmed
126
+
127
+ ---
128
+
129
+ ### ASSET-13 `NativeImageDecoder.decode` leaks the object URL when decoding fails
130
+ - **Severity**: MINOR
131
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/image/codec/NativeImageDecoder.js:87-104`
132
+ - **Defect**: `URL.revokeObjectURL(url)` runs only after `await image.decode()` resolves. If `decode()` rejects (corrupt/unsupported bytes) the function throws and the blob URL — and the blob it pins — is never released.
133
+ - **Failure scenario**: `CodecWithFallback` routes every non-PNG through `NativeImageDecoder`. A batch of truncated or unsupported image assets (a CDN serving an HTML error page under an image path, so `image.decode()` rejects) leaks one `Blob` per failed asset for the lifetime of the document.
134
+ - **Fix**: wrap in `try { … } finally { URL.revokeObjectURL(url); }`.
135
+ - **Confidence**: Confirmed
136
+
137
+ ---
138
+
139
+ ### ASSET-14 Preloader global progress never reaches 1 once any asset fails
140
+ - **Severity**: MINOR
141
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/preloader/AssetPreloader.js:264-281` and `:291-304`
142
+ - **Defect**: `on.progress` is dispatched only from `assetLoadSuccess`, and the global ratio is `#asset_count_loaded / #asset_count`. Failures increment `#asset_count_failed` (which counts toward `#try_finish`) but are excluded from the progress numerator and emit no progress event at all. The last progress event a listener ever sees is therefore `(total - failures) / total`.
143
+ - **Failure scenario**: 20 queued assets, one 404s. The final `on.progress` reports `0.95`; `on.resolved` then fires with `(19, 1)`. A loading bar driven by `on.progress` freezes at 95% and the scene switches under it. (Note the previously-suspected missing level-completion check in `assetLoadFailed` is present and correct at lines 299-301 — a failure does close its level batch.)
144
+ - **Fix**: dispatch progress from `assetLoadFailed` too, and compute the ratio from processed count: `(#asset_count_loaded + #asset_count_failed) / #asset_count`, keeping the loaded count as a separate field on the payload.
145
+ - **Confidence**: Confirmed
146
+
147
+ ---
148
+
149
+ ### ASSET-15 JPEG malformed-APP1 recovery compares a 16-bit marker against 8-bit constants
150
+ - **Severity**: MINOR
151
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/asset/loaders/image/jpeg/JpegImage.js:365`
152
+ - **Defect**: `fileMarker` comes from `readUint16()`, so APP0/APP1 are `0xFFE0`/`0xFFE1`. The recovery branch tests `fileMarker === 0xE0 || fileMarker === 0xE1`, which can never be true. The whole `malformedDataOffset` recovery path (lines 365-377), added per the comment to handle "malformed APP1 markers popular in some phone models", is unreachable.
153
+ - **Failure scenario**: A phone-camera JPEG with a malformed APP1 segment reaches the `default` case; instead of resynchronising it falls through to `throw new Error("unknown JPEG marker " + …)` and `jpeg_decode` fails outright, where the intended behaviour was to skip the bad segment and decode.
154
+ - **Fix**: compare against `0xFFE0`/`0xFFE1`.
155
+ - **Confidence**: Confirmed
156
+
157
+ ---
158
+
159
+ ## Status of the pre-declared "already known" items
160
+
161
+ | Item | Status |
162
+ | --- | --- |
163
+ | `AssetPreloader.assetLoadFailed` missing the level-completion check | **FIXED** — the `(batchElementLoadedCount + batchElementFailedCount) >= batch_size` guard is present at `AssetPreloader.js:299-301`; a failure closes its level. (Separate, still-live progress accounting issue filed as ASSET-14.) |
164
+ | 1-bit and sub-8-bit palette PNG decoding to garbage | **STILL PRESENT** — `png_filter_unFilterNone` unpacks sub-byte samples one-per-byte while filters 1–4 (`Sub`/`Up`/`Average`/`Paeth`) copy packed bytes, and `interlaceNone` sizes both `pixels` and `length` as *packed* bytes (`ceil(bpp*width)`), so even the None path only emits `ceil(width/2)` of `width` nibbles. `PNG.getUint8Data_case3` compounds it with `d = colors * ceil(bitDepth/8)` → 1, reading two 4-bit indices as one. Interlaced sub-8-bit is worse still: `interlaceAdam7`'s `bytes_per_pixel` is fractional, so `new Uint8Array(fractional)` truncates to a zero/short buffer. |
165
+ | `SVGAssetLoader` hanging on any non-200 response | **STILL PRESENT** — `SVGAssetLoader.js:9-21`: the `load` handler only acts when `status === 200`, and the `error` listener does not fire for HTTP error statuses, so 404/500/3xx neither resolve nor reject. Additionally the 200 path never checks for a `<parsererror>` root, so malformed SVG resolves as a "successful" asset whose `create()` clones an error node. |
166
+ | Worker image decoder detaching a cached `ArrayBuffer` | **STILL PRESENT** — `ThreadedImageDecoder.decode` passes `[data.buffer]` to `OnDemandWorkerManager.request`, transferring/neutering the buffer that `ImageRGBADataLoader.__decode_via_worker` obtained from the cached `arraybuffer` `Asset`. Second request for the same path re-uses the cached (now detached) asset → zero-length image. It also kills the `CodecWithFallback` chain: after the threaded codec fails, `NativeImageDecoder` receives a detached `data` and can only produce an empty blob. |