@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.
- package/README.md +2 -2
- package/build/bundle-worker-image-decoder.js +1 -1
- package/package.json +1 -1
- package/src/REVIEW_2026_08_06/adv-core.md +511 -0
- package/src/REVIEW_2026_08_06/adv-gfx.md +480 -0
- package/src/REVIEW_2026_08_06/adv-view.md +446 -0
- package/src/REVIEW_2026_08_06/api-core.md +296 -0
- package/src/REVIEW_2026_08_06/api-engine.md +195 -0
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -0
- package/src/REVIEW_2026_08_06/core-binary.md +348 -0
- package/src/REVIEW_2026_08_06/core-collection.md +376 -0
- package/src/REVIEW_2026_08_06/core-math.md +221 -0
- package/src/REVIEW_2026_08_06/core-process.md +266 -0
- package/src/REVIEW_2026_08_06/core-science.md +223 -0
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -0
- package/src/REVIEW_2026_08_06/engine-animation.md +167 -0
- package/src/REVIEW_2026_08_06/engine-asset.md +166 -0
- package/src/REVIEW_2026_08_06/engine-ecs.md +920 -0
- package/src/REVIEW_2026_08_06/engine-input.md +197 -0
- package/src/REVIEW_2026_08_06/engine-misc.md +248 -0
- package/src/REVIEW_2026_08_06/engine-terrain.md +52 -0
- package/src/REVIEW_2026_08_06/generation.md +176 -0
- package/src/REVIEW_2026_08_06/geom-2d.md +434 -0
- package/src/REVIEW_2026_08_06/geom-3d.md +117 -0
- package/src/REVIEW_2026_08_06/gfx-gi.md +143 -0
- package/src/REVIEW_2026_08_06/gfx-render.md +740 -0
- package/src/REVIEW_2026_08_06/gfx-texture.md +690 -0
- package/src/REVIEW_2026_08_06/prior-criticals.md +49 -0
- package/src/REVIEW_2026_08_06/prior-highs.md +138 -0
- package/src/REVIEW_2026_08_06/test-gaps.md +364 -0
- package/src/REVIEW_2026_08_06/view.md +667 -0
- package/src/REVIEW_2026_08_06.md +571 -0
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +12 -1
- package/src/core/binary/BitImage2.d.ts +5 -1
- package/src/core/binary/BitImage2.d.ts.map +1 -1
- package/src/core/binary/BitImage2.js +5 -1
- package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -1
- package/src/core/bvh8/build/NodeProxy.js +314 -308
- package/src/core/collection/list/List.d.ts +15 -0
- package/src/core/collection/list/List.d.ts.map +1 -1
- package/src/core/collection/list/List.js +34 -0
- package/src/core/collection/list/SortedListProjection.d.ts.map +1 -1
- package/src/core/collection/list/SortedListProjection.js +4 -1
- package/src/core/collection/map/BiMap.d.ts +25 -2
- package/src/core/collection/map/BiMap.d.ts.map +1 -1
- package/src/core/collection/map/BiMap.js +57 -3
- package/src/core/debug/matchers/IsAnything.d.ts.map +1 -1
- package/src/core/debug/matchers/IsAnything.js +5 -1
- package/src/core/events/signal/Signal.d.ts.map +1 -1
- package/src/core/events/signal/Signal.js +3 -1
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -0
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts +31 -0
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -0
- package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts +24 -2
- package/src/core/geom/2d/polygon/polygon2_signed_area.d.ts.map +1 -1
- package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -34
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts +28 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts +40 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -0
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts +28 -0
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.d.ts.map +1 -0
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -0
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts +6 -3
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.d.ts.map +1 -1
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +26 -13
- package/src/core/geom/2d/triangle/tri2_signed_area.d.ts +17 -1
- package/src/core/geom/2d/triangle/tri2_signed_area.d.ts.map +1 -1
- package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -23
- package/src/core/geom/2d/v2_morton_encode.d.ts +20 -0
- package/src/core/geom/2d/v2_morton_encode.d.ts.map +1 -0
- package/src/core/geom/2d/v2_morton_encode.js +23 -0
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.d.ts.map +1 -1
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +4 -1
- package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts +27 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.d.ts.map +1 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -0
- package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts +34 -0
- package/src/core/geom/3d/polygon/polygon3_triangulate.d.ts.map +1 -0
- package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -0
- package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +8 -2
- package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -1
- package/src/core/geom/3d/quaternion/quat3_multiply.js +8 -2
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +32 -2
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts +23 -6
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -124
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts +17 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts +20 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_count_edge_faces.js +40 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_closed.js +34 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts +22 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_is_manifold.js +37 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts +87 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -0
- package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts +32 -0
- package/src/core/geom/3d/triangle/tri3_compute_signed_volume.d.ts.map +1 -1
- package/src/core/geom/3d/triangle/tri3_compute_signed_volume.js +66 -7
- package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts +22 -1
- package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.d.ts.map +1 -1
- package/src/core/geom/3d/triangle/tri3_mesh_compute_signed_volume.js +83 -38
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -0
- package/src/core/geom/vec3/v3_cotangent.d.ts +6 -2
- package/src/core/geom/vec3/v3_cotangent.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_cotangent.js +6 -2
- package/src/core/geom/vec3/v3_cross.d.ts +6 -2
- package/src/core/geom/vec3/v3_cross.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_cross.js +6 -2
- package/src/core/geom/vec3/v3_multiply.d.ts +6 -2
- package/src/core/geom/vec3/v3_multiply.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_multiply.js +6 -2
- package/src/core/geom/vec3/v3_subtract.d.ts +6 -2
- package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_subtract.js +6 -2
- package/src/core/localization/Localization.js +1 -1
- package/src/core/math/random/randomSeed.d.ts +13 -0
- package/src/core/math/random/randomSeed.d.ts.map +1 -0
- package/src/core/math/random/randomSeed.js +16 -0
- package/src/core/math/spline/spline3_hermite_apply_transform.d.ts +50 -0
- package/src/core/math/spline/spline3_hermite_apply_transform.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -0
- package/src/core/math/spline/spline3_hermite_reverse.d.ts +34 -0
- package/src/core/math/spline/spline3_hermite_reverse.d.ts.map +1 -0
- package/src/core/math/spline/spline3_hermite_reverse.js +49 -0
- package/src/core/math/statistics/hammersley_sequence.d.ts +17 -4
- package/src/core/math/statistics/hammersley_sequence.d.ts.map +1 -1
- package/src/core/math/statistics/hammersley_sequence.js +25 -16
- package/src/core/process/Future.d.ts.map +1 -1
- package/src/core/process/Future.js +12 -0
- package/src/core/process/PromiseWatcher.d.ts.map +1 -1
- package/src/core/process/PromiseWatcher.js +9 -2
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +12 -2
- package/src/core/process/worker/WorkerProxy.d.ts.map +1 -1
- package/src/core/process/worker/WorkerProxy.js +5 -2
- package/src/core/process/worker/extractTransferables.d.ts.map +1 -1
- package/src/core/process/worker/extractTransferables.js +4 -1
- package/src/engine/Engine.d.ts +12 -12
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +149 -10
- package/src/engine/EngineHarness.d.ts.map +1 -1
- package/src/engine/EngineHarness.js +4 -1
- package/src/engine/asset/loaders/ArrayBufferLoader.d.ts.map +1 -1
- package/src/engine/asset/loaders/ArrayBufferLoader.js +7 -0
- package/src/engine/ecs/speaker/lines/sets/LineSetDescription.d.ts.map +1 -1
- package/src/engine/ecs/speaker/lines/sets/LineSetDescription.js +3 -1
- package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/TerrainSystem.js +4 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +29 -0
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +84 -23
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -0
- package/src/engine/graphics/GraphicsEngine.d.ts +56 -7
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +196 -24
- package/src/engine/graphics/context/WebGLContextFailureReason.d.ts +9 -0
- package/src/engine/graphics/context/WebGLContextFailureReason.d.ts.map +1 -0
- package/src/engine/graphics/context/WebGLContextFailureReason.js +17 -0
- package/src/engine/graphics/context/WebGLContextMonitor.d.ts +106 -0
- package/src/engine/graphics/context/WebGLContextMonitor.d.ts.map +1 -0
- package/src/engine/graphics/context/WebGLContextMonitor.js +341 -0
- package/src/engine/graphics/context/WebGLContextState.d.ts +14 -0
- package/src/engine/graphics/context/WebGLContextState.d.ts.map +1 -0
- package/src/engine/graphics/context/WebGLContextState.js +24 -0
- package/src/engine/graphics/context/testWebGLContextLoss.d.ts +2 -0
- package/src/engine/graphics/context/testWebGLContextLoss.d.ts.map +1 -0
- package/src/engine/graphics/context/testWebGLContextLoss.js +696 -0
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -32
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts +14 -0
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts.map +1 -0
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.js +41 -0
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +5 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +5 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +15 -0
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +2 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +4 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +488 -476
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +280 -253
- package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/BuildNeighborsFast.js +3 -2
- package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.d.ts.map +1 -1
- package/src/engine/graphics/geometry/MikkT/GenerateSharedVerticesIndexList.js +3 -1
- package/src/engine/graphics/geometry/computeMeshSurfaceArea.js +1 -1
- package/src/engine/graphics/load_and_set_cubemap_v0.d.ts +5 -0
- package/src/engine/graphics/load_and_set_cubemap_v0.d.ts.map +1 -1
- package/src/engine/graphics/load_and_set_cubemap_v0.js +26 -21
- package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/group/ParticleGroup.js +19 -8
- package/src/engine/graphics/particles/particular/group/optimizeCommandQueue.js +1 -1
- package/src/engine/graphics/texture/atlas/AtlasLookupTexture.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/AtlasLookupTexture.js +10 -3
- package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/TextureAtlas.js +4 -1
- package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.d.ts.map +1 -1
- package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +11 -0
- package/src/engine/graphics/texture/cubemap/load_environment_map.d.ts.map +1 -1
- package/src/engine/graphics/texture/cubemap/load_environment_map.js +5 -2
- package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/distance/computeSignedDistanceField_Chamfer.js +26 -18
- package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts +15 -2
- package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/filter/sampler2d_blur_gaussian.js +29 -3
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts +11 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +158 -171
- package/src/engine/graphics/texture/sampler/sampler2d_paint.js +1 -1
- package/src/engine/graphics/texture/sampler/util/bitSet2Sampler2D.js +2 -2
- package/src/engine/input/ecs/util/TopDownCameraControllerHelper.d.ts.map +1 -1
- package/src/engine/input/ecs/util/TopDownCameraControllerHelper.js +10 -3
- package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts +14 -0
- package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts.map +1 -0
- package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.js +38 -0
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +433 -425
- package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +38 -13
- package/src/engine/physics/contact/ManifoldStore.d.ts +15 -3
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +15 -3
- package/src/engine/physics/ecs/Joint.d.ts +7 -4
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +7 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +14 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +16 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +15 -7
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +33 -10
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +6 -2
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +6 -2
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +6 -2
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +6 -2
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +11 -4
- package/src/engine/save/storage/IndexedDBStorage.d.ts.map +1 -1
- package/src/engine/save/storage/IndexedDBStorage.js +21 -3
- package/src/engine/simulation/Ticker.d.ts +12 -0
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +18 -0
- package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
- package/src/engine/sound/simulation/AcousticSimulator.js +9 -1
- package/src/engine/sound/simulation/core/VolumeField.d.ts +6 -2
- package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
- package/src/engine/sound/simulation/core/VolumeField.js +6 -2
- package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts +6 -2
- package/src/engine/sound/simulation/probe/AcousticProbeField.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/AcousticProbeField.js +6 -2
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts +6 -2
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +6 -2
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts +3 -1
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +3 -1
- package/src/engine/ui/GUIEngine.d.ts.map +1 -1
- package/src/engine/ui/GUIEngine.js +8 -1
- package/src/generation/theme/TerrainTheme.d.ts.map +1 -1
- package/src/generation/theme/TerrainTheme.js +2 -1
- package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
- package/src/generation/theme/ThemeEngine.js +6 -5
- package/src/view/ViewGroup.d.ts.map +1 -1
- package/src/view/ViewGroup.js +9 -7
- package/src/view/graphics/WebGLContextFailureView.d.ts +19 -0
- package/src/view/graphics/WebGLContextFailureView.d.ts.map +1 -0
- package/src/view/graphics/WebGLContextFailureView.js +76 -0
- package/src/view/minimap/Minimap.d.ts +8 -0
- package/src/view/minimap/Minimap.d.ts.map +1 -1
- package/src/view/minimap/Minimap.js +16 -3
- package/src/view/minimap/dom/MinimapMarkerView.d.ts.map +1 -1
- package/src/view/minimap/dom/MinimapMarkerView.js +3 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Correctness review — meep `core/` (binary, json, parser, codegen, lang, localization, path, primitives, model)
|
|
2
|
+
|
|
3
|
+
Scope reviewed: every non-spec source file under the nine listed directories.
|
|
4
|
+
Findings marked **Confirmed** were reproduced by executing the real code (node, ESM, asserts active); the reproductions are noted inline.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
### BIN-1 `writeUTF8String` destroys the string body when the buffer grows mid-write
|
|
9
|
+
- **Severity**: CRITICAL
|
|
10
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/binary/BinaryBuffer.js:1086-1141` (interacting with `setCapacity` at `:192-203`)
|
|
11
|
+
- **Defect**: `writeUTF8String` writes UTF-8 bytes into a local `cursor` and only assigns `this.position = cursor` at the very end (line 1141). When the loop hits `ensureCapacity(cursor + 4)` at line 1114, `setCapacity` copies only `Math.min(oldLen, newLen, this.position)` bytes — and `this.position` is still pointing at the *start* of the string body. Every byte written between the start of the body and `cursor` is silently replaced with zeroes.
|
|
12
|
+
- **Failure scenario**: `const b = new BinaryBuffer(); b.writeUTF8String("\u0416".repeat(3000)); b.position = 0; b.readUTF8String()` returns `""` instead of the 3000-character string. Same for `"\u65E5".repeat(3000)` (CJK) and `"\u{1F600}".repeat(3000)` (emoji). The initial reserve is only `1.5 * string.length + 7` bytes, so any string whose UTF-8 size exceeds that by more than one growth step (≥1024 bytes) corrupts — i.e. roughly any Cyrillic/Greek/Hebrew/CJK/emoji string longer than ~2000 code units. ASCII of the same length round-trips fine, which is why this has gone unnoticed.
|
|
13
|
+
- **Fix**: assign `this.position = cursor;` immediately before the in-loop `ensureCapacity(cursor + 4)` call (and re-read `this.__data_uint8`/`this.capacity` after, as it already does). Alternatively size the reserve exactly up front (4 bytes per code unit) so no in-loop growth can occur.
|
|
14
|
+
- **Confidence**: Confirmed (reproduced)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### BIN-2 `Localization.loadLocale` hangs forever when `path` ends with a separator
|
|
19
|
+
- **Severity**: CRITICAL
|
|
20
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/localization/Localization.js:140-143`
|
|
21
|
+
- **Defect**: the trailing-separator trim loop calls `_path.slice(0, _path.length - 1)` and discards the result. `String.slice` does not mutate, so `_path` never changes and the `while` condition never becomes false.
|
|
22
|
+
- **Failure scenario**: `localization.loadLocale('en-gb', 'data/database/text/')` — the loop spins forever, freezing the tab/worker. Any caller passing a path with a trailing `/` or `\` hangs the application. (The default argument has no trailing slash, which is why the shipped path works.)
|
|
23
|
+
- **Fix**: `_path = _path.slice(0, _path.length - 1);`
|
|
24
|
+
- **Confidence**: Confirmed (static; not executed because it hangs)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
### BIN-3 Lone high surrogates desynchronise the UTF-8 stream
|
|
29
|
+
- **Severity**: MAJOR
|
|
30
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/binary/BinaryBuffer.js:1084` (length header) and `:1106-1108` (drop), read side `:1172`
|
|
31
|
+
- **Defect**: the length header written is `string.length` in UTF-16 code units, but a lone high surrogate is skipped (`continue`) and contributes zero bytes. The reader loops `while (i < capacity && charCount < stringLength)`, so it keeps consuming bytes *past the end of the string* to make up the missing count.
|
|
32
|
+
- **Failure scenario**:
|
|
33
|
+
```js
|
|
34
|
+
const b = new BinaryBuffer();
|
|
35
|
+
b.writeUTF8String("\uD800abc"); // length header = 4, only 3 bytes written
|
|
36
|
+
b.writeUint32(0x41414141);
|
|
37
|
+
b.position = 0;
|
|
38
|
+
b.readUTF8String(); // "abcA" <-- ate a byte of the next field
|
|
39
|
+
b.readUint32(); // 0x00414141 instead of 0x41414141
|
|
40
|
+
```
|
|
41
|
+
Every field after the string is shifted by one byte. Lone high surrogates arise routinely from slicing a string that ends mid-emoji (e.g. truncating a player name).
|
|
42
|
+
- **Fix**: count what is actually emitted. Either pre-scan and write the number of code units the writer will really encode, or write a byte-length prefix instead of a code-unit count, or encode the lone surrogate (CESU-8) instead of dropping it — the reader already round-trips lone *low* surrogates that way.
|
|
43
|
+
- **Confidence**: Confirmed (reproduced)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### BIN-4 `StructureBuilder.build()` always throws `ReferenceError`
|
|
48
|
+
- **Severity**: MAJOR
|
|
49
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/binary/type/TypeBuilder.js:76`
|
|
50
|
+
- **Defect**: `buildConstructorFunction(structure)` calls `buildTypePrototype(type, lines)`, but the parameter is named `structure` — `type` is not defined in any enclosing scope.
|
|
51
|
+
- **Failure scenario**: `new StructureBuilder("Foo").build()` throws `ReferenceError: type is not defined`. The class has no working code path at all.
|
|
52
|
+
- **Fix**: `buildTypePrototype(structure, lines);`
|
|
53
|
+
- **Confidence**: Confirmed (reproduced)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### BIN-5 `Type.clone()` loses the type name and flags
|
|
58
|
+
- **Severity**: MAJOR
|
|
59
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/binary/type/Type.js:74-78`
|
|
60
|
+
- **Defect**: `clone()` does `new Type()` with no arguments and then copies only `fields`. `name`, `isPrimitive` and `isFixedLength` are dropped.
|
|
61
|
+
- **Failure scenario**: `new Type("Foo").clone()` throws `Error: Name must be of type "string", instead was "undefined"` when asserts are live. With asserts compiled out (production) it silently returns a `Type` whose `name` is `undefined`, which then poisons `StructureRegistry.__add` (it keys `this.types[undefined]`) and every `getFieldByName`/error message.
|
|
62
|
+
- **Fix**: `const result = new Type(this.name); result.isPrimitive = this.isPrimitive; result.isFixedLength = this.isFixedLength; result.fields = this.fields.slice();`
|
|
63
|
+
- **Confidence**: Confirmed (reproduced)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### BIN-6 `DataViewStructAccessor` generates broken accessors for `Uint64`/`Int64`/`Float16`
|
|
68
|
+
- **Severity**: MAJOR
|
|
69
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/binary/data_view/DataType2DataViewReaders.js:12,17` and `DataType2DataViewWriters.js:12,17`; codegen at `buildAccessor.js:49-53`
|
|
70
|
+
- **Defect**: `DataView` exposes `getBigUint64`/`getBigInt64`, not `getUint64`/`getInt64`. `BinaryDataType.Float16` has no entry in either map at all, yet `assert.enum(type, BinaryDataType)` accepts it and `DataTypeByteSizes` gives it a size, so the schema is considered valid and the generated getter body becomes `return this.$data.undefined(...)`.
|
|
71
|
+
- **Failure scenario**:
|
|
72
|
+
- `new DataViewStructAccessor({ v: BinaryDataType.Uint64 }).v` → `TypeError: this.$data.getUint64 is not a function`
|
|
73
|
+
- `new DataViewStructAccessor({ v: BinaryDataType.Float16 }).v` → `TypeError: this.$data.undefined is not a function`
|
|
74
|
+
Both fail at first *access*, not at construction, so the schema looks accepted.
|
|
75
|
+
- **Fix**: map `Uint64`→`getBigUint64`/`setBigUint64` and `Int64`→`getBigInt64`/`setBigInt64`; either add a Float16 path (`getUint16` + `half_to_float_uint16`) or make `buildAccessor` throw for types with no reader/writer mapping instead of emitting `undefined`.
|
|
76
|
+
- **Confidence**: Confirmed (reproduced)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### BIN-7 `BitSet.shift()` shifts right for negative distances
|
|
81
|
+
- **Severity**: MAJOR
|
|
82
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/binary/BitSet.js:547-553`
|
|
83
|
+
- **Defect**: the negative branch calls `this.shift_right(-distance, ...)` instead of `this.shift_left(-distance, ...)`.
|
|
84
|
+
- **Failure scenario**: `const s = new BitSet(); s.set(5, true); s.shift(-1, 0, 10);` → bit 6 is set, bits 4 and 5 are clear. Expected bit 4 set. The sign of the argument is inverted in effect: a "shift left by 1" moves everything one place right.
|
|
85
|
+
- **Fix**: `this.shift_left(-distance, start_index, end_index);`
|
|
86
|
+
- **Confidence**: Confirmed (reproduced)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### BIN-8 `ImmutableObjectPool.add` can delete the bucket it is about to write into
|
|
91
|
+
- **Severity**: MAJOR
|
|
92
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/object/ImmutableObjectPool.js:133-176` (eviction at `:150`, push at `:171`, crash site `:120`)
|
|
93
|
+
- **Defect**: `add` first resolves/creates the per-key bucket array, *then* evicts the LRU head at line 150. If the evicted element has the same key and was the last one in that bucket, `__removeElement` calls `this.data.delete(key)` — removing the very array the local `elements` variable still points at. The new element is then pushed into a detached array and `size++` still runs.
|
|
94
|
+
- **Failure scenario**:
|
|
95
|
+
```js
|
|
96
|
+
const p = new ImmutableObjectPool({ capacity: 1, perKeyCapacity: 10 });
|
|
97
|
+
p.add(k, "v1");
|
|
98
|
+
p.add(k, "v2"); // evicts v1, deletes bucket k, pushes v2 into the detached array
|
|
99
|
+
p.get(k); // undefined (value is unreachable but size === 1)
|
|
100
|
+
p.clear(); // TypeError: Cannot read properties of undefined (reading 'indexOf')
|
|
101
|
+
```
|
|
102
|
+
With the default `capacity: 100` this triggers whenever the pool is full and the oldest element happens to be the sole entry for the key being added — after which that key is permanently unusable and `clear()` throws.
|
|
103
|
+
- **Fix**: perform the eviction *before* resolving/creating the bucket, and re-resolve `elements` afterwards (or have `__removeElement` skip the `data.delete` when the bucket is the one currently being populated).
|
|
104
|
+
- **Confidence**: Confirmed (reproduced)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### BIN-9 `ObservedEnum.hash()` iterates a plain object with `for...of`
|
|
109
|
+
- **Severity**: MAJOR
|
|
110
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/ObservedEnum.js:94`
|
|
111
|
+
- **Defect**: `for (const n of this.__validSet)` — `__validSet` is a plain object (`Object.<string,T>` per the constructor contract), which is not iterable.
|
|
112
|
+
- **Failure scenario**: `new ObservedEnum(7, { ONE: 1, SEVEN: 7 }).hash()` throws `TypeError: this.__validSet is not iterable`. Any `HashMap`/`HashSet`/`Cache` keyed by an `ObservedEnum` crashes on insert.
|
|
113
|
+
- **Fix**: `for (const n in this.__validSet)`.
|
|
114
|
+
- **Confidence**: Confirmed (reproduced)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### BIN-10 `JsonUtils.stringify` writes `undefined` as the *string* `"null"`
|
|
119
|
+
- **Severity**: MAJOR
|
|
120
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/json/JsonUtils.js:62-63`
|
|
121
|
+
- **Defect**: the `"undefined"` case calls `processValue("null")`, which re-enters the `"string"` branch and emits `JSON.stringify("null")` — i.e. `"null"` with quotes — instead of the bare literal `null`.
|
|
122
|
+
- **Failure scenario**: `stringify({ a: undefined })` → `{"a":"null"}`. Round-tripping through `JSON.parse` yields the string `"null"` where `null` was expected; any downstream `=== null` / `typeof` check now sees a string.
|
|
123
|
+
- **Fix**: `addToOutput("null");` directly, without recursing.
|
|
124
|
+
- **Confidence**: Confirmed (reproduced)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### BIN-11 `JsonUtils.stringify` does not escape property names, producing invalid JSON
|
|
129
|
+
- **Severity**: MAJOR
|
|
130
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/json/JsonUtils.js:16`
|
|
131
|
+
- **Defect**: `addToOutput('"' + name + '":')` concatenates the raw key. Values go through `JSON.stringify`, keys do not.
|
|
132
|
+
- **Failure scenario**:
|
|
133
|
+
- `stringify({ 'a"b': 1 })` → `{"a"b":1}` → `JSON.parse` throws `Expected ':' after property name in JSON at position 4`.
|
|
134
|
+
- `stringify({ 'a\nb': 1 })` → contains a literal newline inside the key → `JSON.parse` throws `Bad control character in string literal`.
|
|
135
|
+
Keys containing `"`, `\` or control characters (Windows paths, user-authored ids) produce output that cannot be parsed back.
|
|
136
|
+
- **Fix**: `addToOutput(JSON.stringify(name) + ':');`
|
|
137
|
+
- **Confidence**: Confirmed (reproduced)
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### BIN-12 `write_property` ignores `part_offset`
|
|
142
|
+
- **Severity**: MAJOR
|
|
143
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/object/write_property.js:17`
|
|
144
|
+
- **Defect**: it calls `read_property(root, parts, 0, last_index, true)` — hard-coding offset `0` and passing `last_index` (= `part_offset + part_count - 1`) as the *count*. The correct arguments are offset `part_offset` and count `part_count - 1`. The two coincide only when `part_offset === 0`, which is the only case the spec covers.
|
|
145
|
+
- **Failure scenario**: `write_property({ a: { b: 0 } }, ['x','a','b'], 1, 2, 31)` should set `root.a.b = 31`; instead it starts at `parts[0] === 'x'` and throws `Error: No no property or getter found for 'x' on [0]'x/a'`. With a root that happens to have the extraneous key, it writes to the wrong location instead of throwing.
|
|
146
|
+
- **Fix**: `const thing = read_property(root, parts, part_offset, part_count - 1, true);`
|
|
147
|
+
- **Confidence**: Confirmed (reproduced)
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### BIN-13 `BoundedValue` drops the lower limit in `copy`, JSON and binary serialisation
|
|
152
|
+
- **Severity**: MAJOR
|
|
153
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/BoundedValue.js:156-160` (`copy`), `:185-190` (`toJSON`/`fromJSON`), `:201-215` (binary)
|
|
154
|
+
- **Defect**: `__limitLower` participates in `equals()`, `hash()` and `getFraction()`, but `copy`, `toJSON`/`fromJSON` and `toBinaryBuffer`/`fromBinaryBuffer` all transfer only `__value` and `__limitUpper`.
|
|
155
|
+
- **Failure scenario**:
|
|
156
|
+
```js
|
|
157
|
+
const a = new BoundedValue(0, 10, 0);
|
|
158
|
+
const b = new BoundedValue(5, 10, 2);
|
|
159
|
+
a.copy(b);
|
|
160
|
+
a.equals(b); // false (a.__limitLower === 0, b's === 2)
|
|
161
|
+
a.getFraction(); // 0.5 instead of 0.375
|
|
162
|
+
```
|
|
163
|
+
The same loss occurs across a save/load cycle: a bar with a non-zero floor reloads with floor 0 and renders at the wrong fill fraction.
|
|
164
|
+
- **Fix**: include `__limitLower` in `copy`, in the JSON payload (with a default for old saves) and in the binary form (versioned, since it changes the wire size).
|
|
165
|
+
- **Confidence**: Confirmed (reproduced)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### BIN-14 `ConnectedBoxLayouter.__execute_swaps` scores the same box twice
|
|
170
|
+
- **Severity**: MAJOR
|
|
171
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.js:132`
|
|
172
|
+
- **Defect**: `const b1_cost_after = this.__compute_box_cost(b0);` — copy-paste of the line above; it should score `b1`.
|
|
173
|
+
- **Failure scenario**: for any pair `(b0, b1)`, the accept/reject test at line 134 compares `b0_cost + b1_cost` against `2 * cost_after(b0)` instead of `cost_after(b0) + cost_after(b1)`. Swapping two boxes where b0's cost drops slightly but b1's cost rises sharply is accepted, so `layout()` converges to a worse (and, because `b1_cost` still feeds the left side, non-monotonic) arrangement. Every call to `layout()` / `step()` is affected.
|
|
174
|
+
- **Fix**: `const b1_cost_after = this.__compute_box_cost(b1);`
|
|
175
|
+
- **Confidence**: Confirmed
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### BIN-15 `string_format_kebab_to_underscore` converts only the first separator
|
|
180
|
+
- **Severity**: MAJOR
|
|
181
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/primitives/strings/string_format_kebab_to_underscore.js:7`
|
|
182
|
+
- **Defect**: `string.replace('-', '_')` with a string pattern replaces the first occurrence only.
|
|
183
|
+
- **Failure scenario**: `string_format_kebab_to_underscore("a-b-c")` returns `"a_b-c"`; the documented result is `"a_b_c"`. Any multi-segment kebab identifier (`"top-left-corner"`, `"en-gb-fallback"`) comes out half-converted.
|
|
184
|
+
- **Fix**: `string.replace(/-/g, '_')` (or `replaceAll`).
|
|
185
|
+
- **Confidence**: Confirmed (reproduced)
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### BIN-16 `LocalizationEngine.resolve` cannot read a `LocaleDataset`
|
|
190
|
+
- **Severity**: MAJOR
|
|
191
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/localization/LocalizationEngine.js:61-64`
|
|
192
|
+
- **Defect**: `setLocaleDataset` is documented `@param {LocaleDataset} dataset` and `#locales` is `Object<LocaleDataset>`, but `resolve` indexes the stored object directly (`locale[fragment_key]`). `LocaleDataset` keeps its entries in a `#map` private field and exposes them only through `get(key)` / `hasKey(key)`, so direct indexing is always `undefined`.
|
|
193
|
+
- **Failure scenario**: `engine.setLocaleDataset("en", someLocaleDataset); engine.resolve("en", "greeting")` returns `undefined` even when `someLocaleDataset.get("greeting") === "Hello"`, and the engine then walks the whole fallback chain for nothing. (It only works today because the spec passes plain objects instead of the documented type.)
|
|
194
|
+
- **Fix**: call `locale.get(fragment_key)` / `locale.hasKey(fragment_key)` — and give `LocaleDataset` a way to populate `#map`, which it currently lacks.
|
|
195
|
+
- **Confidence**: Confirmed (reproduced)
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### BIN-17 `compileReactiveToJS` emits `--x` for nested negation
|
|
200
|
+
- **Severity**: MAJOR
|
|
201
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/reactive/js/compileReactiveToJS.js:98-101`
|
|
202
|
+
- **Defect**: `compileNegate` concatenates `-` directly onto the operand with no parentheses or separating space.
|
|
203
|
+
- **Failure scenario**:
|
|
204
|
+
- `ReactiveNegate.from(ReactiveNegate.from(ReactiveReference.from(v,'a')))` compiles to `--this.a`, which is a *decrement* — it mutates `this.a` and yields the decremented value, instead of evaluating to `a`.
|
|
205
|
+
- `ReactiveNegate.from(ReactiveLiteralNumber.from(-5))` compiles to `--5`, a `SyntaxError` when the string is handed to `new Function`.
|
|
206
|
+
- **Fix**: `return \`-( ${compileExpression(exp.source)} )\`;` (matching how the binary compilers already parenthesise). `compileNot` has the same shape but `!!` is harmless.
|
|
207
|
+
- **Confidence**: Confirmed
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### BIN-18 `readArrayLiteral` accepts unterminated arrays
|
|
212
|
+
- **Severity**: MINOR
|
|
213
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/parser/simple/readArrayLiteral.js:39`
|
|
214
|
+
- **Defect**: the parse loop is `while (i < limit)`. When the input ends immediately after a separator (or after the opening bracket), the loop condition simply goes false and the function *returns a token* instead of reaching either the `]` branch or the `Unterminated array literal` throw.
|
|
215
|
+
- **Failure scenario**: `readArrayLiteral("[1,", 0, 3)` returns a valid array token with `value.length === 1`, `end === 3` — no error. `readArrayLiteral("[", 0, 1)` returns an empty array token. Truncated data is accepted as well-formed.
|
|
216
|
+
- **Fix**: track whether the terminator was consumed and throw `Unterminated array literal` when the loop exits without it.
|
|
217
|
+
- **Confidence**: Confirmed (reproduced)
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### BIN-19 `string_compute_byte_size` under-counts UTF-8 bytes
|
|
222
|
+
- **Severity**: MINOR
|
|
223
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/primitives/strings/string_compute_byte_size.js:9-18`
|
|
224
|
+
- **Defect**: the function counts bytes needed to store the *code unit* (`while (c > 0xff) { p++; c >>= 8; }`), not the UTF-8 encoding length. Code points U+0080–U+00FF need 2 UTF-8 bytes but are counted as 1; U+0800–U+FFFF need 3 but are counted as 2.
|
|
225
|
+
- **Failure scenario**: `string_compute_byte_size("\u65E5\u672C")` returns 4, the real UTF-8 size is 6. `string_compute_byte_size("\u00E9")` returns 1, the real size is 2. In-repo it is used as a `Cache` `keyWeigher` (`FunctionCompiler`, `TerrainLayer`, `TooltipParser`), so those caches systematically exceed their configured `maxWeight` for non-ASCII keys.
|
|
226
|
+
- **Fix**: branch on the code point: `<0x80 → 1`, `<0x800 → 2`, surrogate pair → 4 (consume both units), else 3.
|
|
227
|
+
- **Confidence**: Confirmed (reproduced)
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### BIN-20 `readUnsignedIntegerToken` returns 0 instead of erroring at end-of-input
|
|
232
|
+
- **Severity**: MINOR
|
|
233
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/parser/simple/readUnsignedIntegerToken.js:22-63`
|
|
234
|
+
- **Defect**: the "no digits" error is raised inside the loop (`if (i === cursor)`), so when `cursor === length` the loop body never runs and the function returns `Token(0, cursor, cursor)` with no error.
|
|
235
|
+
- **Failure scenario**: `readNumberToken("-", 0, 1)` returns a number token with value `-0` and `end === 1`; `readNumberToken("", 0, 0)` returns `0`. Truncated numeric input parses as zero rather than raising a `ParserError`.
|
|
236
|
+
- **Fix**: after the loop, `if (i === cursor) throw new ParserError(i, 'Expected at least one digit', text);` — mirroring the fix already applied in `readHexToken.js:113`.
|
|
237
|
+
- **Confidence**: Confirmed
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### BIN-21 `skipWhitespace` does not treat `\r` as whitespace
|
|
242
|
+
- **Severity**: MINOR
|
|
243
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/parser/simple/skipWhitespace.js:21`
|
|
244
|
+
- **Defect**: only `' '`, `'\n'` and `'\t'` are skipped; `'\r'` is not.
|
|
245
|
+
- **Failure scenario**: parsing an array literal written with CRLF line endings — `readArrayLiteral("[1,\r\n2]", 0, 7)` — stops at the `\r`, calls `readLiteralToken` on it and throws `ParserError: Expected literal start, but found ''`. Any CRLF-formatted data file fails to parse on Windows.
|
|
246
|
+
- **Fix**: add `|| char === '\r'` (and consider `'\f'`/`'\v'`).
|
|
247
|
+
- **Confidence**: Confirmed
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### BIN-22 `resolvePathByArray` mutates the caller's array and mis-truncates the error hint
|
|
252
|
+
- **Severity**: MINOR
|
|
253
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/json/resolvePathByArray.js:35` and `:15`
|
|
254
|
+
- **Defect**: (a) `parts.shift()` destructively removes the leading empty segment from the caller-owned array; (b) `buildErrorSuggestions` does `options.splice(0, limit)`, which deletes the **first** 10 keys and keeps the rest, then appends `[... N-10 more options]`. The intent was to keep the first 10.
|
|
255
|
+
- **Failure scenario**: for an object with 15 keys the thrown message lists keys 11–15 and claims "5 more options", while actually hiding 10 — a diagnostic that points at the wrong candidates. Separately, `const p = ["", "a", "b"]; resolvePathByArray(o, p);` leaves the caller holding `["a","b"]`.
|
|
256
|
+
- **Fix**: `options.length = limit;` before the push; and skip the leading empty segment with a local start index instead of `shift()`.
|
|
257
|
+
- **Confidence**: Confirmed
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### BIN-23 `ObservedInteger` binary round-trip turns ±(2^31−1 / −2^31) into ±Infinity
|
|
262
|
+
- **Severity**: MINOR
|
|
263
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/ObservedInteger.js:173-199`
|
|
264
|
+
- **Defect**: `2147483647` and `-2147483648` are used as in-band sentinels for `±Infinity`, and `toBinaryBuffer` writes finite values of exactly those magnitudes unchanged (the source even carries a `//TODO` acknowledging it).
|
|
265
|
+
- **Failure scenario**: `v.set(2147483647); v.toBinaryBuffer(b); b.position = 0; v2.fromBinaryBuffer(b);` → `v2.getValue() === Infinity`. Any stat/counter that legitimately reaches `2^31-1` becomes infinite after a save/load cycle.
|
|
266
|
+
- **Fix**: write a one-byte tag (finite / +inf / -inf) before the int32, or clamp finite writes to `±2147483646`.
|
|
267
|
+
- **Confidence**: Confirmed
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### BIN-24 `LanguageMetadata.fromJSON` throws on an unknown locale and never sets `locale`
|
|
272
|
+
- **Severity**: MINOR
|
|
273
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/localization/LanguageMetadata.js:28-39`; caller `Localization.js:165`
|
|
274
|
+
- **Defect**: (a) the instance `fromJSON` destructures its argument, so `undefined` throws; the caller does `LanguageMetadata.fromJSON(languages_metadata[locale])` from inside a `.then` success handler, where the sibling rejection handler cannot catch it. (b) `fromJSON` never assigns `this.locale`, even though `LocalizationEngine.addLanguageMetadata` keys the registry by `metadata.locale`.
|
|
275
|
+
- **Failure scenario**: `loadLocale('zh-cn')` against the shipped `app/data/database/text/languages.json` (which contains only `en-gb` and `ru`) rejects with `TypeError: Cannot destructure property 'reading_speed' of 'undefined'` instead of falling back to `DEFAULT_LANGUAGE_METADATA`. Separately, every metadata object produced by `fromJSON` registers under the empty-string key, so `LocalizationEngine`'s fallback chain never finds it.
|
|
276
|
+
- **Fix**: default the parameter (`fromJSON(j = {})`) or check for `undefined` at the call site; and pass/assign the locale key.
|
|
277
|
+
- **Confidence**: Confirmed
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### BIN-25 Reactive literals share a module-level `dummySignal`, leaking every expression that connects to one
|
|
282
|
+
- **Severity**: MINOR
|
|
283
|
+
- **File**: `ReactiveLiteralNumber.js:7,20`, `ReactiveLiteralBoolean.js:6,19`, `ReactiveLiteralString.js:7,16` (all under `core/model/reactive/model/terminal/`)
|
|
284
|
+
- **Defect**: each literal instance assigns the same module-scoped `Signal` to `this.onChanged`. `ReactiveBinaryExpression.connect` / `ReactiveUnaryExpression.connect` then register `(this.update, this)` on that shared signal. The handler entry holds a strong reference to the parent expression, and the signal lives for the lifetime of the module.
|
|
285
|
+
- **Failure scenario**: build and drop expressions containing literals without calling `disconnect()` — e.g. `for (let i = 0; i < 100000; i++) ReactiveAdd.from(ReactiveLiteralNumber.from(1), ReactiveLiteralNumber.from(2));` — all 100 000 `ReactiveAdd` instances remain reachable from the global `dummySignal` and are never collected. `compileReactiveExpression` produces literal-bearing trees on the common path.
|
|
286
|
+
- **Fix**: skip registration when the source signal is the shared dummy (e.g. expose `isConstant` on terminals and have `connect` not subscribe), or give each literal its own lazily-created signal.
|
|
287
|
+
- **Confidence**: Confirmed
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### BIN-26 `BoundedValue.setLowerLimit` dispatches `onChanged` with no arguments
|
|
292
|
+
- **Severity**: MINOR
|
|
293
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/BoundedValue.js:86`
|
|
294
|
+
- **Defect**: every other mutator sends `send4(value, upperLimit, oldValue, oldUpperLimit)`; `setLowerLimit` calls `this.onChanged.dispatch()` with zero arguments.
|
|
295
|
+
- **Failure scenario**: `bv.onChanged.add((value, limit) => bar.width = value / limit); bv.setLowerLimit(5);` → the handler receives `(undefined, undefined)` and sets `bar.width = NaN`, whereas the same handler works for `setValue`/`setUpperLimit`.
|
|
296
|
+
- **Fix**: `this.onChanged.send4(this.__value, this.__limitUpper, this.__value, this.__limitUpper);` (and, per the existing TODO, widen the signature to carry the lower limit).
|
|
297
|
+
- **Confidence**: Confirmed
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
### BIN-27 `number_format_by_thousands` inserts separators inside the fractional part
|
|
302
|
+
- **Severity**: MINOR
|
|
303
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/primitives/numbers/number_format_by_thousands.js:13`
|
|
304
|
+
- **Defect**: the regex `\B(?=(\d{3})+(?!\d))` is applied to the whole `toString()` output, including digits after the decimal point.
|
|
305
|
+
- **Failure scenario**: `number_format_by_thousands(1234.5678)` returns `"1,234.5,678"` instead of `"1,234.5678"`. (The in-repo caller `number_pretty_print` always passes an integer, so this only bites external/direct callers of this public util.)
|
|
306
|
+
- **Fix**: split on `.` and apply the grouping regex only to the integer part.
|
|
307
|
+
- **Confidence**: Confirmed
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### BIN-28 `ExpressionNode.computeMaxParentScore` never computes a max
|
|
312
|
+
- **Severity**: MINOR
|
|
313
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js:65-85` (bad call at `:77`)
|
|
314
|
+
- **Defect**: two errors in one function. (a) `result = max2(parent.computeMaxParentScore(scoringFunction))` calls the 2-argument `max2` with one argument — `max2(a, undefined)` evaluates `a < undefined` (false) and returns `a`, so the loop just overwrites `result` with the *last* parent's score rather than the maximum. (b) the `n === 0` branch `return`s before `this.__max_parent_score = result`, so root nodes always report a score of 0 from `getScore()`.
|
|
315
|
+
- **Failure scenario**: `evaluator.build([exprA, exprB])` where `exprA`/`exprB` share sub-expressions: every node's `getScore()` returns `0` (roots never assign; children are never visited because `build` only invokes the method on parentless roots). Any future ordering that reads `getScore()` gets a constant.
|
|
316
|
+
- **Fix**: `result = max2(result, parent.computeMaxParentScore(scoringFunction));` and assign `__max_parent_score` on the `n === 0` path before returning.
|
|
317
|
+
- **Confidence**: Confirmed
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### BIN-29 `Stat.Process.ROUND_DOWN` truncates toward zero and wraps at 2^31
|
|
322
|
+
- **Severity**: MINOR
|
|
323
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/stat/Stat.js:302-304`
|
|
324
|
+
- **Defect**: `v | 0` is an int32 truncation, not a floor.
|
|
325
|
+
- **Failure scenario**: a stat driven negative by a debuff — `postprocess = Stat.Process.ROUND_DOWN`, computed value `-2.5` → `-2`, whereas "round down" is `-3`; the stat reads one point *higher* than intended. And a value above 2^31 (e.g. a score of 3 000 000 000) becomes `-1294967296`.
|
|
326
|
+
- **Fix**: `Math.floor(v)`.
|
|
327
|
+
- **Confidence**: Confirmed
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
### BIN-30 `ObservedValue.equals(null)` throws
|
|
332
|
+
- **Severity**: MINOR
|
|
333
|
+
- **File**: `H:/git/moh/app/src/mir-engine/meep/src/core/model/ObservedValue.js:63`
|
|
334
|
+
- **Defect**: the guard `typeof other === 'object'` is clearly meant to reject non-objects, but `typeof null === 'object'`, so `null` passes and `other.__value` dereferences it.
|
|
335
|
+
- **Failure scenario**: `new ObservedValue(1).equals(null)` throws `TypeError: Cannot read properties of null (reading '__value')` instead of returning `false` — which the guard's own existence shows was the intent. Every sibling class (`ObservedBoolean`, `ObservedString`, `ObservedInteger`) has no guard at all and is documented to take an instance, so this one is specifically claiming to handle the case.
|
|
336
|
+
- **Fix**: `return other !== null && typeof other === 'object' && this.__value === other.__value;`
|
|
337
|
+
- **Confidence**: Confirmed
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Areas reviewed and found sound (no findings)
|
|
342
|
+
|
|
343
|
+
- `BinaryBuffer` integer/float read/write pairs, `readUint24`/`writeUint24` (LE and BE), varint encode/decode across 0…2^31−1, `writeBytes`/`readBytes`, `writeASCIIString`.
|
|
344
|
+
- `EncodingBinaryBuffer` string-interning: the `>>> 1` unsigned shift and the odd low-byte discriminator are correct, and the `BiMap` key/value orientation is self-consistent in both directions.
|
|
345
|
+
- `to_half_float_uint16` / `half_to_float_uint16` (including NaN/±Inf/denormal/max-normal paths), `lsb_32` (the de Bruijn multiply is exact because the operand is always a power of two), `msb_32`, `ctz32`, `bitCount`, `reverse_bits_uint32`, `split_by_2`/`split_by_3`, `Base64` (matches upstream base64-js), `base58_encode`, `SmallFloat`, `OffsetAllocator` (faithful port of sebbbi/OffsetAllocator, including the freelist stack discipline).
|
|
346
|
+
- `BitImage2`: `canBlit`'s cross-word guard is provably in-bounds; `or_blit`, `dilate`, `grow_preserving`, `copy_transposed` are correct.
|
|
347
|
+
- `BitSet` core (`nextSetBit`, `nextClearBit`, `previousSetBit`, `cardinality`, `copy`, `reset`, resize/shrink) — the `setRange` inclusive / `clearRange` exclusive asymmetry is real but every in-repo caller compensates for it, and only the JSDoc is contradictory.
|
|
348
|
+
- `path/*` (behaviour is pinned by specs), `codegen/*`, `objectDeepEquals`, `object_deep_clone`, `compareValues`, `ObjectPoolFactory`, `ModuleRegistry`, `Connection`/`Port`/`NodeInstancePortReference`, `serializeNodeGraphToJSON` / `deserializeNodeGraphFromJSON`, `MultiPredicateEvaluator.__resolve_node` short-circuit logic, `inferReactiveExpressionTypes`, `ReactivePegCompiler`, `AbstractCachingParser`, `readStringToken` (the `length - 1` loop bound is correct for every terminator position), `readHexToken`, `readIdentifierToken`, `readReferenceToken`, `string_jaro_distance`/`string_jaro_winkler`, `computeStringHash`, `number_pretty_print`.
|