@woosh/meep-engine 2.169.0 → 2.169.2
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/editor/ecs/component/editors/NumericIntervalEditor.js +18 -35
- package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -43
- package/editor/ecs/component/editors/ObservedStringEditor.js +34 -51
- package/editor/ecs/component/editors/common/makeV3_editor.js +12 -30
- package/editor/ecs/component/editors/common/two_way_sync.js +50 -0
- package/editor/ecs/component/editors/geom/QuaternionEditor.js +20 -35
- package/editor/ecs/component/editors/geom/Vector1Editor.js +5 -28
- package/editor/ecs/component/editors/primitive/NumberEditor.js +15 -31
- package/editor/process/symbolic/make3DSymbolicDisplay.js +1 -1
- package/editor/tools/paint/TerrainHeightPaintTool.js +22 -31
- package/editor/tools/paint/TerrainPaintTool.js +85 -13
- package/editor/tools/paint/TerrainTexturePaintTool.js +18 -29
- package/editor/view/ecs/EntityList.js +1 -1
- package/editor/view/ecs/components/TerrainController.js +1 -4
- package/editor/view/ecs/components/particles/ParticleLayerController.js +1 -1
- package/editor/view/makeEntityDecorators.js +1 -1
- package/package.json +1 -1
- package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -0
- package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -0
- package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -0
- package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -0
- package/src/REVIEW_2026_08_06.md +39 -0
- package/src/core/IdPool.d.ts +2 -2
- package/src/core/IdPool.d.ts.map +1 -1
- package/src/core/IdPool.js +1 -1
- package/src/core/binary/32BitEncoder.d.ts +2 -3
- package/src/core/binary/32BitEncoder.d.ts.map +1 -1
- package/src/core/binary/32BitEncoder.js +1 -1
- package/src/core/bvh2/bvh3/BVH.d.ts +2 -2
- package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/BVH.js +1 -1
- package/src/core/bvh2/bvh3/query/bvh_collect_user_data.js +2 -2
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
- package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_aabb.js +2 -2
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +2 -2
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_frustum.js +2 -2
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +2 -2
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point_filtered.d.ts +2 -2
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point_filtered.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point_filtered.js +86 -86
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
- package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +2 -2
- package/src/core/bvh2/traversal/aabb3_detailed_volume_intersection_callback_based.d.ts +2 -2
- package/src/core/bvh2/traversal/aabb3_detailed_volume_intersection_callback_based.d.ts.map +1 -1
- package/src/core/bvh2/traversal/aabb3_detailed_volume_intersection_callback_based.js +1 -1
- package/src/core/bvh8/aabb3_round_to_float32.d.ts +16 -0
- package/src/core/bvh8/aabb3_round_to_float32.d.ts.map +1 -0
- package/src/core/bvh8/aabb3_round_to_float32.js +22 -0
- package/src/core/bvh8/bvh8_geometry_validate.d.ts.map +1 -1
- package/src/core/bvh8/bvh8_geometry_validate.js +1 -12
- package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts.map +1 -1
- package/src/core/bvh8/bvh8_geometry_validate_indirect.js +1 -12
- package/src/core/cache/Cache.d.ts +17 -4
- package/src/core/cache/Cache.d.ts.map +1 -1
- package/src/core/cache/Cache.js +16 -3
- package/src/core/cache/LoadingCache.d.ts +1 -1
- package/src/core/cache/LoadingCache.js +2 -2
- package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts +2 -2
- package/src/core/cache/wtinylfu/CacheWTinylfu.js +2 -2
- package/src/core/collection/array/array_index_by_equality.d.ts +2 -2
- package/src/core/collection/array/array_index_by_equality.d.ts.map +1 -1
- package/src/core/collection/array/array_index_by_equality.js +1 -1
- package/src/core/collection/array/array_pick_best_element.d.ts +2 -2
- package/src/core/collection/array/array_pick_best_element.d.ts.map +1 -1
- package/src/core/collection/array/array_pick_best_element.js +1 -1
- package/src/core/collection/array/array_pick_best_elements.d.ts +2 -2
- package/src/core/collection/array/array_pick_best_elements.d.ts.map +1 -1
- package/src/core/collection/array/array_pick_best_elements.js +1 -1
- package/src/core/collection/array/array_set_diff.d.ts +2 -3
- package/src/core/collection/array/array_set_diff.d.ts.map +1 -1
- package/src/core/collection/array/array_set_diff.js +1 -1
- package/src/core/collection/array/array_set_diff_sorting.d.ts +2 -2
- package/src/core/collection/array/array_set_diff_sorting.d.ts.map +1 -1
- package/src/core/collection/array/array_set_diff_sorting.js +1 -1
- package/src/core/collection/array/iterator/ArrayIteratorRandom.d.ts +2 -1
- package/src/core/collection/array/iterator/ArrayIteratorRandom.d.ts.map +1 -1
- package/src/core/collection/heap/BinaryHeap.d.ts +2 -2
- package/src/core/collection/heap/BinaryHeap.d.ts.map +1 -1
- package/src/core/collection/heap/BinaryHeap.js +1 -1
- package/src/core/collection/list/List.d.ts +19 -19
- package/src/core/collection/list/List.d.ts.map +1 -1
- package/src/core/collection/list/List.js +10 -10
- package/src/core/collection/map/HashMap.d.ts +2 -2
- package/src/core/collection/map/HashMap.d.ts.map +1 -1
- package/src/core/collection/map/HashMap.js +1 -1
- package/src/core/collection/set/ArraySet.d.ts +2 -2
- package/src/core/collection/set/ArraySet.d.ts.map +1 -1
- package/src/core/collection/set/ArraySet.js +1 -1
- package/src/core/collection/table/RowFirstTable.d.ts +2 -2
- package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
- package/src/core/collection/table/RowFirstTable.js +1 -1
- package/src/core/collection/table/RowFirstTableSpec.js +4 -4
- package/src/core/color/operations/color_darken.d.ts +5 -2
- package/src/core/color/operations/color_darken.d.ts.map +1 -1
- package/src/core/color/operations/color_darken.js +24 -33
- package/src/core/color/operations/color_desaturate.d.ts +5 -2
- package/src/core/color/operations/color_desaturate.d.ts.map +1 -1
- package/src/core/color/operations/color_desaturate.js +24 -33
- package/src/core/color/operations/color_lighten.d.ts +5 -2
- package/src/core/color/operations/color_lighten.d.ts.map +1 -1
- package/src/core/color/operations/color_lighten.js +24 -33
- package/src/core/color/operations/color_saturate.d.ts +5 -2
- package/src/core/color/operations/color_saturate.d.ts.map +1 -1
- package/src/core/color/operations/color_saturate.js +24 -33
- package/src/core/color/operations/color_scale_okhsv_channel.d.ts +28 -0
- package/src/core/color/operations/color_scale_okhsv_channel.d.ts.map +1 -0
- package/src/core/color/operations/color_scale_okhsv_channel.js +45 -0
- package/src/core/fsm/simple/SimpleStateMachineDescription.d.ts +2 -2
- package/src/core/fsm/simple/SimpleStateMachineDescription.d.ts.map +1 -1
- package/src/core/fsm/simple/SimpleStateMachineDescription.js +1 -1
- package/src/core/geom/2d/bvh/BVH2D.d.ts +2 -2
- package/src/core/geom/2d/bvh/BVH2D.d.ts.map +1 -1
- package/src/core/geom/2d/bvh/BVH2D.js +1 -1
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -1
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +12 -0
- package/src/core/geom/2d/line/segment2_intersects_segment2_2d.d.ts.map +1 -1
- package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -29
- package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.d.ts.map +1 -1
- package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -51
- package/src/core/geom/2d/line/segment2_segment_intersection_fractions.d.ts +31 -0
- package/src/core/geom/2d/line/segment2_segment_intersection_fractions.d.ts.map +1 -0
- package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -0
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.d.ts +6 -1
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -65
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.d.ts +15 -5
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -70
- package/src/core/geom/2d/quad-tree/qt_query_data_raycast.d.ts +2 -2
- package/src/core/geom/2d/quad-tree/qt_query_data_raycast.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_raycast.js +1 -1
- package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts.map +1 -1
- package/src/core/geom/2d/r-tree/StaticR2Tree.js +7 -3
- package/src/core/geom/3d/aabb/AABB3.d.ts +2 -2
- package/src/core/geom/3d/aabb/AABB3.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/AABB3.js +1 -1
- package/src/core/geom/3d/aabb/aabb3_intersects_ray_segment.d.ts +7 -8
- package/src/core/geom/3d/aabb/aabb3_intersects_ray_segment.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_intersects_ray_segment.js +15 -51
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.d.ts.map +1 -1
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +13 -14
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.d.ts.map +1 -1
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +2 -17
- package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.d.ts +22 -0
- package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.d.ts.map +1 -0
- package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.d.ts +19 -0
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.d.ts.map +1 -1
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +5 -9
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.d.ts.map +1 -1
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +4 -12
- package/src/core/geom/3d/frustum/read_frustum_corner.d.ts +5 -0
- package/src/core/geom/3d/frustum/read_frustum_corner.d.ts.map +1 -1
- package/src/core/geom/3d/frustum/read_frustum_corner.js +8 -5
- package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.d.ts +33 -0
- package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.d.ts.map +1 -0
- package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -0
- package/src/core/geom/3d/mat4/m4_compose.d.ts +5 -0
- package/src/core/geom/3d/mat4/m4_compose.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_compose.js +22 -53
- package/src/core/geom/3d/mat4/m4_decompose.d.ts +8 -0
- package/src/core/geom/3d/mat4/m4_decompose.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_decompose.js +84 -76
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.d.ts +2 -0
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +10 -44
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.d.ts +27 -0
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -0
- package/src/core/geom/3d/plane/orient3d_fast.d.ts.map +1 -1
- package/src/core/geom/3d/plane/orient3d_fast.js +2 -22
- package/src/core/geom/3d/plane/orient3d_fast_point.d.ts +28 -0
- package/src/core/geom/3d/plane/orient3d_fast_point.d.ts.map +1 -0
- package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -0
- package/src/core/geom/3d/quaternion/quat3_nlerp.d.ts +48 -0
- package/src/core/geom/3d/quaternion/quat3_nlerp.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -0
- package/src/core/geom/3d/shape/AbstractShape3D.d.ts +9 -0
- package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/AbstractShape3D.js +44 -1
- package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/BoxShape3D.js +2 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +2 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +0 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +25 -56
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +2 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +3 -5
- package/src/core/geom/3d/shape/MeshShape3D.d.ts +0 -11
- package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/MeshShape3D.js +27 -106
- package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -1
- package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/Triangle3D.js +10 -31
- package/src/core/geom/3d/tetrahedra/TetrahedralMesh.d.ts +2 -2
- package/src/core/geom/3d/tetrahedra/TetrahedralMesh.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/TetrahedralMesh.js +1 -1
- package/src/core/geom/3d/tetrahedra/delaunay/debug/push_boundary_with_validation.d.ts +2 -5
- package/src/core/geom/3d/tetrahedra/delaunay/debug/push_boundary_with_validation.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/delaunay/debug/push_boundary_with_validation.js +1 -1
- package/src/core/geom/3d/tetrahedra/delaunay/debug/validate_cavity_boundary.d.ts +2 -2
- package/src/core/geom/3d/tetrahedra/delaunay/debug/validate_cavity_boundary.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/delaunay/debug/validate_cavity_boundary.js +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +8 -57
- package/src/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.d.ts +2 -3
- package/src/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +69 -69
- package/src/core/geom/3d/tetrahedra/validate_tetrahedron_neighbourhood.d.ts +2 -3
- package/src/core/geom/3d/tetrahedra/validate_tetrahedron_neighbourhood.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/validate_tetrahedron_neighbourhood.js +50 -50
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts +8 -1
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +281 -272
- package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadrics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadrics.js +3 -4
- package/src/core/geom/3d/topology/simplify/quadratic/quadric3_set_from_plane_through_point.d.ts +22 -0
- package/src/core/geom/3d/topology/simplify/quadratic/quadric3_set_from_plane_through_point.d.ts.map +1 -0
- package/src/core/geom/3d/topology/simplify/quadratic/quadric3_set_from_plane_through_point.js +27 -0
- package/src/core/geom/3d/topology/struct/TopoEdge.d.ts +2 -2
- package/src/core/geom/3d/topology/struct/TopoEdge.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/TopoEdge.js +1 -1
- package/src/core/geom/3d/topology/struct/TopoMesh.d.ts +2 -3
- package/src/core/geom/3d/topology/struct/TopoMesh.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/TopoMesh.js +1 -1
- package/src/core/geom/3d/topology/struct/TopoTriangle.d.ts +2 -2
- package/src/core/geom/3d/topology/struct/TopoTriangle.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/TopoTriangle.js +1 -1
- package/src/core/geom/3d/topology/struct/TopoVertex.d.ts +2 -2
- package/src/core/geom/3d/topology/struct/TopoVertex.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/TopoVertex.js +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.d.ts +24 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +2 -53
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +3 -4
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +3 -33
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +4 -37
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +3 -32
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.d.ts +32 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -728
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path_polyanya.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path_polyanya.js +13 -24
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +8 -54
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.d.ts +35 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -0
- package/src/core/geom/3d/topology/util/mesh_flood_fill.d.ts +1 -1
- package/src/core/geom/3d/topology/util/mesh_flood_fill.js +1 -1
- package/src/core/geom/3d/triangle/tri3_closest_point.d.ts +29 -0
- package/src/core/geom/3d/triangle/tri3_closest_point.d.ts.map +1 -0
- package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -0
- package/src/core/geom/3d/triangle/tri3_flatten_isometric.d.ts +42 -0
- package/src/core/geom/3d/triangle/tri3_flatten_isometric.d.ts.map +1 -0
- package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -0
- package/src/core/geom/3d/util/aabb3_emit_point_grid.d.ts +2 -2
- package/src/core/geom/3d/util/aabb3_emit_point_grid.d.ts.map +1 -1
- package/src/core/geom/3d/util/aabb3_emit_point_grid.js +1 -1
- package/src/core/geom/Quaternion.d.ts +2 -2
- package/src/core/geom/Quaternion.d.ts.map +1 -1
- package/src/core/geom/Quaternion.js +1 -1
- package/src/core/geom/Vector1.d.ts +2 -2
- package/src/core/geom/Vector1.d.ts.map +1 -1
- package/src/core/geom/Vector1.js +1 -1
- package/src/core/geom/Vector2.d.ts.map +1 -1
- package/src/core/geom/Vector2.js +2 -1
- package/src/core/geom/packing/max-rect/find_best_container.d.ts +2 -2
- package/src/core/geom/packing/max-rect/find_best_container.d.ts.map +1 -1
- package/src/core/geom/packing/max-rect/find_best_container.js +1 -1
- package/src/core/geom/random/random_orthonormal_basis.d.ts +21 -0
- package/src/core/geom/random/random_orthonormal_basis.d.ts.map +1 -0
- package/src/core/geom/random/random_orthonormal_basis.js +31 -0
- package/src/core/geom/vec2/v2_rotate.d.ts +18 -0
- package/src/core/geom/vec2/v2_rotate.d.ts.map +1 -0
- package/src/core/geom/vec2/v2_rotate.js +21 -0
- package/src/core/geom/vec2/v2_rotate_cos_sin.d.ts +19 -0
- package/src/core/geom/vec2/v2_rotate_cos_sin.d.ts.map +1 -0
- package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -0
- package/src/core/geom/vec3/v3_lerp.d.ts +1 -1
- package/src/core/geom/vec3/v3_lerp.js +1 -1
- package/src/core/graph/csr/csr_graph_from_undirected_edge_list.d.ts +30 -0
- package/src/core/graph/csr/csr_graph_from_undirected_edge_list.d.ts.map +1 -0
- package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -0
- package/src/core/graph/layout/CircleLayout.d.ts.map +1 -1
- package/src/core/graph/layout/CircleLayout.js +6 -29
- package/src/core/graph/layout/box/BoxLayouter.d.ts.map +1 -1
- package/src/core/graph/layout/box/BoxLayouter.js +2 -26
- package/src/core/graph/layout/box/position_box_next_to_box.d.ts.map +1 -1
- package/src/core/graph/layout/box/position_box_next_to_box.js +2 -1
- package/src/core/graph/layout/computeDisconnectedSubGraphs.d.ts.map +1 -1
- package/src/core/graph/layout/computeDisconnectedSubGraphs.js +14 -3
- package/src/core/graph/layout/graph_peel_topological_layer.d.ts +25 -0
- package/src/core/graph/layout/graph_peel_topological_layer.d.ts.map +1 -0
- package/src/core/graph/layout/graph_peel_topological_layer.js +45 -0
- package/src/core/json/abstractJSONSerializer.d.ts +2 -2
- package/src/core/json/abstractJSONSerializer.d.ts.map +1 -1
- package/src/core/json/abstractJSONSerializer.js +1 -1
- package/src/core/lang/reactive/AbstractCachingParser.d.ts +2 -3
- package/src/core/lang/reactive/AbstractCachingParser.d.ts.map +1 -1
- package/src/core/lang/reactive/AbstractCachingParser.js +1 -1
- package/src/core/localization/Localization.d.ts +2 -2
- package/src/core/localization/Localization.d.ts.map +1 -1
- package/src/core/localization/Localization.js +1 -1
- package/src/core/math/hash/hash_mix2.d.ts +21 -0
- package/src/core/math/hash/hash_mix2.d.ts.map +1 -0
- package/src/core/math/hash/hash_mix2.js +22 -0
- package/src/core/math/hash/hash_mix3.d.ts +16 -0
- package/src/core/math/hash/hash_mix3.d.ts.map +1 -0
- package/src/core/math/hash/hash_mix3.js +19 -0
- package/src/core/math/lookup/LUT_HEATMAP.d.ts +17 -0
- package/src/core/math/lookup/LUT_HEATMAP.d.ts.map +1 -0
- package/src/core/math/lookup/LUT_HEATMAP.js +30 -0
- package/src/core/math/noise/create_simplex_noise_2d.d.ts +2 -2
- package/src/core/math/noise/create_simplex_noise_2d.d.ts.map +1 -1
- package/src/core/math/noise/create_simplex_noise_2d.js +1 -1
- package/src/core/math/noise/curl_noise_3d.d.ts +6 -0
- package/src/core/math/noise/curl_noise_3d.d.ts.map +1 -1
- package/src/core/math/noise/curl_noise_3d.js +18 -27
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD.js +1 -1
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.d.ts +3 -3
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.d.ts.map +1 -1
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.js +2 -2
- package/src/core/math/random/seededRandom_Mulberry32.d.ts +3 -2
- package/src/core/math/random/seededRandom_Mulberry32.d.ts.map +1 -1
- package/src/core/math/random/seededRandom_Mulberry32.js +1 -1
- package/src/core/math/statistics/hammersley_sequence.d.ts +28 -16
- package/src/core/math/statistics/hammersley_sequence.d.ts.map +1 -1
- package/src/core/math/statistics/hammersley_sequence.js +53 -34
- package/src/core/model/ModuleRegistry.d.ts +2 -2
- package/src/core/model/ModuleRegistry.d.ts.map +1 -1
- package/src/core/model/ModuleRegistry.js +1 -1
- package/src/core/model/node-graph/json/serializeNodeGraphToJSON.d.ts +1 -1
- package/src/core/model/node-graph/json/serializeNodeGraphToJSON.js +2 -2
- package/src/core/model/object/objectDeepEquals.d.ts +2 -2
- package/src/core/model/object/objectDeepEquals.d.ts.map +1 -1
- package/src/core/model/object/objectDeepEquals.js +1 -1
- package/src/core/model/reactive/model/ReactiveExpression.d.ts +2 -2
- package/src/core/model/reactive/model/ReactiveExpression.d.ts.map +1 -1
- package/src/core/model/reactive/model/ReactiveExpression.js +1 -1
- package/src/core/model/validate_enum_schema.d.ts +2 -3
- package/src/core/model/validate_enum_schema.d.ts.map +1 -1
- package/src/core/model/validate_enum_schema.js +1 -1
- package/src/core/process/Future.d.ts +3 -3
- package/src/core/process/Future.d.ts.map +1 -1
- package/src/core/process/Future.js +1 -1
- package/src/core/process/task/Task.d.ts +3 -3
- package/src/core/process/task/Task.d.ts.map +1 -1
- package/src/core/process/task/Task.js +2 -2
- package/src/core/process/task/util/countTask.d.ts +2 -2
- package/src/core/process/task/util/countTask.d.ts.map +1 -1
- package/src/core/process/task/util/countTask.js +1 -1
- package/src/core/process/task/util/randomCountTask.d.ts +2 -2
- package/src/core/process/task/util/randomCountTask.d.ts.map +1 -1
- package/src/core/process/task/util/randomCountTask.js +1 -1
- package/src/core/science/units/MEASUREMENT_UNITS_SI.d.ts +3 -0
- package/src/core/science/units/MEASUREMENT_UNITS_SI.d.ts.map +1 -1
- package/src/core/science/units/MEASUREMENT_UNITS_SI.js +5 -16
- package/src/engine/animation/Animations.d.ts +4 -4
- package/src/engine/animation/Animations.d.ts.map +1 -1
- package/src/engine/animation/Animations.js +2 -2
- package/src/engine/animation/EntityAnimation.d.ts +5 -5
- package/src/engine/animation/EntityAnimation.d.ts.map +1 -1
- package/src/engine/animation/EntityAnimation.js +2 -2
- package/src/engine/animation/TransitionFunctions.d.ts +30 -2
- package/src/engine/animation/TransitionFunctions.d.ts.map +1 -1
- package/src/engine/animation/TransitionFunctions.js +45 -1
- package/src/engine/animation/Tween.d.ts +4 -4
- package/src/engine/animation/Tween.d.ts.map +1 -1
- package/src/engine/animation/Tween.js +2 -2
- package/src/engine/animation/curve/animation_curve_binary_codec.d.ts +29 -0
- package/src/engine/animation/curve/animation_curve_binary_codec.d.ts.map +1 -0
- package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -0
- package/src/engine/animation/curve/draw/canvas_point_to_curve.d.ts +16 -0
- package/src/engine/animation/curve/draw/canvas_point_to_curve.d.ts.map +1 -0
- package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -0
- package/src/engine/animation/curve/draw/curve_point_to_canvas.d.ts +27 -0
- package/src/engine/animation/curve/draw/curve_point_to_canvas.d.ts.map +1 -0
- package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -0
- package/src/engine/animation/curve/draw/position_canvas_to_curve.d.ts +2 -0
- package/src/engine/animation/curve/draw/position_canvas_to_curve.d.ts.map +1 -1
- package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -24
- package/src/engine/animation/curve/draw/position_curve_to_canvas.d.ts +2 -0
- package/src/engine/animation/curve/draw/position_curve_to_canvas.d.ts.map +1 -1
- package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -25
- package/src/engine/animation/curve/editor/canvas2dDrawWorldAxisLabels.d.ts.map +1 -1
- package/src/engine/animation/curve/editor/canvas2dDrawWorldAxisLabels.js +3 -2
- package/src/engine/animation/curve/editor/canvas2dPlotCurvePoints.d.ts.map +1 -1
- package/src/engine/animation/curve/editor/canvas2dPlotCurvePoints.js +14 -14
- package/src/engine/animation/removeEntityWithMeshParticlesEffect.d.ts.map +1 -1
- package/src/engine/animation/removeEntityWithMeshParticlesEffect.js +5 -0
- package/src/engine/asset/AssetManager.d.ts +6 -6
- package/src/engine/asset/AssetManager.d.ts.map +1 -1
- package/src/engine/asset/AssetManager.js +1411 -1411
- package/src/engine/asset/AssetRequest.d.ts +4 -4
- package/src/engine/asset/AssetRequest.d.ts.map +1 -1
- package/src/engine/asset/AssetRequest.js +3 -3
- package/src/engine/asset/loaders/AssetLoader.d.ts +3 -3
- package/src/engine/asset/loaders/AssetLoader.d.ts.map +1 -1
- package/src/engine/asset/loaders/AssetLoader.js +2 -2
- package/src/engine/asset/loaders/GLTFAssetLoader.d.ts.map +1 -1
- package/src/engine/asset/loaders/GLTFAssetLoader.js +25 -51
- package/src/engine/asset/loaders/material/StaticMaterialCache.d.ts +3 -2
- package/src/engine/asset/loaders/material/StaticMaterialCache.d.ts.map +1 -1
- package/src/engine/asset/loaders/material/StaticMaterialCache.js +16 -55
- package/src/engine/asset/loaders/material/TextureCoalescer.d.ts +32 -0
- package/src/engine/asset/loaders/material/TextureCoalescer.d.ts.map +1 -0
- package/src/engine/asset/loaders/material/TextureCoalescer.js +68 -0
- package/src/engine/asset/loaders/material/traverseMaterialTextures.d.ts +10 -2
- package/src/engine/asset/loaders/material/traverseMaterialTextures.d.ts.map +1 -1
- package/src/engine/asset/loaders/material/traverseMaterialTextures.js +10 -2
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +0 -10
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.js +10 -26
- package/src/engine/ecs/EntityComponentDataset.d.ts +6 -6
- package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
- package/src/engine/ecs/EntityComponentDataset.js +3 -3
- package/src/engine/ecs/EntityManager.d.ts +4 -4
- package/src/engine/ecs/EntityManager.js +4 -4
- package/src/engine/ecs/EntityObserver.d.ts +3 -3
- package/src/engine/ecs/EntityObserver.d.ts.map +1 -1
- package/src/engine/ecs/EntityObserver.js +2 -2
- package/src/engine/ecs/System.d.ts +4 -4
- package/src/engine/ecs/System.d.ts.map +1 -1
- package/src/engine/ecs/System.js +2 -2
- package/src/engine/ecs/dynamic_actions/DynamicActorSystem.d.ts +2 -3
- package/src/engine/ecs/dynamic_actions/DynamicActorSystem.d.ts.map +1 -1
- package/src/engine/ecs/dynamic_actions/DynamicActorSystem.js +1 -1
- package/src/engine/ecs/dynamic_actions/actions/definition/WeightedRandomActionDescription.d.ts.map +1 -1
- package/src/engine/ecs/dynamic_actions/actions/definition/WeightedRandomActionDescription.js +5 -0
- package/src/engine/ecs/evaluation/complex/PointFitnessGaussianBlur.d.ts +22 -2
- package/src/engine/ecs/evaluation/complex/PointFitnessGaussianBlur.d.ts.map +1 -1
- package/src/engine/ecs/evaluation/complex/PointFitnessGaussianBlur.js +92 -41
- package/src/engine/ecs/grid/pick.d.ts +4 -2
- package/src/engine/ecs/grid/pick.d.ts.map +1 -1
- package/src/engine/ecs/grid/pick.js +1 -1
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts +3 -1
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts.map +1 -1
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +14 -16
- package/src/engine/ecs/gui/parallax/GuiElementParallaxSystem.d.ts.map +1 -1
- package/src/engine/ecs/gui/parallax/GuiElementParallaxSystem.js +16 -29
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +12 -74
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.d.ts.map +1 -1
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +10 -73
- package/src/engine/ecs/ik/ik_probe_terrain_contact.d.ts +38 -0
- package/src/engine/ecs/ik/ik_probe_terrain_contact.d.ts.map +1 -0
- package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -0
- package/src/engine/ecs/parent/EntityNode.d.ts +4 -4
- package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
- package/src/engine/ecs/parent/EntityNode.js +2 -2
- package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.d.ts +2 -2
- package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.d.ts.map +1 -1
- package/src/engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.js +1 -1
- package/src/engine/ecs/storage/binary/collection/BinaryCollectionHeaderCodec.d.ts +1 -2
- package/src/engine/ecs/storage/binary/collection/BinaryCollectionHeaderCodec.d.ts.map +1 -1
- package/src/engine/ecs/systems/AnimationSystem.d.ts +0 -5
- package/src/engine/ecs/systems/AnimationSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts +2 -2
- package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/TerrainSystem.js +1 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +5 -5
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +3 -3
- package/src/engine/ecs/terrain/util/obtainTerrain.d.ts +2 -3
- package/src/engine/ecs/terrain/util/obtainTerrain.d.ts.map +1 -1
- package/src/engine/ecs/terrain/util/obtainTerrain.js +1 -1
- package/src/engine/ecs/terrain/util/paintTerrainOverlayViaLookupTable.d.ts +0 -1
- package/src/engine/ecs/terrain/util/paintTerrainOverlayViaLookupTable.d.ts.map +1 -1
- package/src/engine/ecs/terrain/util/paintTerrainOverlayViaLookupTable.js +2 -14
- package/src/engine/ecs/util/hideEntityGracefully.js +2 -2
- package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.d.ts +12 -1
- package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.d.ts.map +1 -1
- package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -10
- package/src/engine/graphics/FrameRunner.d.ts +2 -2
- package/src/engine/graphics/FrameRunner.d.ts.map +1 -1
- package/src/engine/graphics/FrameRunner.js +1 -1
- package/src/engine/graphics/GraphicsEngine.d.ts +4 -2
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +1 -1
- package/src/engine/graphics/camera/CameraShake.d.ts +6 -6
- package/src/engine/graphics/camera/CameraShake.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/CameraSystem.d.ts +2 -2
- package/src/engine/graphics/ecs/camera/CameraSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/CameraSystem.js +1 -1
- package/src/engine/graphics/ecs/camera/auto_set_camera_clipping_planes.d.ts.map +1 -1
- package/src/engine/graphics/ecs/camera/auto_set_camera_clipping_planes.js +7 -28
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts +2 -3
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +1 -1
- package/src/engine/graphics/ecs/highlight/renderer/HighlightDecodeShader.d.ts +1 -2
- package/src/engine/graphics/ecs/highlight/renderer/HighlightDecodeShader.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/renderer/HighlightDecodeShader.js +4 -12
- package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.js +2 -11
- package/src/engine/graphics/ecs/highlight/renderer/makeBlurShader_9.d.ts +1 -2
- package/src/engine/graphics/ecs/highlight/renderer/makeBlurShader_9.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/renderer/makeBlurShader_9.js +3 -11
- package/src/engine/graphics/ecs/highlight/renderer/makeDilationShader.d.ts +1 -2
- package/src/engine/graphics/ecs/highlight/renderer/makeDilationShader.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/renderer/makeDilationShader.js +3 -11
- package/src/engine/graphics/ecs/highlight/renderer/make_screen_space_quad_material.d.ts +33 -0
- package/src/engine/graphics/ecs/highlight/renderer/make_screen_space_quad_material.d.ts.map +1 -0
- package/src/engine/graphics/ecs/highlight/renderer/make_screen_space_quad_material.js +44 -0
- package/src/engine/graphics/ecs/highlight/system/HighlightSystemBase.d.ts +55 -0
- package/src/engine/graphics/ecs/highlight/system/HighlightSystemBase.d.ts.map +1 -0
- package/src/engine/graphics/ecs/highlight/system/HighlightSystemBase.js +205 -0
- package/src/engine/graphics/ecs/highlight/system/MeshHighlightSystem.d.ts +4 -45
- package/src/engine/graphics/ecs/highlight/system/MeshHighlightSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/system/MeshHighlightSystem.js +39 -162
- package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.d.ts +4 -51
- package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.js +34 -164
- package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts +5 -57
- package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.js +48 -175
- package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.d.ts.map +1 -1
- package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +7 -25
- package/src/engine/graphics/ecs/light/three/ThreeLightCache.d.ts +1 -1
- package/src/engine/graphics/ecs/light/three/ThreeLightCache.js +1 -1
- package/src/engine/graphics/ecs/make_bvh_depth_range_computer.d.ts +26 -0
- package/src/engine/graphics/ecs/make_bvh_depth_range_computer.d.ts.map +1 -0
- package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -0
- package/src/engine/graphics/ecs/mesh/MeshSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/mesh/MeshSystem.js +2 -5
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts +4 -5
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +4 -7
- package/src/engine/graphics/ecs/render_layers_compute_depth_range.d.ts +35 -0
- package/src/engine/graphics/ecs/render_layers_compute_depth_range.d.ts.map +1 -0
- package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -0
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +2 -5
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +2 -5
- package/src/engine/graphics/ecs/water/WaterSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/WaterSystem.js +2 -5
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_nearest.d.ts.map +1 -1
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_nearest.js +22 -94
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_overlap_clipping_volume.d.ts.map +1 -1
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_overlap_clipping_volume.js +17 -57
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_raycast.d.ts.map +1 -1
- package/src/engine/graphics/geometry/buffered/query/bvh32_geometry_raycast.js +17 -57
- package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.d.ts +30 -0
- package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.d.ts.map +1 -0
- package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -0
- package/src/engine/graphics/geometry/buffered/query/query_bvh_geometry_nearest.d.ts.map +1 -1
- package/src/engine/graphics/geometry/buffered/query/query_bvh_geometry_nearest.js +11 -55
- package/src/engine/graphics/geometry/buffered/read_triangle_vertices.d.ts +29 -0
- package/src/engine/graphics/geometry/buffered/read_triangle_vertices.d.ts.map +1 -0
- package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -0
- package/src/engine/graphics/geometry/computeBoundingSphereFromVertexData.d.ts +2 -2
- package/src/engine/graphics/geometry/computeBoundingSphereFromVertexData.js +1 -1
- package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.d.ts +2 -2
- package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.d.ts.map +1 -1
- package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.js +1 -1
- package/src/engine/graphics/geometry/ribbon/Ribbon.d.ts +2 -2
- package/src/engine/graphics/geometry/ribbon/Ribbon.d.ts.map +1 -1
- package/src/engine/graphics/geometry/ribbon/Ribbon.js +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderDepthV0.js +17 -122
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderLitV0.js +15 -194
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderNormalsV0.js +12 -108
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderPerPixelV0.js +14 -183
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderV0.js +12 -154
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderViewportDepthV0.js +10 -94
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/octahedral/shader/ImpostorShaderWireframeV0.js +8 -72
- package/src/engine/graphics/impostors/octahedral/shader/glsl/common.glsl +10 -0
- package/src/engine/graphics/impostors/voxel/bake/read_atlas_to_samples.d.ts.map +1 -1
- package/src/engine/graphics/impostors/voxel/bake/read_atlas_to_samples.js +79 -58
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderDepthV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderDepthV0.js +11 -22
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderLitV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderLitV0.js +7 -16
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderNormalsV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderNormalsV0.js +5 -9
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderV0.js +4 -19
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderViewportDepthV0.d.ts.map +1 -1
- package/src/engine/graphics/impostors/voxel/shader/VoxelImpostorShaderViewportDepthV0.js +5 -9
- package/src/engine/graphics/material/SplatMaterial.d.ts.map +1 -1
- package/src/engine/graphics/material/SplatMaterial.js +2 -17
- package/src/engine/graphics/material/WaterMaterial.d.ts.map +1 -1
- package/src/engine/graphics/material/WaterMaterial.js +3 -18
- package/src/engine/graphics/material/manager/compilers/CoalesceTextures.d.ts +4 -4
- package/src/engine/graphics/material/manager/compilers/CoalesceTextures.d.ts.map +1 -1
- package/src/engine/graphics/material/manager/compilers/CoalesceTextures.js +25 -70
- package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.js +2 -5
- package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.d.ts +8 -11
- package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -21
- package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.d.ts +39 -0
- package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.d.ts.map +1 -0
- package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -0
- package/src/engine/graphics/particles/node-based/particle/ParticleAttributeSpecification.d.ts +6 -8
- package/src/engine/graphics/particles/node-based/particle/ParticleAttributeSpecification.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/particle/ParticleAttributeSpecification.js +2 -13
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -2
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/computeEmissionFunction.d.ts +2 -2
- package/src/engine/graphics/particles/particular/engine/emitter/computeEmissionFunction.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/computeEmissionFunction.js +1 -1
- package/src/engine/graphics/particles/particular/engine/utils/distributeParticlesOnObject3D.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/utils/distributeParticlesOnObject3D.js +16 -78
- package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.d.ts +45 -0
- package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.d.ts.map +1 -0
- package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -0
- package/src/engine/graphics/particles/particular/engine/utils/distrubuteParticlesOnMesh.d.ts +2 -2
- package/src/engine/graphics/particles/particular/engine/utils/distrubuteParticlesOnMesh.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/utils/distrubuteParticlesOnMesh.js +12 -116
- package/src/engine/graphics/postprocess/threejs/shaders/CopyShader.d.ts +24 -16
- package/src/engine/graphics/postprocess/threejs/shaders/CopyShader.d.ts.map +1 -1
- package/src/engine/graphics/postprocess/threejs/shaders/CopyShader.js +15 -47
- package/src/engine/graphics/render/RendererPool.d.ts +4 -4
- package/src/engine/graphics/render/RendererPool.d.ts.map +1 -1
- package/src/engine/graphics/render/RendererPool.js +2 -2
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts +4 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +4 -0
- package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts +5 -4
- package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts.map +1 -1
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +2 -2
- package/src/engine/graphics/render/layers/RenderLayer.d.ts +2 -2
- package/src/engine/graphics/render/layers/RenderLayer.d.ts.map +1 -1
- package/src/engine/graphics/render/layers/RenderLayer.js +1 -1
- package/src/engine/graphics/render/layers/RenderLayerUtils.js +1 -1
- package/src/engine/graphics/sh3/build_injected_shader.d.ts +51 -0
- package/src/engine/graphics/sh3/build_injected_shader.d.ts.map +1 -0
- package/src/engine/graphics/sh3/build_injected_shader.js +56 -0
- package/src/engine/graphics/sh3/gi/material/MaterialTransformer.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/MaterialTransformer.js +10 -1
- package/src/engine/graphics/sh3/gi/material/chunk_lpv_get_irradiance_at.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/chunk_lpv_get_irradiance_at.js +17 -0
- package/src/engine/graphics/sh3/gi/material/common.d.ts +1 -1
- package/src/engine/graphics/sh3/gi/material/common.js +6 -222
- package/src/engine/graphics/sh3/gi/material/space_fragment/build_fragment_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/space_fragment/build_fragment_shader.js +7 -11
- package/src/engine/graphics/sh3/gi/material/space_fragment/build_vertex_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/space_fragment/build_vertex_shader.js +27 -29
- package/src/engine/graphics/sh3/gi/material/space_vertex/build_fragment_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/space_vertex/build_fragment_shader.js +7 -11
- package/src/engine/graphics/sh3/gi/material/space_vertex/build_vertex_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/gi/material/space_vertex/build_vertex_shader.js +7 -12
- package/src/engine/graphics/sh3/grid_dilate_average.d.ts +38 -0
- package/src/engine/graphics/sh3/grid_dilate_average.d.ts.map +1 -0
- package/src/engine/graphics/sh3/grid_dilate_average.js +175 -0
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.d.ts +3 -1
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lightmap/bake_lightmap_for_scene.js +681 -681
- package/src/engine/graphics/sh3/lightmap/lightmap_dilate.d.ts +4 -0
- package/src/engine/graphics/sh3/lightmap/lightmap_dilate.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lightmap/lightmap_dilate.js +7 -70
- package/src/engine/graphics/sh3/lightmap/lightmap_rasterize_triangle.d.ts +2 -2
- package/src/engine/graphics/sh3/lightmap/lightmap_rasterize_triangle.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lightmap/lightmap_rasterize_triangle.js +145 -145
- package/src/engine/graphics/sh3/lpg/light_probe_grid_dilate.d.ts +6 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_dilate.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/light_probe_grid_dilate.js +14 -113
- package/src/engine/graphics/sh3/lpg/material/chunk_lpg_get_irradiance_at.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/material/chunk_lpg_get_irradiance_at.js +7 -0
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_fragment_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_fragment_shader.js +5 -8
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_vertex_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/material/space_fragment/build_vertex_shader.js +5 -8
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_fragment_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_fragment_shader.js +5 -8
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_vertex_shader.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/material/space_vertex/build_vertex_shader.js +5 -8
- package/src/engine/graphics/sh3/lpv/PathTracerProbeRenderer.d.ts +2 -1
- package/src/engine/graphics/sh3/lpv/PathTracerProbeRenderer.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpv/depth/chunk_probe_depth_sampling.d.ts +38 -0
- package/src/engine/graphics/sh3/lpv/depth/chunk_probe_depth_sampling.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpv/depth/chunk_probe_depth_sampling.js +265 -0
- package/src/engine/graphics/sh3/lpv/depth/octahedral/shader/visualize.frag.glsl +11 -0
- package/src/engine/graphics/sh3/lpv/depth/octahedral/v2/VLPDepthMapVisualisation.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpv/depth/octahedral/v2/VLPDepthMapVisualisation.js +5 -1
- package/src/engine/graphics/sh3/lpv/depth/octahedral/v2/visualise.frag.glsl +189 -419
- package/src/engine/graphics/sh3/shader/visualize.frag.d.ts +1 -1
- package/src/engine/graphics/sh3/shader/visualize.frag.js +7 -0
- package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.d.ts +102 -0
- package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.d.ts.map +1 -0
- package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -0
- package/src/engine/graphics/shaders/chunks/glsl_packing.d.ts +29 -0
- package/src/engine/graphics/shaders/chunks/glsl_packing.d.ts.map +1 -0
- package/src/engine/graphics/shaders/chunks/glsl_packing.js +30 -0
- package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.d.ts +40 -0
- package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.d.ts.map +1 -0
- package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -0
- package/src/engine/graphics/shaders/lib/make_clouds_uniforms.d.ts +22 -0
- package/src/engine/graphics/shaders/lib/make_clouds_uniforms.d.ts.map +1 -0
- package/src/engine/graphics/shaders/lib/make_clouds_uniforms.js +39 -0
- package/src/engine/graphics/texture/attribute_spec_to_three_format.d.ts +6 -0
- package/src/engine/graphics/texture/attribute_spec_to_three_format.d.ts.map +1 -1
- package/src/engine/graphics/texture/attribute_spec_to_three_format.js +9 -9
- package/src/engine/graphics/texture/channel_count_to_format_prefix.d.ts +14 -0
- package/src/engine/graphics/texture/channel_count_to_format_prefix.d.ts.map +1 -0
- package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -0
- package/src/engine/graphics/texture/computeThreeTextureInternalFormatFromDataType.d.ts.map +1 -1
- package/src/engine/graphics/texture/computeThreeTextureInternalFormatFromDataType.js +2 -20
- package/src/engine/graphics/texture/normalized_internal_format.d.ts.map +1 -1
- package/src/engine/graphics/texture/normalized_internal_format.js +2 -20
- package/src/engine/graphics/texture/sampler/Sampler2D.d.ts +2 -2
- package/src/engine/graphics/texture/sampler/Sampler2D.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/Sampler2D.js +1 -1
- package/src/engine/graphics/texture/sampler/convertTexture2Sampler2D.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/convertTexture2Sampler2D.js +9 -3
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.d.ts +6 -3
- 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 +6 -16
- package/src/engine/graphics/texture/sampler/sampler2d_combine.d.ts +2 -2
- package/src/engine/graphics/texture/sampler/sampler2d_combine.d.ts.map +1 -1
- package/src/engine/graphics/texture/sampler/sampler2d_combine.js +1 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +1 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +2 -2
- package/src/engine/grid/obstacle/GridObstacle.d.ts +2 -2
- package/src/engine/grid/obstacle/GridObstacle.d.ts.map +1 -1
- package/src/engine/grid/obstacle/GridObstacle.js +1 -1
- package/src/engine/input/devices/gamepad/GamepadDeviceLayout.d.ts +54 -0
- package/src/engine/input/devices/gamepad/GamepadDeviceLayout.d.ts.map +1 -0
- package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -0
- package/src/engine/input/devices/gamepad/GamepadHandle.d.ts +21 -2
- package/src/engine/input/devices/gamepad/GamepadHandle.d.ts.map +1 -1
- package/src/engine/input/devices/gamepad/GamepadHandle.js +39 -11
- package/src/engine/input/devices/gamepad/GamepadLayout.d.ts +128 -0
- package/src/engine/input/devices/gamepad/GamepadLayout.d.ts.map +1 -0
- package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -0
- package/src/engine/input/devices/gamepad/GamepadStandardMapping.d.ts +10 -0
- package/src/engine/input/devices/gamepad/GamepadStandardMapping.d.ts.map +1 -1
- package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +11 -0
- package/src/engine/input/devices/gamepad/GamepadStick.d.ts +5 -1
- package/src/engine/input/devices/gamepad/GamepadStick.d.ts.map +1 -1
- package/src/engine/input/devices/gamepad/GamepadStick.js +5 -1
- package/src/engine/input/devices/gamepad/gamepad_layout_database.d.ts +27 -0
- package/src/engine/input/devices/gamepad/gamepad_layout_database.d.ts.map +1 -0
- package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -0
- package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.d.ts +25 -0
- package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.d.ts.map +1 -0
- package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -0
- package/src/engine/input/devices/gamepad/prototypeGamepadTester.d.ts +2 -0
- package/src/engine/input/devices/gamepad/prototypeGamepadTester.d.ts.map +1 -0
- package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -0
- package/src/engine/input/ecs/util/TopDownCameraControllerHelper.d.ts.map +1 -1
- package/src/engine/input/ecs/util/TopDownCameraControllerHelper.js +3 -33
- package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts +2 -2
- package/src/engine/intelligence/behavior/ecs/BehaviorComponent.d.ts.map +1 -1
- package/src/engine/intelligence/behavior/ecs/BehaviorComponent.js +1 -1
- package/src/engine/intelligence/behavior/primitive/ActionBehavior.d.ts +3 -3
- package/src/engine/intelligence/behavior/primitive/ActionBehavior.d.ts.map +1 -1
- package/src/engine/intelligence/behavior/primitive/ActionBehavior.js +1 -1
- package/src/engine/intelligence/behavior/util/RandomDelayBehavior.d.ts +2 -1
- package/src/engine/intelligence/behavior/util/RandomDelayBehavior.d.ts.map +1 -1
- package/src/engine/intelligence/behavior/util/RandomDelayBehavior.js +5 -0
- package/src/engine/intelligence/blackboard/Blackboard.d.ts +4 -4
- package/src/engine/intelligence/blackboard/Blackboard.d.ts.map +1 -1
- package/src/engine/intelligence/blackboard/Blackboard.js +2 -2
- package/src/engine/intelligence/mcts/MonteCarlo.d.ts +17 -15
- package/src/engine/intelligence/mcts/MonteCarlo.d.ts.map +1 -1
- package/src/engine/intelligence/mcts/MonteCarlo.js +345 -345
- package/src/engine/intelligence/mcts/StateNode.d.ts +2 -2
- package/src/engine/intelligence/mcts/StateNode.d.ts.map +1 -1
- package/src/engine/intelligence/mcts/StateNode.js +364 -364
- package/src/engine/intelligence/optimization/RandomOptimizer.d.ts +3 -3
- package/src/engine/intelligence/optimization/RandomOptimizer.d.ts.map +1 -1
- package/src/engine/intelligence/optimization/RandomOptimizer.js +2 -2
- package/src/engine/intelligence/optimization/optimize_cyclic_sequence_pairwise.d.ts +2 -2
- package/src/engine/intelligence/optimization/optimize_cyclic_sequence_pairwise.d.ts.map +1 -1
- package/src/engine/intelligence/optimization/optimize_cyclic_sequence_pairwise.js +1 -1
- package/src/engine/knowledge/database/StaticKnowledgeDataTable.d.ts +4 -5
- package/src/engine/knowledge/database/StaticKnowledgeDataTable.d.ts.map +1 -1
- package/src/engine/knowledge/database/StaticKnowledgeDataTable.js +2 -2
- package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts +0 -5
- package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +18 -19
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +18 -13
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +2 -1
- package/src/engine/network/sim/ActionLog.d.ts +9 -6
- package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
- package/src/engine/network/sim/ActionLog.js +43 -80
- package/src/engine/network/sim/SmoothingState.d.ts.map +1 -1
- package/src/engine/network/sim/SmoothingState.js +27 -28
- package/src/engine/network/state/FrameBufferRing.d.ts +84 -0
- package/src/engine/network/state/FrameBufferRing.d.ts.map +1 -0
- package/src/engine/network/state/FrameBufferRing.js +168 -0
- package/src/engine/network/state/InputRing.d.ts +14 -12
- package/src/engine/network/state/InputRing.d.ts.map +1 -1
- package/src/engine/network/state/InputRing.js +37 -76
- package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -1
- package/src/engine/network/transport/LoopbackTransport.js +10 -31
- package/src/engine/network/transport/Transport.d.ts +42 -2
- package/src/engine/network/transport/Transport.d.ts.map +1 -1
- package/src/engine/network/transport/Transport.js +87 -3
- package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/NodeUDPTransport.js +7 -33
- package/src/engine/network/transport/adapters/SimulatedTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/SimulatedTransport.js +17 -28
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +6 -32
- package/src/engine/network/transport/adapters/WebSocketTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/WebSocketTransport.js +6 -32
- package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -1
- package/src/engine/network/transport/adapters/WebTransportTransport.js +11 -22
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +18 -48
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -8
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -1
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -7
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +7 -0
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +11 -18
- package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +7 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +15 -57
- package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_apply_scalar_advection.js +7 -0
- package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.d.ts +28 -0
- package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +12 -14
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +9 -0
- package/src/engine/physics/mls-mpm/MLS_MPM.d.ts +13 -1
- package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
- package/src/engine/physics/mls-mpm/MLS_MPM.js +15 -4
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_triangle_contact.js +9 -0
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +10 -11
- package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_triangle_contact.js +7 -12
- package/src/engine/physics/solver/apply_impulse_to_body.d.ts +36 -0
- package/src/engine/physics/solver/apply_impulse_to_body.d.ts.map +1 -0
- package/src/engine/physics/solver/apply_impulse_to_body.js +86 -0
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +1 -53
- package/src/engine/plugin/EnginePlugin.d.ts +3 -0
- package/src/engine/plugin/EnginePlugin.d.ts.map +1 -1
- package/src/engine/plugin/EnginePlugin.js +3 -0
- package/src/engine/save/GameStateLoader.d.ts +2 -2
- package/src/engine/save/GameStateLoader.d.ts.map +1 -1
- package/src/engine/save/GameStateLoader.js +1 -1
- package/src/engine/save/Storage.d.ts +4 -4
- package/src/engine/save/Storage.d.ts.map +1 -1
- package/src/engine/save/Storage.js +2 -2
- package/src/engine/save/storage/IndexedDBStorage.d.ts +1 -0
- package/src/engine/save/storage/IndexedDBStorage.d.ts.map +1 -1
- package/src/engine/save/storage/IndexedDBStorage.js +61 -60
- package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/SoundControllerSystem.js +2 -10
- package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
- package/src/engine/sound/simulation/AcousticSimulator.js +2 -1
- package/src/engine/sound/simulation/core/AcousticOccluderIndex.d.ts.map +1 -1
- package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +4 -6
- package/src/engine/sound/simulation/core/OcclusionSolver.d.ts.map +1 -1
- package/src/engine/sound/simulation/core/OcclusionSolver.js +2 -9
- package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +9 -14
- package/src/engine/sound/simulation/probe/bakeReverbBands.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/bakeReverbBands.js +2 -6
- package/src/engine/sound/simulation/probe/bakeVisibility.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/bakeVisibility.js +4 -34
- package/src/engine/sound/simulation/probe/probe_densify_portals.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/probe_densify_portals.js +3 -6
- package/src/engine/sound/simulation/probe/probe_edge_key.d.ts +31 -0
- package/src/engine/sound/simulation/probe/probe_edge_key.d.ts.map +1 -0
- package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -0
- package/src/engine/sound/simulation/probe/probe_sdf_grid.d.ts +10 -3
- package/src/engine/sound/simulation/probe/probe_sdf_grid.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/probe_sdf_grid.js +10 -22
- package/src/engine/sound/simulation/probe/probe_visibility_augment.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/probe_visibility_augment.js +12 -48
- package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.d.ts.map +1 -1
- package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +4 -3
- package/src/engine/sound/simulation/prototypeAcousticSolver.js +1652 -1652
- package/src/engine/sound/simulation/render/foaReverbImpulseResponse.d.ts +5 -4
- package/src/engine/sound/simulation/render/foaReverbImpulseResponse.d.ts.map +1 -1
- package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +23 -31
- package/src/engine/sound/simulation/render/reverbImpulseResponse.d.ts +1 -8
- package/src/engine/sound/simulation/render/reverbImpulseResponse.d.ts.map +1 -1
- package/src/engine/sound/simulation/render/reverbImpulseResponse.js +21 -31
- package/src/engine/sound/simulation/render/reverb_band_tails.d.ts +55 -0
- package/src/engine/sound/simulation/render/reverb_band_tails.d.ts.map +1 -0
- package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +6 -22
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +7 -3
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +7 -22
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +28 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +1 -5
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +1 -13
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +2 -8
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +3 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +26 -14
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -1
- package/src/engine/sound/sopra/runtime/BusGraph.js +21 -33
- package/src/engine/sound/sopra/runtime/EventInstance.js +1 -1
- package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +2 -2
- package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -1
- package/src/engine/sound/sopra/util/buildAttenuationCurve.js +40 -40
- package/src/engine/ui/apply_pointer_tilt_3d.d.ts +25 -0
- package/src/engine/ui/apply_pointer_tilt_3d.d.ts.map +1 -0
- package/src/engine/ui/apply_pointer_tilt_3d.js +50 -0
- package/src/engine/ui/bindings/processors/DomElementProcessorTilt3D.d.ts.map +1 -1
- package/src/engine/ui/bindings/processors/DomElementProcessorTilt3D.js +9 -26
- package/src/engine/ui/notification/NotificationManager.d.ts +5 -5
- package/src/engine/ui/notification/NotificationManager.d.ts.map +1 -1
- package/src/engine/ui/notification/NotificationManager.js +4 -4
- package/src/engine/ui/tiles2d/TileGrid.d.ts +2 -2
- package/src/engine/ui/tiles2d/TileGrid.d.ts.map +1 -1
- package/src/engine/ui/tiles2d/TileGrid.js +1 -1
- package/src/generation/filtering/numeric/CellFilterCache.d.ts.map +1 -1
- package/src/generation/filtering/numeric/CellFilterCache.js +4 -21
- package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.d.ts +1 -1
- package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +1 -1
- package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts.map +1 -1
- package/src/generation/filtering/numeric/complex/CellFilterSobel.js +11 -20
- package/src/generation/filtering/numeric/math/CellFilterPower.d.ts +1 -1
- package/src/generation/filtering/numeric/math/CellFilterPower.d.ts.map +1 -1
- package/src/generation/filtering/numeric/math/CellFilterPower.js +1 -5
- package/src/generation/filtering/numeric/process/computeCellFilterGradient.d.ts.map +1 -1
- package/src/generation/filtering/numeric/process/computeCellFilterGradient.js +13 -15
- package/src/generation/filtering/numeric/process/computeCellFilterSobel.d.ts +20 -0
- package/src/generation/filtering/numeric/process/computeCellFilterSobel.d.ts.map +1 -0
- package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -0
- package/src/generation/filtering/numeric/util/CellFilterDisplaced.d.ts.map +1 -1
- package/src/generation/filtering/numeric/util/CellFilterDisplaced.js +11 -6
- package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.d.ts.map +1 -1
- package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -36
- package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.d.ts +21 -0
- package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.d.ts.map +1 -0
- package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -0
- package/src/generation/filtering/util/cell_filter_weights_pick.d.ts +25 -0
- package/src/generation/filtering/util/cell_filter_weights_pick.d.ts.map +1 -0
- package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -0
- package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts.map +1 -1
- package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +14 -1
- package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +2 -2
- package/src/generation/grid/generation/util/buildPathFromDistanceMap.d.ts +1 -1
- package/src/generation/grid/generation/util/buildPathFromDistanceMap.js +1 -1
- package/src/generation/markers/GridActionRuleSet.d.ts.map +1 -1
- package/src/generation/markers/GridActionRuleSet.js +10 -7
- package/src/generation/markers/GridCellActionPlaceMarker.d.ts.map +1 -1
- package/src/generation/markers/GridCellActionPlaceMarker.js +10 -12
- package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts +2 -1
- package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts.map +1 -1
- package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts +2 -1
- package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts.map +1 -1
- package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.js +7 -42
- package/src/generation/markers/transform/MarkerNodeTransformRotateRandom.d.ts +2 -1
- package/src/generation/markers/transform/MarkerNodeTransformRotateRandom.d.ts.map +1 -1
- package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts.map +1 -1
- package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.js +11 -6
- package/src/generation/markers/transform/MarkerNodeTransformerRecordPropertyClosure.d.ts +4 -3
- package/src/generation/markers/transform/MarkerNodeTransformerRecordPropertyClosure.d.ts.map +1 -1
- package/src/generation/markers/transform/MarkerNodeTransformerRecordPropertyClosure.js +1 -1
- package/src/generation/markers/transform/MarkerNodeTransformerRecordUniqueRandomEnum.d.ts +2 -1
- package/src/generation/markers/transform/MarkerNodeTransformerRecordUniqueRandomEnum.d.ts.map +1 -1
- package/src/generation/placement/GridCellPlacementRule.d.ts.map +1 -1
- package/src/generation/placement/GridCellPlacementRule.js +10 -7
- package/src/generation/placement/action/GridCellActionPlaceTags.d.ts.map +1 -1
- package/src/generation/placement/action/GridCellActionPlaceTags.js +10 -4
- package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.d.ts.map +1 -1
- package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +2 -38
- package/src/generation/placement/action/util/GridCellDisplacedAction.d.ts.map +1 -1
- package/src/generation/placement/action/util/GridCellDisplacedAction.js +11 -6
- package/src/generation/rules/cell/CellMatcherGridPattern.d.ts.map +1 -1
- package/src/generation/rules/cell/CellMatcherGridPattern.js +11 -4
- package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
- package/src/generation/theme/ThemeEngine.js +7 -12
- package/src/view/common/VirtualListView.d.ts +1 -1
- package/src/view/common/VirtualListView.js +1 -1
- package/src/view/elements/Group.d.ts +24 -3
- package/src/view/elements/Group.d.ts.map +1 -1
- package/src/view/elements/Group.js +56 -34
- package/src/view/tooltip/gml/compiler/GMLReferenceCompiler.d.ts +2 -2
- package/src/view/tooltip/gml/compiler/GMLReferenceCompiler.d.ts.map +1 -1
- package/src/view/tooltip/gml/compiler/GMLReferenceCompiler.js +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Domain G — engine/network + engine/save + engine/ecs/storage
|
|
2
|
+
|
|
3
|
+
Source root for all paths: `H:/git/moh/app/src/mir-engine/meep/src/`.
|
|
4
|
+
Every finding below was verified by opening the cited files. `*.spec.js` and `prototype*/test*` harnesses excluded.
|
|
5
|
+
|
|
6
|
+
## Findings
|
|
7
|
+
|
|
8
|
+
| # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
|
|
9
|
+
|---|---|---|---|---|---|---|---|
|
|
10
|
+
| 1 | **CRITICAL** | **The ActionLog record wire-format is hand-parsed in 5 independent places** (written in 1). Format is documented only in a comment block, never in code. | writer: `engine/network/sim/SimActionExecutor.js:242-302` `execute`; readers: `engine/network/replication/Replicator.js:335` `#scan_record_in_scope`, `engine/network/replication/Replicator.js:376` `#copy_action_to`, `engine/network/sim/RewindEngine.js:119` `#skip_record`, `engine/network/sim/RewindEngine.js:143` `#restore_prior_state`, `engine/network/orchestrator/ServerAuthoritativeServer.js:502` `#read_historical` | All five readers repeat the identical 8-line prologue: `readUintVar()` prior_count → loop{`readUintVar` entity_id, `readUint8` component_type_id, `readUint32` payload_len, `position += len`} → `readUint8` action_type_id → `readUint8` sender_id → `readUint32` action_payload_len. `Replicator.js:359` and `RewindEngine.js:130` both carry the same `// sender_id` comment. Format spec lives in `engine/network/sim/ActionLog.js:10-22` (a docblock). Changing the payload_len width, adding a field, or reordering requires 6 coordinated edits with zero compile-time coupling; one miss silently misparses every subsequent record in the frame. | `UNJUSTIFIED` | One `engine/network/sim/action_record.js` module: `action_record_write_prior(buffer, ...)`, `action_record_read_header(buffer, out)`, `action_record_skip(buffer)`. Readers 3–5 become 1-line calls; `#restore_prior_state`/`#read_historical` keep only their per-entry payload handling. | Medium effort / Medium risk — hot path, but `sim_action.spec.js`, `RewindEngine.spec.js`, `Replicator.spec.js`, `ServerAuthoritative.spec.js` cover it end-to-end. |
|
|
11
|
+
| 2 | **HIGH** | **`ActionLog` and `InputRing` are the same per-frame ring of `BinaryBuffer` slots**, written twice. | `engine/network/sim/ActionLog.js:39-189` (class `ActionLog`); `engine/network/state/InputRing.js:28-173` (class `InputRing`) | Both hold `#buffers: BinaryBuffer[]`, `#frame_numbers: Int32Array` filled `-1`, `#write_ends: Int32Array`, index by `frame % frame_capacity`. `ActionLog.js:49-95` vs `InputRing.js:37-73` constructors are 97% token-identical (mechanical brief C, sim=0.969) — same validation throw, same three allocations, same `setCapacity` loop. `buffer_for` (`ActionLog.js:165` / `InputRing.js:124`) and `write_end_for` (`ActionLog.js:181` / `InputRing.js:139`) are token-identical. Only the *framing policy* differs: ActionLog has explicit `begin_frame`/`end_frame` (open-frame state), InputRing has a single atomic `write(frame, writer)`. The `has()` vs `has_frame()` naming divergence is pure accident. | `UNJUSTIFIED` | Promote a `FrameBufferRing` to `core/collection/` (or `engine/network/state/`) owning `frame_capacity`, `#buffers`, `#frame_numbers`, `#write_ends`, `has(frame)`, `buffer_for(frame)`, `write_end_for(frame)`, `slot_begin(frame)`, `slot_commit(frame)`. `ActionLog` composes it and adds the open-frame guard; `InputRing` composes it and adds `write`/`for_each_in_range`. Do **not** unify via a sentinel/flag parameter (house rule). | Low effort / Low risk — both classes have direct specs; the extracted primitive is ~70 lines with no behaviour change. |
|
|
12
|
+
| 3 | **HIGH** | **No shared binary codec exists for the engine's own value types; every adapter open-codes `write 3 floats`.** Systemic, not per-file. | `engine/ecs/transform/TransformSerializationAdapter.js:24-26`; `engine/interpolation/TransformPoseSerializationAdapter.js:33-39`; `engine/network/adapters/TransformReplicationAdapter.js:51-53`; `engine/network/adapters/Vector3InterpolationAdapter.js:29-31,42-44`; `engine/physics/ecs/RigidBodySerializationAdapter.js:33,37,43`; `engine/physics/ecs/JointSerializationAdapter.js:30-31`; `engine/graphics/ecs/camera/topdown/TopDownCameraControllerSerializationAdapter.js:16`; `engine/input/devices/gamepad/GamepadStickCalibration.js:101`; `engine/navigation/ecs/components/PathSerializationAdapter.js:30` | Nine files each write `buffer.writeFloatNN(v.x); …(v.y); …(v.z);` inline. `Vector3.toBinaryBuffer` (`core/geom/Vector3.js:1044`) and `toBinaryBufferFloat32` (`:1068`) exist and are marked `@deprecated` — deprecated in favour of *nothing*, so callers regressed to inline code. Meanwhile `core/geom/vec3/serialization/v3_binary_equality_encode.js`/`_decode.js` **is** the shape that works and is reused by exactly 3 adapters (`TransformSerializationAdapter.js:37,52`, `TransformReplicationAdapter.js:62,79`, `TransformInterpolationAdapter.js:37,46,77`) — proof the primitive pattern is viable and already half-built. | `PROMOTION` | Add `core/geom/vec3/serialization/v3_binary_write_f32/_f64` + `v3_binary_read_f32/_f64(buffer, out, offset)`, and `v4_`/`quat3_` siblings next to the existing `v3_binary_equality_*`. Delete the `@deprecated` `Vector*.toBinaryBuffer*` methods' bodies in favour of delegating (they are public library surface — keep the names). | Low effort / Low risk per site; the value is that a precision or endianness change becomes one edit. |
|
|
13
|
+
| 4 | **HIGH** | **AUTH_STATE payload is an untagged concatenation whose writer and reader are two independently-written loops.** A skip on one side silently desynchronizes the whole payload. | writer: `engine/network/NetworkSession.js:1481-1506` `#send_auth_state_for_entity`; reader: `engine/network/NetworkSession.js:1912-1920` `onApplyAuthState` handler | Writer iterates `this.#replicated`, `continue`s when `world.getComponent(entity_id, ComponentClass) === undefined`, and calls `adapter.serialize(buf, live)` — **no per-component tag, no length prefix**. Reader iterates the same map and `continue`s when the *receiver's* `getComponent` is undefined. The two skip predicates evaluate on different worlds. If host and client component composition differ for one entity (common during spawn/despawn races), the reader deserializes component N's bytes into component N+1. The comment at `:1910` ("Mirrors the host's serialize order") is the only thing enforcing the invariant. | `UNJUSTIFIED` | Both loops call one `#for_each_replicated(entity_id, cb)` helper, and the payload gains a `uint8 type_id + uint32 len` frame per component — the same framing `Snapshotter` already uses (`engine/network/sim/Snapshotter.js:82-91`). That makes reader skips independent of writer skips. | Medium effort / Medium risk — wire-format change; `ServerAuthoritative.spec.js` and `NetworkPeer.auth_state.spec.js` are the gate. |
|
|
14
|
+
| 5 | **HIGH** | **Three complete tagged-value object-graph serializers coexist**, two of them with no production consumer. | `engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js` (v1, 130 lines); `engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter2.js` (v2, 493 lines); `engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js` + `BinaryObjectDeSerializer.js` (v3, 331+315 lines) | `BinaryObjectSerializationAdapter2.js:3-14` declares `HEADER_TAG_NULL/UNDEFINED/BOOLEAN_TRUE/BOOLEAN_FALSE/POSITIVE_INTEGER/NEGATIVE_INTEGER/FLOAT/STRING`; `object-v2/ObjectBinaryFormat.js:31-45` declares `TAG_NULL/UNDEFINED/FALSE/TRUE/POS_INT/NEG_INT/FLOAT/STRING` — same eight concepts, different numbering. `Adapter2.#trySerializePrimitive` (`:263-310`) and `BinaryObjectSerializer.writeValue` (`:195-238`) are the same `switch (typeof value)` with the same int/varint-vs-float64 split; `Adapter2.deserialize` (`:442-486`) and `BinaryObjectDeSerializer.readValue` (`:182-227`) the same tag switch. Adapter2 additionally reimplements the shape/schema table (`#writeSchema`/`#readSchema` `:194-255`, class `SchemaTable` `:92-151`) that object-v2 implements as `#internShape`/shape table. **Grep shows `BinaryObjectSerializationAdapter2` has zero importers anywhere in `src/`**, and object-v2 has none outside its own README/DESIGN/specs/`serializerTestHarness.js`. Only v1 is wired (`engine/ecs/storage/BinaryBufferSerializer.js:125`, `BinaryBufferDeSerializer.js:108`, `engine/intelligence/blackboard/BlackboardDynamicStorageAdapter.js:44`, `engine/sound/sopra/serialization/*`). | `UNJUSTIFIED` | object-v2 is strictly the superset (refs/cycles, class table, shape table, bidirectional migration). Delete `BinaryObjectSerializationAdapter2.js` — it is an abandoned intermediate, not library surface anyone can be using (no importers, no spec). Then migrate `BinaryBufferSerializer/DeSerializer` to object-v2 and reduce v1 to a shim, or explicitly document v1 as the ECS-collection path and object-v2 as the graph path. | Deleting Adapter2: trivial. Migrating v1→v3: high effort / high risk (save-file format), and should be a separate decision. |
|
|
15
|
+
| 6 | **HIGH** | **"Resolve the migrator chain for a stored version, or throw" is written 3×, and the three have divergent up/down policies.** | `engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js:104-118` `deserialize`; `engine/ecs/storage/binary/collection/BinaryCollectionDeSerializer.js:215-232` `initialize`; `engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js:278-302` `#readAdapterObject` | All three: read stored `version` → compare to `adapter.version` → `registry.getUpgradersChain(name, version, adapter.version)` → throw if `null` → `executeBinaryClassUpgraderChain(chain, buffer, tmp0, tmp1)`. But `BinaryCollectionDeSerializer.js:215` **throws outright** when `version > adapter.version`, whereas `BinaryObjectDeSerializer.js:279` explicitly supports it ("migrate up OR down to the adapter's current version") and v1 (`:108`) just tests `!==`. Same registry, same upgrader graph, three different downgrade policies. Buffer management also diverges: v1 reuses two long-lived `__upgradeBuffer0/1` fields (`:24-30`), object-v2 allocates a fresh pair per object (`:301`), collection resets its pair per `initialize` (`:235-236`). | `UNJUSTIFIED` | One `binary_migrate_payload(registry, typeName, stored_version, adapter, buffer)` in `engine/ecs/storage/binary/`, returning the migrated buffer or the original. Pick one downgrade policy and document it. | Low effort / Medium risk — the behaviour unification is the risky part, not the extraction. |
|
|
16
|
+
| 7 | **MEDIUM** | `NetworkSession.normalize_if_dirty` and `#render_interpolated_entities` share a verbatim 18-line nested traversal. | `engine/network/NetworkSession.js:1063-1082` (inside `normalize_if_dirty`); `engine/network/NetworkSession.js:2003-2023` (inside `#render_interpolated_entities`) | Identical body: `for (const entity_id of this.#remote_entities)` → `getComponent(entity_id, NetworkIdentity)` guard → `for (const [ComponentClass, desc] of this.#replicated)` → `desc.interp === null` guard → live-component guard → `scratch.position = 0` → `#interp_log.interpolate(scratch, identity.network_id, desc.interp_type_id, …)` → `if (!ok) continue` → `scratch.position = 0` → `binary_registry.getAdapter(ComponentClass.typeName).deserialize(scratch, live)`. The only differences are the `(tick, tick, 0)` vs `(tick_a, tick_b, t)` arguments and a `wrote_anything = true` line. The same outer traversal appears a third time (serializing, not deserializing) at `:1947-1959` `#on_frame_applied`. | `UNJUSTIFIED` | Extract `#apply_interpolated(tick_a, tick_b, t) → boolean`. `normalize_if_dirty` calls it with `(tick, tick, 0)`; `#render_interpolated_entities` calls it with the computed triple and uses the return for `render_dirty`. Extract `#for_each_remote_replicated(cb)` for the shared outer walk so `#on_frame_applied` uses it too. | Low effort / Low risk. |
|
|
17
|
+
| 8 | **MEDIUM** | Transport adapters copy-paste their entire stats and disconnect-latch machinery instead of putting it on the `Transport` base. | `getStats()` ×6: `engine/network/transport/LoopbackTransport.js:186`, `engine/network/transport/adapters/SimulatedTransport.js:244`, `.../WebRTCDataChannelTransport.js:123`, `.../WebSocketTransport.js:172`, `.../NodeUDPTransport.js:198`, `.../WebTransportTransport.js:161`. Latch: `WebSocketTransport.js:61,89-90,159`, `WebRTCDataChannelTransport.js:49,63-64,70-71,109`, `NodeUDPTransport.js:59,81-82,88-89,168,184`. Base class: `engine/network/transport/Transport.js:29-112` | All six `getStats()` bodies are the identical 6-line field-by-field copy of a `{bytes_in, bytes_out, packets_in, packets_out}` object; all six declare `#stats = { bytes_in: 0, … }`. Three adapters independently implement the "`#disconnect_fired` latch so a local `disconnect()` doesn't echo `onDisconnect`" idiom, each with its own copy of `if (this.#disconnect_fired) return; this.#disconnect_fired = true; this.onDisconnect.send1(reason);`. **`WebTransportTransport` has no latch at all** (`:146-156` `disconnect()`), so it *does* echo — the divergence a shared base would have prevented. `disconnect()` is token-identical (65 tokens) between `SimulatedTransport.js:228` and `LoopbackTransport.js:166` (mechanical brief A). `send()` is token-identical (64 tokens) between `WebRTCDataChannelTransport.js:87` and `WebSocketTransport.js:103`; `NodeUDPTransport.js:131` is the same plus a remote-null check — all three compute `const payload = (length === bytes.byteLength) ? bytes : bytes.subarray(0, length)`. | `UNJUSTIFIED` | Move `#stats` + `getStats()` + protected `count_in(n)`/`count_out(n)` onto `Transport`. Add a protected `fire_disconnect_once(reason)` + `latch_disconnect()` pair there. Add a `transport_trim_payload(bytes, length)` helper (or accept the 1-liner). Leave the paired `disconnect()` of Loopback/Simulated as-is unless the pair-unbinding is hoisted to a shared `PairedTransport` base. | Low effort / Low risk — `Transport.js` already exists precisely as the shared base and is under-used. |
|
|
18
|
+
| 9 | **MEDIUM** | `send_fragmented` and `resend_fragment_chunk` duplicate the fragment header write, the chunk-slice math, and — with two *different formulas* — the `total_chunks` derivation. | `engine/network/transport/fragments/fragment_send.js:69-86` (`send_fragmented` loop body); `engine/network/transport/fragments/fragment_send.js:127-146` (`resend_fragment_chunk`) | Both compute `chunk_offset = i * MAX_FRAGMENT_CHUNK_BYTES`, `remaining`, `chunk_size = remaining < MAX ? remaining : MAX`, then write the identical 5-byte header (`scratch[0]=type; scratch[1]=id&0xFF; scratch[2]=(id>>8)&0xFF; scratch[3]=index; scratch[4]=total`), `scratch.set(payload.subarray(...), FRAGMENT_HEADER_BYTES)`, `send_fn(scratch, FRAGMENT_HEADER_BYTES + chunk_size)`. `total_chunks` is `Math.ceil(len/MAX)` at `:61` but `len <= MAX ? 1 : Math.ceil(len/MAX)` at `:127-129`. The doc at `:96-97` states the invariant ("The wire layout matches what `send_fragmented` would have produced") in prose because nothing enforces it — and a retransmitted chunk that carries a different `total_chunks` than the original makes the receiver drop the whole assembly (`FragmentAssembler.js:168-173`). | `UNJUSTIFIED` | One module-private `write_fragment_chunk(payload, payload_length, message_id, chunk_index, total_chunks, type, scratch, send_fn)` and one `fragment_total_chunks(payload_length)`. Both exports become thin loops/guards over it. | Low effort / Low risk — `fragment_send` has a spec and the NACK path has `NetworkPeer` integration coverage. |
|
|
19
|
+
| 10 | **MEDIUM** | `ServerAuthoritativeServer` compacts its 4 parallel pending arrays with two near-verbatim 19-line methods differing only in an inequality. | `engine/network/orchestrator/ServerAuthoritativeServer.js:597-616` `#pending_trim_to_window`; `engine/network/orchestrator/ServerAuthoritativeServer.js:634-653` `#pending_filter_to_future` | Byte-for-byte identical apart from `>= window_oldest` vs `> keep_threshold`: same `kept` cursor, same four `[kept] = [i]` assignments, same four `.length = kept` truncations, same `#pending_referenced_frames.length = 0` + rebuild loop. The code says so itself at `:629-631`: *"Same structural pattern as `__pending_trim_to_window` but with the inequality flipped"*. Adding a fifth parallel array means editing both. | `UNJUSTIFIED` | Single `#pending_keep_from(min_frame)` — callers pass `window_oldest` and `keep_threshold + 1`. Uniform control flow, no predicate parameter, no sentinel. | Trivial effort / Low risk — `ServerAuthoritative.spec.js` covers both paths. |
|
|
20
|
+
| 11 | **MEDIUM** | `snapshotter_apply` and `snapshotter_apply_to_existing` are two hand-written parsers of one wire format, with divergent bounds handling. | `engine/network/sim/Snapshotter.js:108-151` `snapshotter_apply`; `engine/network/sim/Snapshotter.js:176-222` `snapshotter_apply_to_existing` | Same outer/inner loop, same three `MalformedPacketError` bounds checks (entity overrun, payload overrun, read-past-end) with only the function name changed in the message, same `type_id/payload_len` read, same skip-to-stay-aligned pattern. They differ only in what happens per component: create-entity-and-attach vs look-up-and-deserialize-in-place (three skip cases at `:200-213` vs one at `:134-138`). A future hardening of the length validation must be applied twice. | `UNJUSTIFIED` | One `snapshotter_walk(buffer, buffer_end, on_entity, on_component)` doing the framing + all bounds checks; the two public functions supply the two apply policies. | Low effort / Low risk — `Snapshotter.spec.js` has dedicated malformed-packet tests (`:338`, `:369`). |
|
|
21
|
+
| 12 | **MEDIUM** | `NetworkPeer` repeats the same 6-line send prologue/epilogue across 9 packet-send methods. | `engine/network/orchestrator/NetworkPeer.js` — `send_recovery_request:947`, `send_state_burst_for_range:975`, `send_auth_state:1038`, `send_initial_sync:1064`, `send_resume_hello:1089`, `send_resume_accept:1110`, `send_resume_reject:1125`, `send_disconnect:1144`, `send_time_dilation_feedback:1168` | Each is: `const conn = this.#peers.get(peer_id); if (conn === undefined) throw new Error(\`NetworkPeer.<name>: peer ${peer_id} not connected\`); this.#send_buffer.position = 0; this.#send_buffer.writeUint8(NetworkPacketType.<X>);` … `this.#send_to_peer(conn, this.#send_buffer.raw_bytes, this.#send_buffer.position);` — 9 copies of the connection lookup + error string + buffer reset + type byte + dispatch. This is duplicated *logic* (lookup/throw/reset), not adapter shape. | `UNJUSTIFIED` | Private `#begin_packet(peer_id, method_name, packet_type) → conn` and `#end_packet(conn)`. Each `send_*` keeps only its own field writes. Stays fully typed — no string-keyed dispatch table. | Trivial effort / Low risk. |
|
|
22
|
+
| 13 | **MEDIUM** | The security-critical bounds-checked action-header read is written 3× inside a single method. | `engine/network/replication/Replicator.js:224-231` (deferral path), `:256-263` (skip path), `:272-279` (apply path) | Each block is `if (in_buffer.position + ACTION_HEADER_BYTES > in_buffer_end) throw new MalformedPacketError(...); readUint8(); const len = readUint32(); if (in_buffer.position + len > in_buffer_end) throw new MalformedPacketError(...)`. Three copies of the invariant that gates untrusted-length parsing (comment at `:219-223` explains why it must not be an `assert`). The three error messages have already drifted ("action header overruns packet" vs "…(skip; frame …)" vs "skipped action payload of …"). One missed copy = OOB read / cross-packet leak. | `UNJUSTIFIED` | `#read_action_header(buffer, end, frame, i, count) → payload_len` (or a small `{type_id, payload_len}` out-param object hoisted to a field to avoid allocation). All three call it. | Trivial effort / Low risk — `Replicator.spec.js:644,666` already test malformed lengths. |
|
|
23
|
+
| 14 | **MEDIUM / PROMOTION** | Shortest-path quaternion nlerp (dot-sign-flip → componentwise lerp → normalize) written 3× with no core primitive. | `engine/network/adapters/QuaternionInterpolationAdapter.js:27-42`; `engine/network/adapters/TransformInterpolationAdapter.js:56-65`; a third variant (lerp toward identity) at `engine/network/sim/SmoothingState.js:166-183` | The first two are the same 12 lines: `if (a·b < 0) negate b`, four `q = a + (b - a) * t`, `len2`, `inv_len = len2 > 0 ? 1/Math.sqrt(len2) : 1`, scale. `core/geom/3d/quaternion/` has `quat3_multiply`, `quat3_integrate`, `quat3_from_axis_angle`, `quat3_to_matrix3`, `quat3_encode/decode_from_uint32` — **but no `quat3_nlerp`/`quat3_slerp`**; the only slerp in the codebase is the allocating object method `Quaternion.slerpQuaternions` (`core/geom/Quaternion.js:1197`). `SmoothingState.js:162-165` even says *"use the static helper variant if available. For now, do a manual lerp+normalize"* — an explicit note that the primitive is missing. Note also `Vector3InterpolationAdapter.js:38-40` uses `core/math/lerp.js` while the other two inline `a + (b - a) * t` 4 and 10 times (mechanical brief F, `inline_lerp`). | `PROMOTION` | Add `core/geom/3d/quaternion/quat3_nlerp.js` — `quat3_nlerp(out, out_offset, ax,ay,az,aw, bx,by,bz,bw, t)`, flat-array, allocation-free, matching the existing `quat3_*` signature convention. All three sites call it. | Low effort / Low risk — pure function, trivially spec'd. |
|
|
24
|
+
| 15 | **MEDIUM** | `TransformInterpolationAdapter` re-derives `TransformReplicationAdapter`'s wire layout by hand; the coupling is documented but unenforced. | `engine/network/adapters/TransformReplicationAdapter.js:45-63` (`serialize`), `:69-86` (`deserialize`); `engine/network/adapters/TransformInterpolationAdapter.js:29-78` (`interpolate`) | `interpolate` reads `Float32 ×3 → readUint32 (packed quat) → v3_binary_equality_decode` twice and writes the same sequence back at `:73-77`. That is a third and fourth hand-written copy of the same field order/precision. Doc at `:15-17` says "matching the wire layout of `TransformReplicationAdapter`". Adding a field to the replication adapter silently makes every interpolated remote Transform garbage — no type, no test, no assert links them. | `UNJUSTIFIED` | Export the layout as three small functions from `TransformReplicationAdapter.js` (`transform_wire_read(buffer, out, offset)`, `transform_wire_write(buffer, px,py,pz, qpacked, sx,sy,sz)`); the replication adapter and the interpolation adapter both call them. Interp keeps only the blend math. | Low effort / Low risk — both have specs (`TransformReplicationAdapter.spec.js`, `TransformInterpolationAdapter.spec.js`). |
|
|
25
|
+
| 16 | **MEDIUM** | `IndexedDBStorage` repeats the transaction+objectStore+request+listener block 4×, and the durability fix was applied to only one copy. | `engine/save/storage/IndexedDBStorage.js:45-76` `remove`, `:78-96` `load`, `:98-147` `store`, `:149-167` `list` | All four: `this.db.then(db => { const transaction = db.transaction(MAIN_STORE_NAME, <mode>); const objectStore = transaction.objectStore(MAIN_STORE_NAME); const idbRequest = objectStore.<op>(…); idbRequest.addEventListener('success'|'error', …) })`. The comment at `:108-112` documents a real correctness lesson — *"A successful request only means the write was queued… Reporting success off the request turns a lost save into a reported save"* — and `store` was fixed to resolve on `transaction.complete` and reject on `transaction.abort`. **`remove` (a write) was not**: `:57-59` still resolves off `idbRequest`'s `success`. Textbook "fix in one place misses the other". | `UNJUSTIFIED` | Private `#with_store(mode, run, resolve, reject)` that opens the transaction, hands the object store to `run`, and wires `complete`/`abort` on the transaction for writes. `remove`/`store` share the write path; `load`/`list` share the read path. | Low effort / Low risk — `IndexedDBStorage.spec.js` exists. Fixing `remove` is a behaviour change (a correct one). |
|
|
26
|
+
| 17 | **MEDIUM** | `FragmentAssembler` and `FragmentRetention` each implement the same bounded Map + FIFO-order-array cache, including a token-identical `#delete`. | `engine/network/transport/fragments/FragmentAssembler.js:95-103` (`#pending` + `#order`), `:150-154` (evict-oldest), `:286-290` (`#delete`), `:280-283` (`clear`); `engine/network/transport/fragments/FragmentRetention.js:72-80`, `:104-107`, `:189-193` (`#delete`), `:183-186` (`clear`) | `#delete(message_id) { this.#map.delete(message_id); const idx = this.#order.indexOf(message_id); if (idx >= 0) this.#order.splice(idx, 1); }` is identical in both. `if (map.size >= cap) { const oldest = order.shift(); if (oldest !== undefined) map.delete(oldest); }` is identical in both. `clear()` is identical. Both are keyed by the same `message_id` space and live side-by-side in `NetworkPeer`'s per-peer connection record (`NetworkPeer.js:674-680`). A third, structurally-similar bounded-key window is `ReliableCommandPipeline.#received_seqs`+`#received_order` (`engine/network/transport/ReliableCommandPipeline.js:150-158`, prune at `:250-256`). | `UNJUSTIFIED` | Promote a `BoundedFifoMap` (or `KeyedFifoCache`) to `core/collection/map/` with `set`, `get`, `delete`, `evict_oldest`, `peek_oldest`, `clear`. All three compose it; per-entry TTL/retry policy stays in the callers. | Low effort / Low risk. |
|
|
27
|
+
| 18 | **MEDIUM / PROMOTION** | Four independent implementations of "per-key monotonic frame watermark map". | `engine/network/state/Baseline.js:16-83` (whole class, `Map<peer,frame>` + monotonic `set_acked`); `engine/network/replication/Replicator.js:104,244,305,322` (`#applied_through` `Map<peer,frame>`); `engine/network/orchestrator/ServerAuthoritativeServer.js:214,238-241,413-418` (`#peer_max_received_frame` `Map<peer,frame>`); `engine/network/orchestrator/ServerAuthoritativeClient.js:52,340-341` (`#last_reconciled_frame`, a `Uint32Map`) | All four store "highest frame seen for key K", all four do the same `const prev = map.get(k); if (prev === undefined || v > prev) map.set(k, v)` guard (or its inline equivalent), and all four need the same "-1 when absent" read and a `forget(key)` on disconnect. `Baseline` is literally that class and already lives in `engine/network/state/`. The backing store choice is also inconsistent (three plain `Map`, one `core/collection/Uint32Map`) with no stated reason. `Replicator.forget_peer` (`:320`) and `Baseline.forget` (`:62`) are the same method written twice. | `UNJUSTIFIED` | Reuse `Baseline` (or rename it `FrameWatermarkMap` and keep `Baseline` as an alias — meep is a library) for all four. Settle on `Uint32Map` for the integer-keyed cases. | Low effort / Low risk — `Baseline` has a spec; the other three have orchestrator-level coverage. |
|
|
28
|
+
| 19 | **LOW** | `Uint8Array`/`TextEncoder` length-prefixed blob framing hand-rolled at 4 sites while a correct primitive exists in one. | primitive: `engine/ecs/storage/binary/object-v2/objectStringCodec.js:29-48`; re-derived: `engine/network/orchestrator/NetworkPeer.js:1150-1156` + `:607-615` (DISCONNECT label, `uint8` length + `TextEncoder`/`TextDecoder`), `engine/network/diagnostics/ReplayLog.js:110-113` + `:129-132` (`uvar` length + bytes), `engine/network/NetworkSession.js:1287-1300` (`ReplaceComponentAction` `uint32` length + bytes) | `writeObjectString`/`readObjectString` is exactly the "byte-length-prefixed, NUL-safe" codec, and its docblock (`objectStringCodec.js:3-8`) explains why `BinaryBuffer.writeUTF8String` cannot be used for arbitrary data. It is buried inside `object-v2` and invisible to `engine/network`, which reinvented it (with a silent 255-byte truncation at `NetworkPeer.js:1151`). Three length-prefix widths in use (`uint8`, `uvar`, `uint32`) with no shared convention. | `PROMOTION` | Move `writeObjectString`/`readObjectString` to `core/binary/` as `binary_write_string_prefixed` / `binary_read_string_prefixed` (+ a `bytes` sibling), re-export from `object-v2` for compatibility. `NetworkPeer.send_disconnect`/dispatch use it. | Low effort / Low risk. |
|
|
29
|
+
| 20 | **LOW** | `SmoothingState` inlines two existing core primitives verbatim. | `engine/network/sim/SmoothingState.js:132-137` (quaternion multiply-by-conjugate); `engine/network/sim/SmoothingState.js:155` (`Math.sqrt(ex*ex + ey*ey + ez*ez)`) | Lines 134-137 are the *identical four expressions* as `core/geom/3d/quaternion/quat3_multiply.js:27-30` (`aw*bx + ax*bw + ay*bz - az*by`, …), just with the second operand pre-conjugated at `:133`. Line 155 re-derives `core/geom/vec3/v3_length.js` (mechanical brief F, `inline_length3`). Both primitives exist, are tested (`quat3_multiply.spec.js`), and this is cold code (once per corrected entity per frame). | `UNJUSTIFIED` | `quat3_multiply(s.orientation_error, 0, px,py,pz,pw, -qx,-qy,-qz,qw)` and `v3_length(ex,ey,ez)`. | Trivial effort / Low risk. |
|
|
30
|
+
| 21 | **LOW** | Two inline fixed-size sample rings with full-window scans, while `core/collection/RingBuffer.js` and `array_compute_min_max` exist. | `engine/network/time/AdaptiveRenderDelay.js:124-128` (`#lateness` Float64Array + `#index` + `#filled`), scan at `:183-189`; `engine/network/time/TimeSync.js:51-64` (`#rtt_samples`/`#offset_samples` + `#sample_cursor`), scan at `:106-116` | Both: fixed `Float64Array`, cursor `(i + 1) % capacity`, then an O(n) pass over the whole window to find an extreme. `AdaptiveRenderDelay`'s min/max pass is `core/collection/array/array_compute_min_max`. `TimeSync`'s is an argmin over one array with a parallel array read — genuinely needs the paired index, so `array_compute_min` doesn't fit. | `AdaptiveRenderDelay`: `UNJUSTIFIED` (use `array_compute_min_max`). `TimeSync`: `JUSTIFIED (paired argmin over two parallel arrays; no core primitive matches)` | Swap the `AdaptiveRenderDelay` scan for `array_compute_min_max`. Leave the ring storage alone — both are perf-shaped fixed typed arrays and `RingBuffer` is an object-slot ring. | Trivial effort / Low risk. |
|
|
31
|
+
| 22 | **LOW** | `#grow_*` typed-array doubling written twice, token-identical. | `engine/network/replication/Replicator.js:404-409` `#grow_record_bounds`; `engine/network/sim/RewindEngine.js:176-181` `#grow_record_starts` | `const old = this.#x; const next = new Int32Array(old.length * 2); next.set(old); this.#x = next;` — identical in both files, and these are the only two `old.length * 2` grow sites in `engine/`+`core/`. | `UNJUSTIFIED` | `core/collection/array/typed/typed_array_grow(array, min_length)` returning the (possibly same) array. Also usable by `ReplicationSlotTable.#grow` (`engine/network/state/ReplicationSlotTable.js:198-210`), which does the same with two arrays and a `fill(-1)`. | Trivial effort / Low risk. |
|
|
32
|
+
| 23 | **LOW** | `seq16_advance` bypassed for an inline `& 0xFFFF`. | `engine/network/orchestrator/NetworkPeer.js:917` (`this.#next_fragment_message_id = (this.#next_fragment_message_id + 1) & 0xFFFF;`) vs `engine/network/core/sequence/seq16.js:67` `seq16_advance` | The `seq16` module is the single home for 16-bit modular sequence arithmetic and is used correctly by `Channel.js:167`. This one site open-codes it. Cosmetic today, but the whole point of the module is that every wrap-safe operation goes through it. | `UNJUSTIFIED` | `seq16_advance(this.#next_fragment_message_id)`. | Trivial effort / Trivial risk. |
|
|
33
|
+
| 24 | **LOW** | `Storage`'s promise wrappers are re-derived by its own consumer instead of being used. | `engine/save/Storage.js:36-40,49-53,76-78,95-97,117-119` (`promiseLoadBinary`, `promiseStoreBinary`, `promiseList`, `promiseRemove`, `promiseContains`); `engine/save/StorageBackedList.js:88-90` and `:132-134` | `StorageBackedSet` writes `new Promise((resolve, reject) => this.storage.store(this.key, list, resolve, reject, noop))` twice, and a 3-level callback pyramid at `:47-68` for read — while `Storage` already exposes `promiseStoreBinary`/`promiseLoadBinary`/`promiseContains`. The two hand-written copies are already inconsistent (`:89` passes `noop` for `progress`, `:133` omits it). `Storage` has `promiseStoreBinary` but no `promiseStore`, which is presumably why the consumer gave up. | `UNJUSTIFIED` | Add `promiseStore`/`promiseLoad` alongside the existing `promiseStoreBinary`/`promiseLoadBinary` in `Storage`, and have `StorageBackedSet` use them. | Trivial effort / Low risk. |
|
|
34
|
+
| 25 | **LOW** | "What is this class's serialized name?" resolved by 5 different rules across the serialization layer. | `engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js:269-272`; `engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js:59-67`; `engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter2.js:324-337`; `engine/ecs/storage/binary/BinarySerializationRegistry.js:57-67`; `engine/ecs/storage/BinaryBufferSerializer.js:140-144` | object-v2 does `registry.getClassName(ctor)` then an **own-property** `typeName` check (guarding against a subclass inheriting its ancestor's `typeName`); v1 does a bare `Object.getPrototypeOf(object).constructor.typeName` (walks the prototype chain — the divergence is already logged in `src/REVIEW_2026_08_06/engine-ecs.md:665-669`); Adapter2 does `class_registry.findNamesByModule(ctor)` with a `ctor.typeName` fallback; the registry and `BinaryBufferSerializer` each do their own bare `typeName` read with different error text. | `UNJUSTIFIED` | One `binary_resolve_type_name(registry, ctor)` in `engine/ecs/storage/binary/` implementing object-v2's (correct) rule. All five sites call it. | Low effort / Low risk. |
|
|
35
|
+
|
|
36
|
+
## Serialization surface census
|
|
37
|
+
|
|
38
|
+
"Would a field change need editing here?" = if you add/remove/reorder a field on the type, must this site be edited by hand?
|
|
39
|
+
|
|
40
|
+
### `Transform` (`engine/ecs/transform/Transform.js`)
|
|
41
|
+
|
|
42
|
+
| # | Encoding | Path | Layout | Field change needs edit? |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| 1 | JSON | `engine/ecs/transform/Transform.js:252` `toJSON` / `:226` `fromJSON` | `{position, rotation, scale}` delegating to the value types' own `toJSON` | **Yes** |
|
|
45
|
+
| 2 | Save-game binary | `engine/ecs/transform/TransformSerializationAdapter.js:18` / `:45` | 3×Float64 pos, uint32 packed quat, equality-encoded scale | **Yes** |
|
|
46
|
+
| 3 | Network wire binary | `engine/network/adapters/TransformReplicationAdapter.js:45` / `:69` | 3×Float32 pos, uint32 packed quat, equality-encoded scale | **Yes** |
|
|
47
|
+
| 4 | Network wire binary (read+write, *third* copy of layout #3) | `engine/network/adapters/TransformInterpolationAdapter.js:29` `interpolate` | reads #3 twice, writes #3 once | **Yes** — and silently, with no error |
|
|
48
|
+
| 5 | Local interpolation binary | `engine/interpolation/TransformPoseSerializationAdapter.js:30` / `:46` | 7×Float64 (pos + quat, no scale) | **Yes** |
|
|
49
|
+
| 6 | object-v2 reflection | `engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js:309` `#writeReflected` via `Object.keys` | own enumerable fields incl. `matrix`, `flags` | No (reflective) — but produces a *different* field set than 1–5 |
|
|
50
|
+
|
|
51
|
+
**5 hand-written encodings of one type.** #2/#3/#4 share a field order that nothing enforces.
|
|
52
|
+
|
|
53
|
+
### `Vector3` (`core/geom/Vector3.js`)
|
|
54
|
+
|
|
55
|
+
| # | Encoding | Path | Field change needs edit? |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| 1 | JSON | `core/geom/Vector3.js:1014` / `:1026` | Yes |
|
|
58
|
+
| 2 | Float64 binary (`@deprecated`) | `core/geom/Vector3.js:1044` / `:1055` | Yes |
|
|
59
|
+
| 3 | Float32 binary (`@deprecated`) | `core/geom/Vector3.js:1068` / `:1079` | Yes |
|
|
60
|
+
| 4 | Equality-compressed binary | `core/geom/vec3/serialization/v3_binary_equality_encode.js` / `_decode.js` | Yes |
|
|
61
|
+
| 5 | Float32 interpolation | `engine/network/adapters/Vector3InterpolationAdapter.js:27` | Yes |
|
|
62
|
+
| 6–14 | 9 inline open-codings inside component adapters | `TransformSerializationAdapter.js:24`, `TransformPoseSerializationAdapter.js:33`, `TransformReplicationAdapter.js:51`, `RigidBodySerializationAdapter.js:33,37,43`, `JointSerializationAdapter.js:30-31`, `TopDownCameraControllerSerializationAdapter.js:16`, `GamepadStickCalibration.js:101`, `PathSerializationAdapter.js:30`, `TerrainSerializationAdapter.js:37,40,90` | Yes, each |
|
|
63
|
+
|
|
64
|
+
**~14 independent encodings.** Only #4 is a shared primitive with a spec.
|
|
65
|
+
|
|
66
|
+
### `Quaternion` (`core/geom/Quaternion.js`)
|
|
67
|
+
|
|
68
|
+
| # | Encoding | Path | Field change needs edit? |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| 1 | JSON | `core/geom/Quaternion.js:1345` / `:1359` | Yes |
|
|
71
|
+
| 2 | Float64 binary | `core/geom/Quaternion.js:1367` / `:1378` | Yes |
|
|
72
|
+
| 3 | Float32 binary | `core/geom/Quaternion.js:1391` / `:1402` | Yes |
|
|
73
|
+
| 4 | Smallest-three uint32 | `core/geom/3d/quaternion/quat3_encode_to_uint32.js` / `quat3_decode_from_uint32.js` (alias files `quat_*` re-export) | Yes |
|
|
74
|
+
| 5 | Float32 interpolation | `engine/network/adapters/QuaternionInterpolationAdapter.js:14` | Yes |
|
|
75
|
+
| 6 | Inline in Transform pose adapter | `engine/interpolation/TransformPoseSerializationAdapter.js:36-39,51-53` | Yes |
|
|
76
|
+
|
|
77
|
+
### `Vector4` / `Vector2` / `Vector1` / `Color`
|
|
78
|
+
|
|
79
|
+
| Type | Encodings | Paths |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `Vector4` | JSON + Float64 binary | `core/geom/Vector4.js:437`/`:446`, `:454`/`:465` |
|
|
82
|
+
| `Vector2` | JSON + Float64 binary + Float32 binary | `core/geom/Vector2.js:422`/`:426`, `:439`/`:448`, `:459`/`:468` |
|
|
83
|
+
| `Vector1` | JSON + Float64 binary | `core/geom/Vector1.js:270`/`:278` |
|
|
84
|
+
| `Color` | JSON + Float32 binary (`@deprecated`) + CSS-string `parse` | `core/color/Color.js:571`/`:567`, `:642`/`:653`, `:664` |
|
|
85
|
+
|
|
86
|
+
`Vector4.fromBinaryBuffer` (`:465`) and `Quaternion.fromBinaryBuffer` (`:1378`) are **literally identical** (4× `readFloat64` + `set`); `Color.fromBinaryBuffer` (`:653`) is the same shape at Float32. See dismissed leads — this one is justified.
|
|
87
|
+
|
|
88
|
+
### `NetworkIdentity` (the only ECS component with a *single* encoding)
|
|
89
|
+
|
|
90
|
+
| # | Encoding | Path |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| 1 | Save-game binary | `engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js:36` / `:59` |
|
|
93
|
+
|
|
94
|
+
Deliberately never on the wire (`:19-22`) — the counter-example that shows one encoding is achievable when the design says so.
|
|
95
|
+
|
|
96
|
+
### Summary
|
|
97
|
+
|
|
98
|
+
`Transform` has **5** hand-maintained binary/JSON encodings, `Vector3` ~**14**, `Quaternion` **6**. A field-layout change to `Transform` requires 5 coordinated edits, **none of which is enforced by a type, an assert, or a shared test**; the `TransformReplicationAdapter` ⟷ `TransformInterpolationAdapter` pair (#3/#4) is coupled by a prose comment only, and a mismatch there corrupts every interpolated remote entity with no error.
|
|
99
|
+
|
|
100
|
+
## Dismissed leads
|
|
101
|
+
|
|
102
|
+
- **`Color.fromBinaryBuffer` / `Quaternion.fromBinaryBuffer` / `Vector4.fromBinaryBuffer` token-identical (mechanical brief A).** JUSTIFIED: 5-line leaf codecs; a `read_float_tuple(buffer, n, precision)` primitive would need a precision selector (Color is f32, the others f64) — a sentinel parameter the house rules reject — and adds a call layer to the lowest-level codec for no maintenance win. The *real* problem is finding #3 (no shared vector codec used by adapters), not these methods.
|
|
103
|
+
- **`seq16.js` vs `seq32.js` (near-identical API and `greater_than` body).** JUSTIFIED: `seq32_greater_than`/`seq32_distance` must use Number arithmetic because JS bitwise operators sign-extend at bit 31 (documented at `seq32.js:36-38`); a shared implementation would be wrong for one of them. Separately noted: `seq32_*` has **no production callers** (only its own spec) — library surface, not dead code, so leave it.
|
|
104
|
+
- **Hand-written per-class `BinaryClassSerializationAdapter` subclasses looking alike.** JUSTIFIED by explicit design bet (action-log replication with hand-written adapters). The duplication that *is* worth fixing is the missing value-type codec primitives underneath them (#3), not the adapters.
|
|
105
|
+
- **`BinaryObjectSerializer.writeValue` vs `BinaryObjectDeSerializer.readValue`, `serialize` vs `deserialize`, `snapshotter_emit` vs `snapshotter_apply`.** JUSTIFIED: encode/decode pairs are inherently mirrored; the format constants are already shared via `ObjectBinaryFormat.js` (which is why the mechanical brief flagged the import block, not the logic).
|
|
106
|
+
- **`BinaryCollectionSerializer.setRegistry`/`setBuffer` vs `BinaryCollectionDeSerializer.setRegistry`/`setBuffer`; `BinaryObjectSerializer.setRegistry` vs `BinaryObjectDeSerializer.setRegistry`.** 4-line assertion setters. Too small; a shared base class would be ceremony.
|
|
107
|
+
- **`SimActionRegistry` vs `ReplicatedComponentRegistry` "assign a sequential uint8 type_id, cap at 256".** ~6 shared lines inside otherwise-unrelated classes (one owns an object pool, the other an adapter cache). Not worth a shared base.
|
|
108
|
+
- **`SimActionRegistry`'s per-type object pool vs `core/model/object/ObjectPoolFactory`.** Checked: the registry pool is a `SimAction[][]` indexed by `type_id` with `reset()`-on-acquire; `ObjectPoolFactory` is per-class with a factory callback. Swapping would need one factory per action class registered at `register()` time. Marginal.
|
|
109
|
+
- **`ReplicationSlotTable`'s free-list + highwater vs `core/IdPool`.** Checked `core/IdPool.js`: it is `BitSet`-backed with `nextClearBit` allocation (lowest-free-first). `ReplicationSlotTable` needs LIFO recycling *plus* a per-slot generation counter *plus* the reverse `entity→network` map, and deliberately keeps ids dense in a typed array. Different enough.
|
|
110
|
+
- **`JitterBuffer.push` sorted-insert vs `ServerAuthoritativeServer.#track_referenced_frame` sorted-insert.** Both already use `core/collection/array/binarySearchHighIndex` — this is reuse working correctly, not duplication.
|
|
111
|
+
- **`SimulatedTransport`'s linear insertion-sort into `in_queue` (`:163-166`) vs `JitterBuffer`.** SimulatedTransport is a test/prototype link simulator; the insert is O(n) by design for tiny queues. Cold code, different key.
|
|
112
|
+
- **`SyncTest.__fnv_byte` (`engine/network/diagnostics/SyncTest.js:58`) vs `core/collection/array/typed/uint8_array_hash`.** Checked both: the core hash is a DJB-style word-at-a-time hash with no incremental accumulator, while `fingerprint_world` must interleave entity_id and type_id bytes between component payloads. Not a drop-in. Noted, not filed.
|
|
113
|
+
- **`ScopeFilter` / `owner_authorization` / `ChangedEntitySet` / `PriorityAccumulator` / `MutationLedger` / `TimeDilation`.** Read; no duplication found.
|
|
114
|
+
|
|
115
|
+
## Notes — why duplication accumulates here
|
|
116
|
+
|
|
117
|
+
1. **Wire formats are documented, not typed.** `ActionLog.js:10-22`, `Replicator.js:26-35`, `Snapshotter.js:19-29`, `Channel.js:9-16`, `fragment_send.js:12-21`, `TransformReplicationAdapter.js:23-28` all describe their byte layout in a docblock and then hand-write the reader and writer separately. Nothing in the language couples them, so every new consumer of a format writes a fresh parser (finding #1: five parsers for one format). The one place this was done right — `ObjectBinaryFormat.js` as a shared constants module — is exactly the place the mechanical pass found *no* logic duplication.
|
|
118
|
+
|
|
119
|
+
2. **Three generations of object serializer were built without retiring the previous one.** v1 (`object/BinaryObjectSerializationAdapter`) is wired; v2 (`object/BinaryObjectSerializationAdapter2`) has zero importers; v3 (`object-v2/`) has a README, a DESIGN.md, four spec files, and zero production consumers. Each generation re-derived the primitive-tag switch and the interning table from scratch rather than extending its predecessor. "meep is a library, unreferenced exports are public surface" protects v3 (documented, spec'd, intended to be used) but not v2 (undocumented intermediate with no spec).
|
|
120
|
+
|
|
121
|
+
3. **`Transport.js` was written as a base class but only used as an interface.** It has default `getStats`, `connect`, `disconnect` implementations (`:95-111`), and six subclasses that override all three with copies of each other rather than calling up. The `#disconnect_fired` latch was invented three times independently — and forgotten on the fourth adapter (`WebTransportTransport`), which is now behaviourally inconsistent.
|
|
122
|
+
|
|
123
|
+
4. **`core/` primitives are hard to discover from `engine/network/`.** `quat3_multiply` is inlined verbatim in `SmoothingState.js` with a comment saying the helper doesn't exist; `lerp` is used in one interpolation adapter and inlined 14× in the other two; `v3_length` is inlined; `objectStringCodec`'s length-prefixed codec is reinvented in `NetworkPeer`. There is a real gap too — no `quat3_nlerp` exists anywhere — but three of the four cases are pure discovery failure.
|
|
124
|
+
|
|
125
|
+
5. **Parallel-array containers invite copy-paste compaction.** `ServerAuthoritativeServer`'s four `#pending_*` arrays forced two 19-line compaction methods (#10) and two dedup triple-comparison loops (`:454-459` and `:567-572`). `core/collection/table/RowFirstTable` exists for exactly this and is not used here — worth considering if a fifth column is ever added.
|
|
126
|
+
|
|
127
|
+
6. **One real correctness consequence, already latent.** Finding #16 (`IndexedDBStorage.remove` never got the transaction-durability fix that `store` did) and finding #4 (AUTH_STATE writer/reader skip on different predicates) are both live divergences produced by this pattern, not hypotheticals.
|
|
128
|
+
|
|
129
|
+
7. **Out of scope but noted (house rule: don't silently catch).** `engine/network/orchestrator/ServerAuthoritativeClient.js:379-386` wraps `rewind_engine.rewind_to` in a `try { … } catch (e) { return; }` that swallows every error, not just the anticipated "log rolled past server_frame". `engine/save/storage/IndexedDBStorage.js:120-125` (`get_bound_address`-style `catch → return null`) and `WebTransportTransport.js:148-155` (three `catch (_) { /* swallow */ }`) are the same shape. Separate concern from duplication.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Domain H — engine (ecs, intelligence, animation, input, control, asset, misc)
|
|
2
|
+
|
|
3
|
+
Source root: `H:/git/moh/app/src/mir-engine/meep/src/`. All paths below are relative to that root.
|
|
4
|
+
Every finding below was verified by opening the cited files.
|
|
5
|
+
|
|
6
|
+
## Findings
|
|
7
|
+
|
|
8
|
+
| # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
|
|
9
|
+
|---|---|---|---|---|---|---|---|
|
|
10
|
+
| 1 | CRITICAL | **Two complete ECS animation-playback systems over three.js `AnimationMixer`**, each with its own component, clip type, flag enum, screen-space culling and debt-time accumulator | `engine/ecs/systems/AnimationSystem.js:179` `AnimationSystem` (+ `engine/ecs/animation/Animation.js:10`, `engine/ecs/animation/AnimationClip.js:6`, `engine/ecs/animation/AnimationClipFlag.js:5`) ⟷ `engine/graphics/ecs/animation/animator/AnimationGraphSystem.js:21` `AnimationGraphSystem` (+ `graph/AnimationGraph.js`, `animator/AnimationClip.js:8`, `animator/AnimationClipFlag.js:5`) | `AnimationSystem.screenSpaceSize:271` vs `AnimationGraphSystem.screenSpaceSize:221` are the same body (`boundingSphere`→`max3(scale)`→`sphere_project`→`* viewport.x * viewport.y`, same `assert.notEqual(cameraMatrix,null,'camera matrix is null')`). `AnimationSystem.shouldEntityBeAnimated:302` vs `AnimationGraphSystem.shouldEntityBeAnimated:181` — same early-outs, same `areaInPixel < 32` magic threshold. Both do `traverseComponents(...)` debt accumulation then `meshSystem.traverseVisible(...)` and both call `CameraSystem.getFirstActiveCamera(ecd)` + `matrixWorld.copy/invert` + `c.fov/180`. | UNJUSTIFIED | `AnimationGraph` (E) is the richer/newer path (blend state, transitions, notifications, declares `components_used`). Extract the shared visibility gate as a typed helper `engine/graphics/ecs/mesh/mesh_screen_space_pixel_area(mesh, cameraMatrixElements, focalLength, viewportW, viewportH)` in `engine/graphics/ecs/mesh/`, called by both; then mark `Animation`/`AnimationSystem` `@deprecated` in favour of `AnimationGraph` (library — do not delete). | M / M — `screenSpaceSize` extraction is mechanical and covered by both systems' behaviour; the deprecation is doc-only |
|
|
11
|
+
| 2 | HIGH | **`AnimationClipFlag` declared twice with the same numeric value `1` and opposite meanings** | `engine/ecs/animation/AnimationClipFlag.js:6` `{ ClampWhenFinished: 1 }` ⟷ `engine/graphics/ecs/animation/animator/AnimationClipFlag.js:9` `{ Repeat: 1 }` | Both are consumed as `clip.flags` on a class named `AnimationClip`; `engine/ecs/systems/AnimationSystem.js:93` reads `clip.getFlag(AnimationClipFlag.ClampWhenFinished)` while `engine/graphics/ecs/animation/animator/AnimationClip.js:48,142` reads `getFlag(AnimationClipFlag.Repeat)`. `engine/ecs/animation/AnimationClip.js:124` writes `flags` as a raw `uint8` to the save stream (`AnimationSerializationAdapter.js:16`), so byte `1` decodes to "clamp" or "repeat" depending only on which import a future reader picks. An import auto-completed to the wrong sibling type-checks, runs, and silently inverts looping. | UNJUSTIFIED | Rename the enums to match their owning systems (`AnimationClipFlag` → `MixerAnimationClipFlag` in `engine/ecs/animation/`, `AnimationGraphClipFlag` in `animator/`), keeping the old names as `@deprecated` re-exports (library surface). | S / L |
|
|
12
|
+
| 3 | HIGH | **Three unrelated classes exported as `AnimationClip`**, plus three as `AnimationTrack` | `engine/animation/clip/AnimationClip.js:1` (name + `AnimationTrack[]`, glTF-shaped, no flags) ⟷ `engine/ecs/animation/AnimationClip.js:6` (`ObservedString` name + `repeatCount`/`weight`/`timeScale` + flags) ⟷ `engine/graphics/ecs/animation/animator/AnimationClip.js:8` (`def` + `weight`/`timeScale` + flags + notification dispatch). Tracks: `engine/animation/clip/AnimationTrack.js:6` ⟷ `engine/animation/keyed2/AnimationTrack.js:8` ⟷ three's `KeyframeTrack` | Overlap is *conceptual, not textual* — none of the three shares a field layout, so these are genuinely three parallel clip models, not copies. `keyed2/AnimationTrack.js:6` documents its own obsolescence: "Consider using `AnimationCurve` instead if you're starting a new project". | UNJUSTIFIED (naming) — not code duplication | Same as #2: disambiguate the exported names, add `@deprecated` on `keyed2/AnimationTrack`. Do **not** merge the data models; they serve different runtimes. | S / L |
|
|
13
|
+
| 4 | HIGH | **Divergent copy of the Gaussian-blur-over-a-sampled-function algorithm — the copy still carries the bug the other one fixed** | `engine/ecs/evaluation/complex/PointFitnessGaussianBlur.js:62` `evaluate` ⟷ `generation/filtering/numeric/complex/CellFilterGaussianBlur.js:93` `execute` | `CellFilter` version precomputes a normalized kernel via `core/math/build_gaussian_kernel_2d.js`, centres it at `(samples-1)*0.5`, forces odd sample counts (`nextOdd`) and derives `sigma = (samples-1)/6`. `PointFitness` version centres at `samplesX / 2` — for `samples=5` the taps land at `-2.5,-1.5,-0.5,0.5,1.5`, i.e. **shifted half a sample off centre**, so the blur biases the result; it also hard-codes `sigma = 10` regardless of sample count (with taps in ±2.5 that is effectively a box filter), normalizes by an accumulated `powerTotal` instead of a normalized kernel, and never calls the existing `build_gaussian_kernel_*` primitives. | UNJUSTIFIED | Port the fixed formulation: use `build_gaussian_kernel_2d`/`_1d` and the `(samples-1)*0.5` centring in `PointFitnessGaussianBlur`. Third-axis (`z`) support means a `build_gaussian_kernel_3d` sibling in `core/math/` is the clean shared path. | M / M — output values change (they are currently wrong); `PointFitnessFunction` has one external consumer (`mir-engine/model/game/logic/combat/scene-selection/SceneAffliction.js`) |
|
|
14
|
+
| 5 | HIGH | **Binary wire format written in two places: the serializer and the version upgrader each re-derive the same header encoding** | `engine/grid/position/GridPositionSerializationAdapter.js:14` `serialize` ⟷ `engine/grid/position/GridPositionSerializationUpdater_0_1.js:11` `upgrade`; and `engine/grid/grid2transform/GridPosition2TransformSerializationAdapter.js:15` `serialize` ⟷ `engine/grid/grid2transform/GridPosition2TransformSerializationUpgrader_0_1.js:12` `upgrade` | The upgraders reproduce the v1 write path byte-for-byte, comments included (`//both coordinates are present`, `//write X`, `//both components are uint`). Changing the v1 encoding in the adapter leaves the upgrader emitting the old bytes into a stream the new deserializer will misread — a silent save-corruption path. Contrast `engine/navigation/ecs/path_following/PathFollowerSerializationUpgrader_3_4.js:8`, which uses `BinaryBuffer.copyUint16/copyFloat32` and never re-derives a format. | UNJUSTIFIED | Extract the v1 writers as typed free functions next to the adapters — `grid_position_write_v1(buffer, x, y)` and `grid_position_2_transform_write_offset_v1(buffer, offsetX, offsetY)` — and call them from both `serialize` and `upgrade`. | S / L |
|
|
15
|
+
| 6 | HIGH | **Two parallel transform-parenting subsystems** (component + deferred binding + parent-transform subscription), one carrying a TODO that points at the other's implementation | `engine/ecs/attachment/Attachment.js:5` + `engine/ecs/attachment/AttachmentSystem.js:100/254` + `engine/ecs/attachment/TransformAttachmentBinding.js:8` ⟷ `engine/ecs/transform-attachment/TransformAttachment.js:17` + `engine/ecs/transform-attachment/TransformAttachmentSystem.js:176/201` + its `UpdateContext:14` | Components are near-identical (`parent:number = -1`, local `transform:Transform`, an `immediate`/`Immediate` flag). Both systems queue an entity until the parent `Transform` exists (`AttachmentSystem.processWaiting:105` / `TransformAttachmentSystem.__queue`), then subscribe to parent-transform change and recompose the child's world transform. `TransformAttachmentBinding.js:10` literally says *"TODO make use of `Transform.multiplyTransforms` for better readability and performance"* — which is exactly what `TransformAttachmentSystem`'s `UpdateContext.update:51` already does. Both are live: `Attachment` via `engine/ecs/speaker/VoiceSystem.js:423` and `engine/graphics/debug/shape_to_visual_entity.js:56`; `TransformAttachment` via `engine/ecs/parent/EntityNode.js:187`. | UNJUSTIFIED | `TransformAttachment` == `Attachment` with no socket. Make `AttachmentSystem`'s `TransformAttachmentBinding.update` delegate to `Transform.multiplyTransforms` (kills the matrix path and the TODO), and route both systems' deferred-parent wait through one typed `PendingParentQueue` class. Longer term the socket-less case should be expressed as `Attachment` with an identity socket. | L / M |
|
|
16
|
+
| 7 | HIGH | **Per-entity link/unlink bookkeeping hand-rolled in ≥9 systems while the engine already ships a typed base class for it — and one hand-rolled copy has drifted into a listener leak** | Hand-rolled: `engine/grid/position/GridPositionSystem.js:47/79/88`, `engine/grid/grid2transform/GridPosition2TransformSystem.js:40/79/87`, `engine/grid/transform2grid/Transform2GridPositionSystem.js:100/119`, `engine/ecs/transform-attachment/TransformAttachmentSystem.js:176/201`, `engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js:88/110`, `engine/ecs/gui/position/ViewportPositionSystem.js:132/167`, `engine/input/ecs/systems/InputSystem.js:113/141`, `engine/graphics/ecs/animation/AnimationControllerSystem.js:18/64`, `engine/sound/ecs/SoundControllerSystem.js:49/103`. Existing solution: `engine/ecs/system/AbstractContextSystem.js:9` + `engine/ecs/system/SystemEntityContext.js` | Same shape everywhere: sparse per-entity store + a binding object subscribed in `link` and unsubscribed in `unlink`. Three incompatible store conventions (`this.data[]`, `this.removers[]`, `Map`) and three binding conventions (closure with a `.clear` property, plain `{update, transform, component}` + a module-level `deregister`, and a class with `link()`/`unlink()`). `GridPositionSystem.reset:88` and `GridPosition2TransformSystem.reset:87` are the same `for (let entity in data) { if (!data.hasOwnProperty(entity)) continue; … sync.clear(); }` body. **Drift already happened:** `ViewportPositionSystem.link:154` adds `el.view.size.onChanged.add(updatePosition, eventContext)` and `unlink:167-181` never removes it — a per-entity listener leak on a live GUI system. Only 7 systems engine-wide use `AbstractContextSystem` (`ParentEntitySystem`, `DynamicActorSystem`, `VoiceSystem`, `LightSystem`, `PathDisplaySystem`, `SpriteSystemPE`, `FPDecalSystem`). | UNJUSTIFIED | Migrate the hand-rolled systems onto `AbstractContextSystem` + a per-system `SystemEntityContext` subclass — already typed, already verbose, no strings/registries/decorators. `Transform2GridPositionSystem.Synchronizer` and `TransformAttachmentSystem.UpdateContext` are `SystemEntityContext` in all but name. Fix the `ViewportPositionSystem` leak as part of the migration. | L / M — one system at a time; each migration is behaviour-preserving and independently testable |
|
|
17
|
+
| 8 | HIGH | **Vendored copy of three.js `KeyframeTrack.optimize()` that diverged from upstream and no longer does anything** | `engine/ecs/animation/AnimationOptimizer.js:16` `optimizeTrack` (called from `engine/asset/loaders/GLTFAssetLoader.js:426` on every loaded glTF animation) | The body is three.js's `KeyframeTrack.optimize`, but `this` here is the `AnimationOptimizer`, not the track — so lines 111-112 write `this.times`/`this.values` **onto the shared module-level optimizer instance** (`GLTFAssetLoader.js:32 const animationOptimizer = new AnimationOptimizer()`) and the track is never modified. It also carries upstream's typo `time !== time[0]` (should be `times[0]`). Net effect per track: two full array copies allocated and discarded. Meanwhile meep has its own, tested curve reducers: `engine/animation/curve/animation_curve_optimize.js:91` and `engine/animation/curve/compression/downsample_float_array_curve_by_error.js`. | UNJUSTIFIED | Either drop the call (it is a pure cost today) or re-target the writes to the track (`track.times = …; track.values = …`) and add a spec. Do not re-vendor: prefer `animation_curve_optimize` semantics once glTF tracks are routed through `engine/animation/clip/`. | S / L (currently a no-op, so "fixing" it changes glTF output — gate behind a test) |
|
|
18
|
+
| 9 | MEDIUM | **`TextureAttachmentsByMaterialType` iteration written five times, twice in the same file** | `engine/asset/loaders/material/traverseMaterialTextures.js:9` (the generic one) ⟷ `engine/asset/loaders/GLTFAssetLoader.js:40` `replace_material_textures_from_cache` ⟷ `engine/asset/loaders/GLTFAssetLoader.js:92` `configureTextures` ⟷ `engine/asset/loaders/material/StaticMaterialCache.js:39` `acquire` ⟷ `engine/graphics/material/manager/compilers/CoalesceTextures.js:25` `transform` | `StaticMaterialCache.acquire:48-89` and `CoalesceTextures.transform:26-66` are byte-identical including the commented-out `//console.log('Re-using texture', existingTexture);`. `traverseMaterialTextures` already exists and is used by three other call sites (`MaterialDescriptor.js:23`, `promiseMaterialLoaded.js:13`, `prototypeLightManager.js:1232`) — the loader/compiler path just never adopted it. A new material type added to `TextureAttachmentsByMaterialType` propagates to all five automatically, but any change to the *null-texture* or *write-back* convention has five edit sites. | UNJUSTIFIED | Route all five through `traverseMaterialTextures(material, cb, thisArg)`. The texture-cache body shared by `StaticMaterialCache.acquire` and `CoalesceTextures.transform` becomes one exported `coalesce_material_textures(material, textureCache)` used by both. | S / L |
|
|
19
|
+
| 10 | MEDIUM | **~70-line terrain-contact probe + influence computation copy-pasted between the two IK solvers** | `engine/ecs/ik/TwoBoneInverseKinematicsSolver.js:80-151` (inside `solve:50`) ⟷ `engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js:80-151` (inside `solve:42`) | Identical sequence, same variable names, same comments: primary `terrain.raycastFirstSync` along the bone direction → `if (!contactExists) return` → secondary cast from the effector back along `-contactNormal` → `if (!contactExists) return` → `v3_displace_in_direction(targetPosition, offset*scale, contact…, normal…)` → `v3_dot(direction…, delta_contact_c…)` side test → `dot < 0 ? influence = 1 : influence = 1 - clamp01(constraint.distance.normalizeValue(inverseLerp(0, scale, len)))` → `if (influence <= 0) return`. Only `limbLength` vs `bone_scale` and the ray origin differ. Both solvers are live via `engine/ecs/ik/InverseKinematicsSystem.js:26-27`. | UNJUSTIFIED | Extract `engine/ecs/ik/ik_probe_terrain_contact(out_target, terrain, origin, effector, direction, constraint, length_scale) → number` returning the influence (`0` = no contact / no influence). Both solvers then keep only their rotation math. | M / L — pure extraction, both solvers already spec-adjacent |
|
|
20
|
+
| 11 | MEDIUM | **Axis-tick-walking algorithm forked; the ceil→floor fix landed in only one copy** | `engine/animation/curve/editor/canvas2dDrawWorldAxisLabels.js:17` ⟷ `engine/graphics/canvas/canvas2d_draw_linear_scale.js:18` | Same prologue verbatim (`if (spacing <= 0) throw`, `delta_x/delta_y`, `v2_length`, `if (distance === 0) throw`, `direction_x/direction_y`). `canvas2d_draw_linear_scale.js:44-46` carries the fix *and the explanation*: "using ceil would place a final tick past position_1 with an extrapolated value" → `Math.floor`. `canvas2dDrawWorldAxisLabels.js:44` still has `Math.ceil` and therefore still draws that extrapolated final tick. They also use two different number formatters (`formatTruncDecimal` vs `core/primitives/numbers/number_pretty_print`). | UNJUSTIFIED | One `canvas2d_draw_linear_scale` taking an explicit `value_step` (uniform control flow — always supply the step; callers compute `(value_1-value_0)/mark_count` when they want a fitted scale). Delete the `Math.ceil` variant's body and re-export. | S / L |
|
|
21
|
+
| 12 | MEDIUM | **A second 2D chart-plotting toolkit under `engine/animation/curve/editor/`, forked from `engine/graphics/canvas/`** | `engine/animation/curve/editor/canvas2dPlotCurveLine.js:17` ⟷ `engine/graphics/canvas/canvas2d_plot_data_line.js:16`; supporting pairs `canvas2dDrawWorldGrid.js` ⟷ `canvas2d_draw_grid.js`, `canvas2dDrawWorldAxisLabels.js` ⟷ `canvas2d_draw_linear_scale.js`, `canvas2dPlotCurvePoints.js` ⟷ `canvas2d_plot_line_array.js` | The two plot functions share the same skeleton and the same hard-coded palette: `'#222222'` background fill, two grid passes at `'#262626'` then `'#303030'` spacing 32, axis at `height - 4` and `x = 4`, `strokeStyle = '#00ff00'`, `lineWidth = 1`. A third copy of the same palette+grid pass is inlined at `engine/animation/curve/view/AnimationCurveView.js:159-203`. Style split (`snake_case` vs `camelCase`) marks the fork. `canvas2dPlotCurvePoints.js:1` also imports `Vector2` **from `three`** inside a pure-2D canvas helper — a vendor-boundary leak. | UNJUSTIFIED | Keep `engine/graphics/canvas/*` as the toolkit. The genuinely new capability in the editor fork is *world-space* grid/labels (`canvas2dDrawWorldGrid` handles a value range, `canvas2d_draw_grid` is screen-space only) — promote that as `canvas2d_draw_grid_world` alongside the existing helpers, then make the editor call the shared functions. Fix the `three` import to `core/geom/Vector2`. | M / M |
|
|
22
|
+
| 13 | MEDIUM | **Three curve-space ↔ canvas-space mapping implementations inside the same feature** | `engine/animation/curve/view/AnimationCurveView.js:120/137` `point_curve_to_canvas` / `point_canvas_to_curve` (3×3 matrix + inverse) ⟷ `engine/animation/curve/draw/position_curve_to_canvas.js:12` / `position_canvas_to_curve.js:12` (size/frame/margin, allocates a `Vector2` per call) ⟷ `engine/animation/curve/editor/canvas2dPlotCurvePoints.js:32-60` (inline `axisSize`/`offset`/`inverseTimeRange` normalization) | All three map `(time, value) ↔ (px, py)` for the same `AnimationCurve` under the same margin convention. The `draw/` pair is used by `build_tangent_editor.js:127`, `displayMousePos.js:17`, `isInjectedKeyframeInBounds.js:21/56`, `createKeyframeDraggableAspect.js:64`, `createPanTool.js:30`; the matrix pair only by `AnimationCurveView`; the inline one only by the plot. A margin/anchoring change has to be made three ways or the editor's hit-testing desyncs from what is drawn. | UNJUSTIFIED | Keep the matrix formulation (`AnimationCurveView`'s `m3_cm_compose_transform` + `m3_invert`) — it is the only one that is exactly invertible — and expose it as a small typed `CurveViewport` class in `engine/animation/curve/` with `curve_to_canvas(out, x, y)` / `canvas_to_curve(out, x, y)` writing into caller-supplied arrays (kills the per-call `Vector2` allocation too). | M / M |
|
|
23
|
+
| 14 | MEDIUM | **Mouse-parallax 3D tilt math copy-pasted, and the copy's pivot silently diverged** | `engine/ecs/gui/parallax/GuiElementParallaxSystem.js:36` `updateEntity` ⟷ `engine/ui/bindings/processors/DomElementProcessorTilt3D.js:56` `handleFrame` | Identical body down to the variable names and the CSS write (`transformOrigin = '50% 50%'`, `` transform = `rotate3d(${tilt_x},${tilt_y},0,${angle}deg)` ``). `GuiElementParallaxSystem.js:53-54` computes `center_x`/`center_y` from `getBoundingClientRect()` **and never uses them** — line 60-61 takes the delta against the *viewport* centre instead, so the ECS version tilts every element identically regardless of where it sits on screen. `DomElementProcessorTilt3D.js:82-83` uses the element centre (the evidently intended behaviour). The unused pair is the copy-paste fingerprint. | UNJUSTIFIED | Extract `engine/ui/apply_pointer_tilt_3d(el, pointerX, pointerY, viewportW, viewportH, angle, depth)` and call it from both. Decide the pivot once (element centre) — that also removes the dead `getBoundingClientRect()` call from the ECS path. | S / L |
|
|
24
|
+
| 15 | MEDIUM | **`AnimationController` and `SoundController` are the same rule-driven event-binding system twice** | `engine/graphics/ecs/animation/AnimationControllerSystem.js:18` `link` / `:64` `unlink` ⟷ `engine/sound/ecs/SoundControllerSystem.js:49` `link` / `:103` `unlink` | `unlink` is token-identical (`this.removers[entity]` → `delete` → `callbacks.forEach(cb => dataset.removeEntityEventListener(entity, cb.name, cb.handler))`). `link` differs only in the payload built inside the start handler (`new AnimationClip()` + `animation.clips.add` vs `new SoundTrack()` + `emitter.tracks.add`); the surrounding rule-walk, the optional `stopEvent` registration and the `callbacks.push({name, handler})` bookkeeping are identical, comment placement included. | UNJUSTIFIED | Fold both onto `AbstractContextSystem` (finding #7) with a per-system `SystemEntityContext` subclass; the `{startEvent, stopEvent}` rule subscription becomes a shared typed `EntityRuleBinder` holding the `{name, handler}[]` and its teardown. The two payload builders stay as distinct typed methods. | M / L |
|
|
25
|
+
| 16 | MEDIUM | **`removeEntityWithMeshParticlesEffect` reimplements `removeEntityWithEffect` and drops the transient-save guard** | `engine/animation/removeEntityWithMeshParticlesEffect.js:150` ⟷ `engine/animation/AnimationUtils.js:168` `removeEntityWithEffect` (used by `engine/animation/removeEntityGenericEffect.js:150`) | Same skeleton: same `removeComponentsExcept(ecd, entity, [Mesh, ParticleEmitter, Trail2D, Transform])` list, same `stopEntityAndNotifyWhenStopped(entity, ecd).then(() => ecd.removeEntity(entity))` tail. `removeEntityWithEffect:179` adds `SerializationMetadata.Transient` so a dying entity cannot land in a save; the mesh-particles copy never does — an entity mid-death-effect is serializable there. | UNJUSTIFIED | Have `removeEntityWithMeshParticlesEffect` call `removeEntityWithEffect`, passing the already-built emitter (it only needs the extra `distributeParticlesOnObject3D` step before the call). | S / L |
|
|
26
|
+
| 17 | MEDIUM | **`setFlag`/`clearFlag`/`writeFlag`/`getFlag` quartet copied into 34 classes, with the `getFlag` predicate already diverged** | 34 files carry the identical quartet, e.g. `engine/ecs/animation/Animation.js:64-97`, `engine/ecs/gui/GUIElement.js`, `engine/ecs/speaker/Voice.js`, `engine/ecs/transform/Transform.js`, `engine/navigation/ecs/path_following/PathFollower.js`, `engine/grid/grid2transform/GridPosition2Transform.js`, `engine/asset/AssetRequest.js`, `core/events/signal/Signal.js`, `view/View.js`. Divergent `getFlag`: `engine/ecs/animation/AnimationClip.js:62`, `engine/ecs/Entity.js:142`, `core/math/lookup/ParameterLookupTable.js:82` | 34 sites use `return (this.flags & flag) === flag;` (all-bits-of-the-mask); 3 use `!== 0` (any-bit). Equivalent for single-bit flags, **opposite for composite masks** — and composite masks exist (`engine/ecs/animation/Animation.js:40` seeds `flags = MeshSizeCulling \| Playing`). Nothing documents which convention a given class follows. | UNJUSTIFIED (in logic, not merely shape — the predicate semantics disagree) | Two typed pieces, no metaprogramming: (a) free functions in `core/binary/bitfield/` (`bitfield_test_all`, `bitfield_test_any`) so intent is named at each call site; (b) a concrete `BitFlagCarrier` base class in `core/model/` for the ~25 classes with no other superclass. At minimum, unify the 3 outliers on the documented convention. | M / M — mechanical but wide; a codemod plus one spec asserting composite-mask behaviour per class |
|
|
27
|
+
| 18 | MEDIUM | **Two parallel input-binding ECS subsystems, one deprecated only in a JSDoc line** | `engine/input/ecs/systems/InputSystem.js:92` + `engine/input/ecs/components/Input.js` + `engine/input/ecs/ism/InputBinding.js:4` (path → event name, `ecd.sendEvent`) ⟷ `engine/input/ecs/systems/InputControllerSystem.js:186` + `engine/input/ecs/components/InputController.js` + `engine/input/ecs/components/InputBinding.js:3` (path → listener fn, priority/exclusive proxy dispatch) | `InputControllerSystem.js:184` says "Prefer to use {@link InputSystem} instead" but the class is otherwise undecorated and fully live. Both resolve the device signal identically via `resolvePath(devices, path)` and subscribe/unsubscribe. Within `InputSystem.js`, `linkBinding:14` and `unlinkBinding:29` are a 14-line verbatim pair differing only in `signal.add` vs `signal.remove` — all three assert messages duplicated. | UNJUSTIFIED (the `linkBinding`/`unlinkBinding` pair); the two subsystems are a migration in progress | Collapse `linkBinding`/`unlinkBinding` into one `resolve_device_signal(devices, path) → Signal` (the asserts live there) with `.add`/`.remove` at the two call sites. Add `@deprecated` to `InputControllerSystem`, `InputController` and `engine/input/ecs/components/InputBinding` so the JSDoc note is machine-visible. | S / L |
|
|
28
|
+
| 19 | MEDIUM | **JSON-derived asset loaders repeat the same scaffold, and all of them lose parse errors** | `engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js:7` ⟷ `engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js:7` ⟷ `engine/asset/loaders/JavascriptAssetLoader.js:22` | All three: `await this.assetManager.promise(path, GameAssetType.X, {scope, progress})` → `.create()` → `new Asset(() => parse(json), 1)` → `success(asset)`. None passes `failure`, so a throw out of the awaited call or out of the parser rejects the async `load()` into an unobserved promise — compare `GLTFAssetLoader.js:441`, which routes explicitly (`.then(success, failure)`) with a comment explaining exactly this hazard. | UNJUSTIFIED | A typed `DerivedAssetLoader extends AssetLoader` with an abstract `sourceType` getter and `build(json) → *`, whose `load` owns the `try/catch → failure(e)` wiring once. Verbose, typed, one subclass per asset kind. | S / L |
|
|
29
|
+
| 20 | LOW | **Three loaders hand-roll a structural stand-in for the existing `Asset` class** | `engine/asset/loaders/TextAssetLoader.js:7`, `engine/asset/loaders/JsonAssetLoader.js:19`, `engine/asset/loaders/SVGAssetLoader.js:14` | Each builds `const asset = { create: function () { return X; } }` instead of `new Asset(factory, byteSize)`. `Asset` (`engine/asset/Asset.js`) carries `byteSize`, `dependencies` and the scope machinery the `AssetManager` overhaul introduced; the duck-typed objects report no weight to the cache. | UNJUSTIFIED | `new Asset(() => data, data.length)` in all three. | S / L |
|
|
30
|
+
| 21 | LOW | **`Quaternion.fromAxisAngle` and `Quaternion.lookRotation` reimplemented module-privately** | `engine/input/ecs/util/TopDownCameraControllerHelper.js:21` `CreateFromAxisAngle` and `:32` `lookAt` ⟷ `core/geom/Quaternion.js:335` `fromAxisAngle` / `:217` `lookRotation`, and `core/geom/3d/quaternion/quat3_from_axis_angle.js:7` | `quat3_from_axis_angle.js` carries the JSDoc line "just in case you need that function also"; `TopDownCameraControllerHelper.js:20` carries the *same stray comment* as a `//` line — proof of a copy chain. The local `lookAt` also hard-codes `3.1415926535897932` and two `0.000001` epsilons instead of `core/math/PI.js` / `core/math/EPSILON.js`, and unlike `Quaternion.lookRotation` (which has a spec suite at `core/geom/Quaternion.spec.js:274-297`) it takes no up-vector. | UNJUSTIFIED | Delete the two module-private functions; call `Quaternion.prototype.fromAxisAngle` / `lookRotation`. (`core/geom/3d/quaternion/quat3_createFromAxisAngle.js` is a correctly-documented `@deprecated` alias — leave it.) | S / L |
|
|
31
|
+
| 22 | LOW | **Quaternion-rotate-vector inlined in a GUI system** | `engine/ecs/gui/hud/HeadsUpDisplaySystem.js:92-106` (inside `__apply_entity_transformation:75`) ⟷ `core/geom/vec3/v3_quaternion_apply.js`, `core/geom/Vector3.js`, `core/geom/Vector4.js` | The `ix/iy/iz/iw` two-Hamilton-product expansion is reproduced verbatim. `v3_quaternion_apply(out, out_offset, vx, vy, vz, …)` already writes into a caller-supplied array, so the usual "avoid an object allocation" defence does not apply. | UNJUSTIFIED | Call `v3_quaternion_apply` with a module-static scratch `Float64Array(3)`. | S / L |
|
|
32
|
+
| 23 | LOW | **Cubic easing functions redefined module-privately instead of joining the engine's easing enum** | `engine/control/first-person/abilities/Mantle.js:9` `easeInOutCubic`, `:21` `easeOutCubic`, `:30` `easeInCubic` ⟷ `engine/animation/TransitionFunctions.js:7` (`Linear`, `Sine`, `EaseIn`, `EaseOut`, `EaseInOut`, `CubicEaseIn`) | `TransitionFunctions` is the engine-wide easing enum (11 consumers across `achievements`, `ui`, `ecs/gui`, `ecs/speaker`, `animation`). Cubic in/out/in-out is exactly what it is missing, so the next consumer will write a fourth copy. A parallel *curve*-valued easing set also exists at `engine/animation/curve/preset/CURVE_EASE_IN.js` / `_OUT` / `_IN_OUT` — the same three concepts as `AnimationCurve` objects. | PROMOTION | Add `CubicEaseIn`/`CubicEaseOut`/`CubicEaseInOut` to `TransitionFunctions` and have `Mantle.js` import them. Cross-reference `TransitionFunctions.EaseIn` ↔ `CURVE_EASE_IN` in JSDoc so the two representations are discoverable as a pair. | S / L |
|
|
33
|
+
| 24 | LOW | **The same 6-stop heatmap palette declared twice** | `engine/ecs/terrain/util/paintTerrainOverlayViaLookupTable.js:11-20` ⟷ `engine/graphics/render/forward_plus/debug/createScreenGrid.js:180-189` | Identical `new ParameterLookupTable(4)` + the same six RGBA rows (`0,0,0,255` … `255,5,5,255`) + `computeUniformPositions()`. A palette change makes the terrain overlay and the froxel debug grid disagree. | PROMOTION | `core/math/lookup/LUT_HEATMAP.js` exporting a frozen shared `ParameterLookupTable`. | S / L |
|
|
34
|
+
| 25 | LOW | **Graphviz DOT preamble duplicated across graph dumpers** | `engine/intelligence/behavior/behavior_to_dot.js:240-257` ⟷ `core/bvh8/bvh8_convert_to_dot.js:110-122`; also `core/graph/convert_graph_to_dot_string.js:27`, `core/model/node-graph/util/convert_node_graph_to_dot_string.js:43`, `engine/graphics/render/frame_graph/FrameGraph.js:543` | The `font = {name:"helvetica", size:10}` object and the `graph [style=invis, rankdir="TB" ordering=out, splines=spline]` + `node [shape=record, fontname=…, fontsize=…, margin="0.2,0.03", fillcolor=ivory]` attribute lines are byte-identical between the behavior and BVH8 dumpers. Five emitters, five copies of the styling contract. | PROMOTION | `core/graph/dot/dot_graph_begin(line_builder, name)` / `dot_graph_end(line_builder)` in `core/`, carrying the default node/edge styling. | S / L |
|
|
35
|
+
| 26 | LOW | **Third binary-search-over-sorted-keys implementation** | `engine/animation/curve/AnimationCurve.js:~228` (index search inside the keyframe lookup) ⟷ `engine/animation/async/table_find_min_index_in_ordered_column.js:10` ⟷ `core/collection/array/binarySearchLowIndex.js:10` | Same `(min + max) >>> 1` loop three ways, with three different out-of-range answers: `binarySearchLowIndex` returns `-1` below the range, `table_find_min_index_in_ordered_column` returns `min2(minIndex, maxIndex)`, `AnimationCurve` pre-clamps to `0` / `key_count - 1`. The table variant is legitimately different (it indexes a `RowFirstTable`, not an array). | UNJUSTIFIED for the `AnimationCurve` copy; JUSTIFIED for the table variant (different storage) | `AnimationCurve` can call `binarySearchLowIndex(keys, time, compare_time_to_keyframe)` with a module-static comparator (no per-call closure). Document the differing out-of-range contracts in all three JSDocs regardless. | S / L |
|
|
36
|
+
| 27 | LOW | **Composite-behavior children wire format written twice** | `engine/intelligence/behavior/composite/SequenceBehaviorSerializationAdapter.js:19/37` ⟷ `engine/intelligence/behavior/composite/ParallelBehaviorSerializationAdapter.js:19/42` | Both encode the child list as `writeUintVar(n)` + N `objectAdapter.serialize`, and decode as `readUintVar()` + `clearChildren()` + N `addChild`. `ParallelBehavior` merely prefixes two policy bytes. Only two adapters exist today, so the blast radius is small — but a change to the child-list encoding needs both. | UNJUSTIFIED (marginal) | A typed `CompositeBehaviorSerializationAdapter extends ObjectBasedClassSerializationAdapter` exposing `serializeChildren(buffer, value)` / `deserializeChildren(buffer, value)`; the two concrete adapters keep their own `serialize`/`deserialize` and call them. | S / L |
|
|
37
|
+
| 28 | LOW | **`1.0 + (raw - 1.0) * flatBoost` — `lerp` re-derived four times** | `engine/control/first-person/mastery/StrideTimingJumpEvaluator.js:95`, `SlideInitiationTimingEvaluator.js:81`, `BreathRhythmEvaluator.js:131`, `FootAsymmetryTurnEvaluator.js:107` | The identical "scale the deviation around 1.0" expression, each with its own 2–4 line comment explaining it. `core/math/lerp.js` exists and is spec'd. | UNJUSTIFIED (cosmetic) | `lerp(1.0, raw, this.flatBoost)`, with the explanation moved to `MasteryEvaluator`'s class JSDoc where the convention is already documented. | S / L |
|
|
38
|
+
| 29 | LOW | **Cross-domain check — `engine/ecs/ik` correctly reuses the physics two-bone solver** | `engine/ecs/ik/TwoBoneInverseKinematicsSolver.js:11` imports `two_joint_ik` from `engine/physics/inverse_kinematics/two_joint_ik.js`; live via `engine/ecs/ik/InverseKinematicsSystem.js:26` | Verified: there is **no** second two-bone IK implementation on the ECS side — the solver delegates. Logged so the physics-domain agent does not double-report it. | JUSTIFIED (correct reuse) | none | — |
|
|
39
|
+
|
|
40
|
+
## Dismissed leads
|
|
41
|
+
|
|
42
|
+
- **`PointFitnessBinary.evaluate:29` ⟷ `CellFilterBinaryOperation.execute:31`** — same template-method shape, but different hierarchies with different sample signatures (`(ecd,x,y,z)` vs `(grid,x,y,rotation)`); no shared base is viable. Shape-only.
|
|
43
|
+
- **`PointFitnessAdd.from` / `PointFitnessMultiply.from` (+ 10 more in the [49x12] group)** — per-class typed factories identical in shape. Explicitly deliberate per house rules.
|
|
44
|
+
- **`CompositeBehavior.addChildren:36` ⟷ `Tag.addAll:59` ⟷ `Task.addDependencies:187`** — 5-line "for-each call the singular add" over three unrelated types. Too small to share; extracting would need a callback and lose the typing.
|
|
45
|
+
- **`AnimationOptimizer.optimize:123` ⟷ `IncrementalDeltaSet.pushOther:310`** — the mechanical match is only the `for (i…) { … method(item) }` loop. The *real* problem with `AnimationOptimizer` is finding #8.
|
|
46
|
+
- **`BehaviorComponent.from:121` ⟷ `KillBehavior.create:23`** — assert-then-construct factories on unrelated classes. (Worth noting only that the codebase uses `from` and `create` interchangeably for the same idiom.)
|
|
47
|
+
- **`makeDefaultSlideInitiationTimingCurve:21` ⟷ `makeDefaultStrideTimingJumpCurve:18`** — identical construction *shape* but the four keyframe values are authored design data with different documented intent. Correctly built on the existing `AnimationCurve`/`Keyframe` types.
|
|
48
|
+
- **`Voice`** declared in `engine/ecs/speaker/Voice.js` and `engine/sound/sopra/runtime/Voice.js` — completely different concepts (speech-bubble ECS component vs. a pooled WebAudio source). Name collision only, no shared code.
|
|
49
|
+
- **`InputBinding`** declared in `engine/input/ecs/components/` and `engine/input/ecs/ism/` — different data models (path→callback vs path→event-name). Rolled into finding #18 as the deprecation half; not itself duplicated code.
|
|
50
|
+
- **`canvas2dDrawWorldGrid` ⟷ `canvas2d_draw_grid`** — the world-space variant genuinely handles a value range the screen-space one does not. Covered as the promotion half of #12, not as duplication.
|
|
51
|
+
- **`PoseInterpolationAdapter.interpolate:17`** (7 inline lerps + inline nlerp) — deliberate scalar-register hot path over a `BinaryBuffer`; using `Vector3`/`Quaternion` would force reads into objects. Justified.
|
|
52
|
+
- **`HeadsUpDisplaySystem` ⟷ `ViewportPositionSystem`** — the HUD system writes into `ViewportPosition` and lets `ViewportPositionSystem.setElementPosition:197` do the DOM placement. Correct layering, no duplication.
|
|
53
|
+
- **`prototype_first_person_controller.js` ⟷ `prototypeAcousticSolver.js`** (`makePhysicsGroundResolver`, `buildInputBindings`, `recomputeMove`, `bindMoveKey`, `logHelp`) — both are `prototype*.js` browser harnesses, out of scope.
|
|
54
|
+
- **`table_find_min_index_in_ordered_column`** as a duplicate of `binarySearchLowIndex` — justified: it indexes a `RowFirstTable`, not an array. Only the `AnimationCurve` copy is flagged (#26).
|
|
55
|
+
- **`GridPositionSerializationAdapter.serialize` ⟷ `GridPositionSerializationUpdater_0_1.upgrade` as "shape duplication"** — *not* dismissed; this is finding #5. Listed here only because the mechanical brief filed it as a MinHash near-match rather than as a format-invariant hazard.
|
|
56
|
+
- **`removeEntityGenericEffect.js` / `removeEntityWithMeshParticlesEffect.js` embedded emitter JSON** (~130 lines each, `cloud-puff` vs `mesh-cloud-puff-0`) — duplicated *data*, not logic; the two presets have genuinely different tuning. Belongs in `data/` but is not a reuse failure.
|
|
57
|
+
- **`engine/interpolation/**` vs `core/math` easing** — no duplicate easing library there; `engine/interpolation/` is snapshot interpolation (`InterpolationLog`, `Interpoland`, binary pose adapters), unrelated to easing curves. The real easing duplication is #23.
|
|
58
|
+
- **`binary_search_inline` / `inline_clamp_ternary` hits under `engine/asset/loaders/**`** — checked: `image/png/crc32.js:24` and `png_filter_unFilterNone.js:42` are bit-shifts inside PNG codecs, not searches; `computeMassRatios.js:30`'s `Math.max(0, Math.min(1, x))` is one site and `clamp01` exists, but it is cold init code (LOW-of-LOW, folded into no row).
|
|
59
|
+
|
|
60
|
+
## Notes
|
|
61
|
+
|
|
62
|
+
Structural reasons duplication concentrates in this domain:
|
|
63
|
+
|
|
64
|
+
1. **Three generations of animation coexist with no deprecation markers on the older two.** `engine/animation/keyed2/` (multi-property tracks + per-key transition functions, the only one the GUI/notifications/achievements actually use) → `engine/animation/curve/` (`AnimationCurve` + Hermite `Keyframe`, self-declared as the successor in `keyed2/AnimationTrack.js:6`) → `engine/animation/clip/` (glTF-shaped clip/track built on `AnimationCurve`). Orthogonally, **two** ECS animation runtimes drive three.js (`engine/ecs/animation/` + `AnimationSystem` vs `engine/graphics/ecs/animation/animator/` + `AnimationGraphSystem`). Five layers, one namespace: three exported `AnimationClip`, three `AnimationTrack`, two `AnimationClipFlag`. Because meep is a library, nothing was deleted during each migration — but nothing was marked `@deprecated` either, so each generation keeps attracting new code and each new consumer picks whichever name resolves first.
|
|
65
|
+
|
|
66
|
+
2. **The engine already solved the ECS lifecycle problem and then only half-adopted the solution.** `AbstractContextSystem` + `SystemEntityContext` is exactly the typed, verbose, no-magic answer the house rules ask for — and only 7 systems use it. Nine more in this domain hand-roll the same store + binding + teardown, in three mutually incompatible conventions. That is where the drift shows up: `ViewportPositionSystem` subscribes six things and unsubscribes five.
|
|
67
|
+
|
|
68
|
+
3. **Serialization upgraders re-derive formats instead of copying fields.** The `BinaryBuffer.copyUint16/copyFloat32` idiom (used correctly by the `PathFollower` upgrader chain) makes an upgrader immune to later format changes. The two `grid/` upgraders instead paste the serializer's write path, which couples every future format edit to two files that no test compares.
|
|
69
|
+
|
|
70
|
+
4. **Fork-then-fix, never back-port.** Four independent instances of the same failure mode: `CellFilterGaussianBlur` fixed a centring/normalization bug that `PointFitnessGaussianBlur` still has; `canvas2d_draw_linear_scale` fixed a `ceil`→`floor` tick bug (with a comment explaining it) that `canvas2dDrawWorldAxisLabels` still has; `TransformAttachmentSystem` adopted `Transform.multiplyTransforms` while `TransformAttachmentBinding` still carries a TODO to do so; `removeEntityWithEffect` added the `SerializationMetadata.Transient` guard that `removeEntityWithMeshParticlesEffect` lacks. In each pair the *newer* file is correct, which means the duplication is actively hiding fixes rather than merely wasting lines.
|
|
71
|
+
|
|
72
|
+
5. **`snake_case` vs `camelCase` marks the fault lines.** `engine/graphics/canvas/canvas2d_*` vs `engine/animation/curve/editor/canvas2d*` is the clearest case — the naming split is a reliable indicator of a fork, and searching for one convention never surfaces the other.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Domain I — generation + view + editor
|
|
2
|
+
|
|
3
|
+
Scope: `meep/src/generation/**`, `meep/src/view/**`, `meep/editor/**`.
|
|
4
|
+
Every row below was verified by opening the cited files. `*.spec.js`, `prototype*.js`, `test*.js` excluded.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Findings — generation
|
|
9
|
+
|
|
10
|
+
| # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
|
|
11
|
+
|---|---|---|---|---|---|---|---|
|
|
12
|
+
| G1 | HIGH | Uniform-cost grid BFS/Dijkstra (open heap + `closed` BitSet + distance relaxation) written three times; the `inOpen` membership-mirror optimisation landed in only two of them | `src/generation/grid/generation/util/buildDistanceMapToObjective.js:15` `buildDistanceMapToObjective` (relax 85–92) · `src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js:66` `build` (heap 93, `inOpen` 107, `processNeighbor` 145, expansion 193–208) · `src/generation/grid/generation/road/GridTaskGenerateRoads.js:104` `buildPaths` (heap 121, `inOpen` 129, relax 282–297) | The JSDoc comment *"membership mirror of the open heap, O(1) replacement for linear heap.contains scans"* appears **verbatim** at `GridTaskBuildSourceDistanceMap.js:104` and `GridTaskGenerateRoads.js:126` — a copy-paste of a performance fix. `buildDistanceMapToObjective` never received it; it dodges the problem with a different heap (`Uint32Heap.insert_or_update`) instead. Three different heap types (`Uint32Heap`, `BinaryHeap`, `BinaryHeap`), three different relaxation shapes, one algorithm. `GridTaskBuildSourceDistanceMap` additionally hardcodes the 4-neighbourhood as index arithmetic (`±1`, `±width`) instead of a mask, so it silently cannot be switched to 8-way | UNJUSTIFIED | One `grid_build_distance_field({distances, open, closed, grid, neighbourhoodMask, traversable, onVisit})` in `generation/grid/generation/util/`, built on `Uint32Heap` (which already has `insert_or_update`, removing the need for the mirror entirely). `buildDistanceMapToObjective` becomes a thin `onVisit` wrapper; the road builder passes its heuristic-free variant | Medium effort / Medium risk — three call sites with different early-exit semantics; needs a generation-output regression test |
|
|
13
|
+
| G2 | MEDIUM | 2D rotation of a local offset by `rotation` radians, inlined 8× — `core/geom/vec2/` has **no** `v2_rotate` | `src/generation/filtering/numeric/util/CellFilterDisplaced.js:33-40` `execute` · `src/generation/markers/GridActionRuleSet.js:176-180` `process` · `src/generation/markers/GridCellActionPlaceMarker.js:156-167` `buildBaseNode` · `src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.js:34-41` `transform` · `src/generation/placement/action/GridCellActionPlaceTags.js:87-103` `execute` · `src/generation/placement/action/util/GridCellDisplacedAction.js:41-48` `execute` · `src/generation/placement/GridCellPlacementRule.js:104-113` `execute` · `src/generation/rules/cell/CellMatcherGridPattern.js:94-107` `match` | Byte-identical four-line body at all 8 sites (`const sin = Math.sin(rotation); const cos = Math.cos(rotation); const rotated_local_x = local_x * cos - local_y * sin` / `..._y = local_x * sin + local_y * cos;`) — even the missing semicolon after `* sin` is reproduced everywhere. `core/geom/vec2/` contains only `v2_matrix3_cm_multiply`/`_rm_multiply`; there is no rotate-by-angle primitive. Downstream handling then diverges: `CellMatcherGridPattern:109` and `GridCellActionPlaceTags:106` `Math.round` the result, the other six do not | PROMOTION | `core/geom/vec2/v2_rotate.js` — `v2_rotate(result, result_offset, x, y, angle)` (result-first, matching the house convention), plus `v2_rotate_cos_sin` for the `GridActionRuleSet` loop that rotates many offsets by the same angle. Note in JSDoc that callers own the rounding decision | Low effort / Low risk — pure extraction, no behaviour change |
|
|
14
|
+
| G3 | MEDIUM | Weighted random selection over `CellFilter` weights implemented three times, one bypassing `random_pick_weighted_index` with a different prefix-sum convention | `src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.js:56` `execute` · `src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js:78` `execute` · `src/generation/theme/ThemeEngine.js:411` `applyNodes` (454–485) | Sites 1 and 2 are the same 30-line body — module-scope `evaluatedWeights` array, `weightFilter.execute(...)`, `if (weightValue > 0) totalWeight += weightValue`, identical `//if the weight is negative, we clamp it to 0` comment, `random_pick_weighted_index(...)`, identical `index === -1` early-out. Only the position source differs (`node.position.x/y` vs `x,y`). Site 3 hand-rolls the same selection with an **exclusive** prefix sum (`themeInfluence[j] = influenceSum;` *before* `influenceSum += influence`, line 469–471) and `binarySearchLowIndex`, where the core primitive uses an **inclusive** prefix sum and `binarySearchHighIndex`. Both are individually correct, but the two conventions cannot be swapped, so a fix to one never transfers | UNJUSTIFIED | Extract `cell_filter_weights_pick(random, weightedElements, grid, x, y)` in `generation/filtering/` returning the picked index (owning the CDF scratch, the negative-weight clamp and the `-1` contract). Sites 1 and 2 call it; site 3 switches to `random_pick_weighted_index` with the inclusive convention | Low effort / Low risk — three specs already exist |
|
|
15
|
+
| G4 | MEDIUM | Sobel 3×3 tap + gradient magnitude duplicated verbatim; the shared helper already exists next door and is not used | `src/generation/filtering/numeric/complex/CellFilterSobel.js:35` `execute` (39–55) · `src/generation/filtering/numeric/process/computeCellFilterGradient.js:12` `computeCellFilterGradient` (15–31) | Lines 39–55 of `CellFilterSobel.execute` and lines 15–31 of `computeCellFilterGradient` are identical character-for-character including the `//read surrounding points`, `// compute gradients` and `//normalize vector` comments and the `2.0 *` literals. `CellFilterSobel` keeps the magnitude and discards the direction; `computeCellFilterGradient` keeps the direction and discards the magnitude. The sibling family got this right: `CellFilterCurvature` and `CellFilterAngleToNormal` both delegate to `process/computeFilterSurfaceNormal.js` | UNJUSTIFIED | Add `generation/filtering/numeric/process/computeCellFilterSobel.js` writing raw `dX, dY` into a `result` pair. `CellFilterSobel.execute` returns `v2_length(dX, dY)`; `computeCellFilterGradient` divides by it. Both `Math.sqrt(dX*dX + dY*dY)` sites also bypass `core/geom/vec2/v2_length` | Low effort / Low risk |
|
|
16
|
+
| G5 | MEDIUM | The 4-way and 8-way grid neighbourhood masks are re-declared per class, while `ORTHOGONAL_NEIGHBOURHOOD_MASK` exists with zero importers and one copy is dead | `src/engine/grid/ORTHOGONAL_NEIGHBOURHOOD_MASK.js:6` · `src/generation/grid/generation/discrete/GridTaskConnectRooms.js:44` `neighbourhoodSearchMask` and `:57` `neighbourhoodDrawMask` · `src/generation/grid/generation/road/GridTaskGenerateRoads.js:456` `neighbourhoodSearchMask` and `:469` `neighbourhoodDrawMask` | The two `neighbourhoodSearchMask` fields are element-for-element `[0,-1, -1,0, 1,0, 0,1]` — identical to `ORTHOGONAL_NEIGHBOURHOOD_MASK`, which a repo-wide grep shows has **no importer at all**. The two `neighbourhoodDrawMask` fields hold the same 8 offsets in different orders (`ConnectRooms`: `-1,-1 / 0,-1 / 1,-1 …`; `GenerateRoads`: `-1,-1 / 1,-1 / 0,-1 …`) — and `GridTaskGenerateRoads.neighbourhoodDrawMask` is **never read** (grep: only the declaration), i.e. it is a stale copy carried over from `GridTaskConnectRooms` | UNJUSTIFIED (constants only — see Dismissed for the loop bodies) | Import `ORTHOGONAL_NEIGHBOURHOOD_MASK` for the 4-way case and add a sibling `MOORE_NEIGHBOURHOOD_MASK` next to it for the 8-way case; delete the dead `neighbourhoodDrawMask` on `GridTaskGenerateRoads`. Keep them as per-instance overridable fields defaulting to the shared constants | Low effort / Low risk |
|
|
17
|
+
| G6 | MEDIUM | Rasterising a `CellFilter` into a `Sampler2D` at a scale is written twice | `src/generation/filtering/numeric/CellFilterCache.js:37` `initialize` (55–68) · `src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js:9` `populateSampler2DFromCellFilter` (17–35) | Same nested `for y / for x`, same `grid_x`/`grid_y` scale division, same `writeChannel(x, y, 0, v)`. Only the scale is expressed differently (`1/this.scale` vs `result.width / grid.width` — algebraically the same when the sampler is `scale ×` the grid). `populateSampler2DFromCellFilter` also carries the `generation/COORDINATES.md` ratio-mapping comment that `CellFilterCache` lacks, so the two would silently drift if that convention were ever revised | UNJUSTIFIED | Split the loop out of `populateSampler2DFromCellFilter` into `write_cell_filter_to_sampler2d(result, filter, grid)` (no `ensureInitialized` inside — the seed-0 initialise at `populateSampler2DFromCellFilter.js:14` is what currently blocks direct reuse); both callers use it | Low effort / Low risk |
|
|
18
|
+
| G7 | LOW | `CellFilterPower` re-derives the base class's two-child evaluation instead of implementing `operation()` | `src/generation/filtering/numeric/math/CellFilterPower.js:28` `execute` · base at `src/generation/filtering/core/CellFilterBinaryOperation.js:31` `execute` | All 8 other `CellFilterBinaryOperation` subclasses (`Add`/`Subtract`/`Multiply`/`Divide`/`Max2`/`Min2`/`Step`/`And`) implement `operation(left, right)`; `CellFilterPower` alone overrides `execute` with a hand-copied `this.left.execute(...)` / `this.right.execute(...)` pair. Any future change to the base `execute` (memoisation, assertions, a rotation-propagation fix) silently skips this one node | UNJUSTIFIED | Replace the `execute` override with `operation(value, power) { return Math.pow(value, power); }` and add the `get dataType()` the sibling algebra nodes carry | Trivial / Trivial |
|
|
19
|
+
| G8 | LOW | Two boolean algebras over the *identical* `(grid, x, y, rotation) → bool` domain, joined by adapters in both directions | `src/generation/rules/logic/CellMatcherAnd.js:6` `match` · `src/generation/filtering/boolean/logic/CellFilterAnd.js:30` `operation` · adapters `src/generation/filtering/CellFilterCellMatcher.js:41` `execute` and `src/generation/rules/CellMatcherFromFilter.js:28` `match` | `CellFilterAnd` is boolean-typed (`dataType === DataType.Boolean`) over the same signature `CellMatcher` already covers, and the `CellFilterCellMatcher`/`CellMatcherFromFilter` pair converts freely between the two, so `CellFilterAnd.from(a, b)` is expressible as `CellFilterCellMatcher.from(CellMatcherAnd.from(CellMatcherFromFilter.from(a), CellMatcherFromFilter.from(b)))`. `CellFilterAnd` is the *only* member of the boolean-filter branch (`filtering/boolean/logic/` has one file) and has no non-spec consumer, whereas the `CellMatcher` side is complete (`And`/`Or`/`Not`/`Any`/`Decorator`) and is what every grid task uses | UNJUSTIFIED (documentation-level) | Do not delete (public library surface, house rule). Add a JSDoc note on `CellFilterAnd` naming `CellMatcherAnd` as the canonical boolean combinator and the adapters as the bridge, so nobody grows a parallel `CellFilterOr`/`CellFilterNot` branch | Trivial / Trivial |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Findings — view + editor
|
|
24
|
+
|
|
25
|
+
| # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
|
|
26
|
+
|---|---|---|---|---|---|---|---|
|
|
27
|
+
| V1 | HIGH | `ImageView` and `VideoView` are the same pooled media element view; the pooling backend was upgraded in one and left behind in the other | `src/view/elements/image/ImageView.js:52` `constructor`, `:108` `destroy`, `:128` `__setSize`, `:138` `__setSource` · `src/view/elements/video/VideoView.js:66` `constructor`, `:120` `destroy`, `:146` `__setSize`, `:156` `__setSource` | Constructors are identical from the `src` resolution through cache-key construction, attribute copying, `pool.release(cacheKey)`, the `classList` loop and `this.size.onChanged.add(this.__setSize, this)`; `__setSize` and `__setSource` are byte-identical. The only intended difference is the tag (`'img'` vs `"video"`). Two unintended divergences: (a) `ImageView` uses `ImmutableObjectPool` (`:24-27`) and releases correctly (`:114-118` `if (!added) pool.release(key)`), while `VideoView` still uses a hand-rolled `Cache` of element arrays (`:14-18`, `:38-57`) whose `destroy` at `:132` only releases the pooled key inside `else if (elements.length < 100)` — once a bucket is full, neither the element nor the pooled `HTMLElementCacheKey` is returned; (b) `ImageView:61` null-guards the `url` argument (`src = ''`), `VideoView:75` does not, so a null URL yields `src="undefined"` | UNJUSTIFIED | `MediaElementView` base under `src/view/elements/` owning the constructor, `destroy`, `__setSize`, `__setSource` and the `ImmutableObjectPool`, parameterised by tag. `ImageView`/`VideoView` become `super('img')` / `super('video')` shells (both are public exports, so keep the class names and the `isVideoView` marker) | Low effort / Medium risk — `VideoView` gains `ImmutableObjectPool` semantics; check nothing depends on the video element surviving in the `Cache` |
|
|
28
|
+
| V2 | HIGH | The `NumericInterval` min ≤ max invariant is enforced three different ways in three different interval editors | `src/view/controller/controls/NumericIntervalControl.js:33` `surrogate2model` (42–48) · `editor/view/ecs/components/common/NumericIntervalController.js:44` and `:54` (write handlers) · `editor/ecs/component/editors/NumericIntervalEditor.js:42` `sync_down` (53–59) and `:72` `validate` | Three incompatible behaviours for the same invariant. (1) collapses the interval — `if (min > max) { maxController.setValue(min); interval.set(min, min); }`. (2) widens — `interval.set(v, max2(v, vMax…))` / `interval.set(min2(v, vMin…), v)`. (3) refuses the write — `if (v0 <= v1) interval.set(v0, v1)` — plus a `blur` listener that restores the old value. Editing "min" past "max" therefore does three different things depending on which panel you opened. (2) and (3) even build the same widget from the same `NumberController`, differing only in layout and the clamp rule | UNJUSTIFIED | One `NumericIntervalController` (the `editor/view/ecs/components/common/` one, already `NumberController`-based) owning a single documented clamp policy; `NumericIntervalEditor.build` returns it, and the dat.gui `NumericIntervalControl` either wraps it or is dropped in favour of it. Whichever policy is chosen must be stated in JSDoc on `NumericInterval` itself | Medium effort / Medium risk — user-visible behaviour change in two of the three panels; pick the policy deliberately |
|
|
29
|
+
| V3 | HIGH | Two-way "model ↔ widget" sync with a re-entrancy lock is hand-rolled in eight editors, half of them without the lock; `FieldValueAdapter` already exists as the seam that would collapse the `Observed*Editor` family into the `primitive/*Editor` family | `editor/ecs/component/editors/ObservedIntegerEditor.js:6` `build` vs `editor/ecs/component/editors/primitive/NumberEditor.js:6` `build` · `editor/ecs/component/editors/ObservedStringEditor.js:6` `build` vs `editor/ecs/component/editors/primitive/StringEditor.js:6` `build` · `editor/ecs/component/editors/ObservedBooleanEditor.js:5` `build` vs `editor/ecs/component/editors/primitive/BooleanEditor.js:6` `build` · also `editor/ecs/component/editors/geom/Vector1Editor.js:20-47`, `editor/ecs/component/editors/geom/QuaternionEditor.js:19-59`, `editor/ecs/component/editors/common/makeV3_editor.js:35-66`, `editor/ecs/component/editors/NumericIntervalEditor.js:27-62` | Every one of these is the same shape: `let lock = false;` + `sync_up()` (model → widget) + `sync_down()` (widget → model), each opening with `if (lock) { return; } lock = true;` and closing with `lock = false;`, wired as `ctrl.on.linked.add(sync_up); ctrl.value.onChanged.add(sync_down); ctrl.bindSignal(<model>.onChanged, sync_up);`. The Observed↔primitive pairs differ *only* in how the value is read and written (`instance.getValue()`/`instance.set()` + `bindSignal(instance.onChanged, …)` versus `field.adapter.read(parent, field.name)`/`.write(…)` with no change signal) — and the guard is present in `ObservedIntegerEditor`/`ObservedStringEditor`/`NumberEditor` but absent in `StringEditor` and `BooleanEditor`, so the protocol is not even applied consistently. `editor/ecs/component/FieldValueAdapter.js:3` is already the abstraction for "read/write a field on a parent"; it just lacks an observed variant that also exposes a change signal | UNJUSTIFIED | Add `ObservedFieldValueAdapter extends FieldValueAdapter` (read/write via `getValue`/`set`, plus a `change_signal(object, field_name)` accessor returning `onChanged` or `Signal.NONE`), then delete `ObservedBooleanEditor`/`ObservedIntegerEditor`/`ObservedStringEditor` and register the `primitive/` editors for those types. Hoist the lock+sync wiring into a single `bind_two_way(view, read, write, change_signal)` helper used by every remaining editor | Medium effort / Medium risk — touches the type registry; the `Observed*` classes are internal to the editor so removal is safe, but registration order must be checked |
|
|
30
|
+
| V4 | HIGH | `GroupView` re-implements `View`'s child list, bypassing the link/unlink lifecycle | `src/view/elements/Group.js:3` `GroupView` — `this.children = []` at `:9`, `add(view)` at `:11`, `remove(view)` at `:17`, `update()` at `:25` · base at `src/view/View.js:107` `children`, `:365` `addChild`, `:433` `removeChild` · sole consumer `editor/view/ecs/components/TerrainController.js:131-133` | `View` already declares `children = []` (`View.js:107`), and `View.addChild` (`:365`) does four things: asserts the child is not already linked, sets `child.parent = this`, calls `child.link()` when the parent is linked, then pushes and appends. `GroupView` shadows the same `children` field with its own array and its `add()` only does `push` + `appendChild` — `parent` is never set and `link()` is never called, so any child added through `GroupView.add()` never fires `on.linked` and `View.link()` at `:303-308` (which walks `this.children`) will never reach it either. `GroupView.update()` calls `c.update()`, which is not a method on `View` at all. `TerrainController.js:131-133` adds a `LabelView` and a size control this way | UNJUSTIFIED | Delete `GroupView`; `TerrainController` uses `EmptyView.group([...], { classList: ['group'] })` (`src/view/elements/EmptyView.js:80`), which goes through `addChild` and preserves the lifecycle. If the `.group` CSS class is load-bearing, pass it in the options | Low effort / Low risk — one consumer; `GroupView` is a default export so keep a deprecation note if external users are a concern |
|
|
31
|
+
| V5 | HIGH | `ScalarParameterLUTController` and `ColorParameterLUTController` are two ~400-line copies of the same LUT keyframe-marker strip | `editor/view/ecs/components/particles/ScalarParameterLUTController.js:119` `buildMarker`, `:229` local `MarkerView`, `:303` `createMarker`, `:310` `resizeMarkerPool`, `:320` `updateMarkers`, `:357` `update` · `editor/view/ecs/components/particles/ColorParameterLUTController.js:45` `buildMarker`, `:18` local `MarkerView`, `:334` `createMarker`, `:341` `resizeMarkerPool`, `:351` `updateMarkers`, `:384` `update` | Both files declare their own module-local `class MarkerView extends View`, their own `buildMarker(markers, model, update, canvas[, selection])` and their own `createMarker` / `resizeMarkerPool` / `updateMarkers` / `update` quartet. Inside `buildMarker`, the following are identical in both: the `pMarker`/`pGlobal` `PointerDevice` pair, the `dragAnchor` capture on `pGlobal.on.down`, `handleMove`'s dead-marker guard (`if (markerIndex === -1) { pMarker.stop(); pGlobal.stop(); return; }`), the `positionDelta = delta.x / canvas.width` + `clamp01(oldPosition + positionDelta)` position drag, the middle-mouse delete (`lut.positions.splice(markerIndex, 1); lut.data.splice(markerIndex * lut.itemSize, lut.itemSize);`), and the `dragStart` handler including `dragScale = lut.valueMax - lut.valueMin; if (dragScale === 0) { dragScale = 0.1; }`. `resizeMarkerPool` is byte-identical apart from the parent view (`self` vs `vGradient`). The smoking gun: **`let dragging = false;` is declared and assigned but never read in both files** (`Scalar:130`, `Color:56`) — dead code faithfully carried across the copy. Genuinely different: the value axis only (scalar Y-drag + line plot vs. colour swatch + gradient + selection) | UNJUSTIFIED | Extract `ParameterLUTMarkerStrip` (or a `buildLutMarker` + `LutMarkerPool` pair) into `editor/view/ecs/components/particles/`, owning `MarkerView`, position dragging, deletion, pool sizing and the `setFlag(WriteMode) → addValue → clearFlag(WriteMode)` insert. The two controllers supply a value-axis strategy (drag response + marker decoration + canvas draw) | Medium effort / Medium risk — pointer-drag code with no test coverage; extract in one step and exercise both panels manually |
|
|
32
|
+
| V6 | MEDIUM | The generic N-numeric-field two-way-bound control already exists in the editor; three dat.gui controls re-implement it, and a fourth path exists inside `DatGuiController` | `editor/ecs/component/editors/common/makeV3_editor.js:11` `make_row_editor` (the generic version) · `src/view/controller/controls/Vector2Control.js:4` `constructor` (32–77) · `src/view/controller/controls/Vector3Control.js:4` `constructor` (35–83) · `src/view/controller/controls/NumericIntervalControl.js:4` `constructor` (33–84) · `editor/ecs/component/editors/geom/Vector1Editor.js:7` `build` · `editor/view/ecs/components/DatGuiController.js:115-154` (`add`, `isVector2 \|\| isVector3` branch) | `make_row_editor(props, value, change_signal)` is already parameterised over an arbitrary list of field names and drives `Vector2Editor` (`['x','y']`), `Vector3Editor`, `Vector4Editor` (`['x','y','z','w']`) and `QuaternionEditor`. The three `src/view/controller/controls/*` classes each hand-roll the same thing against dat.gui: `modelChanged(modelNew, modelOld)` is **byte-identical** in all three (`NumericIntervalControl.js:70`, `Vector2Control.js:63`, `Vector3Control.js:69`), as is the `modelWriteLock`/`controllerWriteLock` pair and the `this.on.linked.add(function () { model2surrogate(); }); this.model.process(modelChanged);` tail. `Vector1Editor` is `make_row_editor(['x'], v1)` written out longhand. `DatGuiController.add` contains yet another vector path via a get/set proxy and a dat.gui folder | UNJUSTIFIED | Promote `make_row_editor` out of `makeV3_editor.js` into its own module (the filename lies about its contents) and make the three `src/view/controller/controls/*` classes thin wrappers: `Vector2Control` = `['x','y']`, `Vector3Control` = `['x','y','z']`, `NumericIntervalControl` = `['min','max']` + the V2 clamp policy. Replace `Vector1Editor.build` with `make_row_editor(['x'], v1)` | Medium effort / Medium risk — the three controls render through dat.gui, so either they keep a dat.gui backend behind the shared binding logic or the widget changes appearance |
|
|
33
|
+
| V7 | MEDIUM | The terrain brush's UV footprint and its UV→texel conversion are each derived independently in the base tool and both subclasses | `editor/tools/paint/TerrainPaintTool.js:117` `updateOverlay` (120–143) · `editor/tools/paint/TerrainHeightPaintTool.js:169` `paint` (178–193) + `make_mod_action` `:27` (31–41) · `editor/tools/paint/TerrainTexturePaintTool.js:93` `paint` (103–121, 145–155) | `TerrainHeightPaintTool.paint:183-193` and `TerrainTexturePaintTool.paint:111-121` contain the identical 11-line UV-rect derivation (`uv_x = brushPosition.x / terrainSize.x` … `uv_y1 = uv_y + uv_h / 2`), and `make_mod_action:31-41` and `TerrainTexturePaintTool.paint:145-155` contain the identical 8-line UV→integer-texel conversion (`h_x0 = uv_x0 * W` … `y1 = Math.floor(h_y1)`), differing only in whether `W/H` come from `samplerHeight` or `splat.size`. `TerrainPaintTool.updateOverlay:129-143` computes the *same* rect a third time in overlay space (`brushRadius = brushSize / 2`). All three must agree or the on-screen brush preview stops matching what is painted — and the base class already owns `__brushPosition`, `settings.brushSize` and `this.terrain` | UNJUSTIFIED | Two protected helpers on `TerrainPaintTool`: `computeBrushUVRect(result)` and `static uvRectToTexelRect(result, uv_rect, width, height)`. `updateOverlay` and both `paint` implementations consume them | Low effort / Low risk — pure extraction inside one class hierarchy |
|
|
34
|
+
| V8 | MEDIUM | Stack-based quicksort skeleton written three times; `core/collection/array` already ships two variants, and the scratch-stack fix reached only one copy | `src/view/minimap/gl/MinimapMarkersGL.js:141` `__sortByZIndex` · `src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js:736-792` · `src/engine/graphics/render/forward_plus/sort_decal_data.js:47` `sort_decal_data` · primitives at `src/core/collection/array/array_quick_sort_by_lookup_uint.js:11` and `array_sort_quick.js:17` | All three share the same loop skeleton down to the comments (`//Stack-based implementation, avoiding recursion for performance improvement`, `/* partition */`, `/* recursion */`, `//do swap`) and the same `pivotIndex = (left + right) >> 1`, `while (i <= j)`, `if (i !== j)` structure. The stack storage diverged: `ParticleEmitter:737` was migrated to the shared `SCRATCH_UINT32_TRAVERSAL_STACK` with a proper `stack_top` frame, `sort_decal_data:1` still uses a module-scope `_scratch_array` (non-reentrant), and `MinimapMarkersGL:151` allocates a fresh JS array on every sort. `ParticleEmitter:731` has the `particleCount <= 1` early-out; the other two do not (`array_quick_sort_by_lookup_uint:16` has it as `start >= end`) | UNJUSTIFIED | `MinimapMarkersGL.__sortByZIndex` should call `array_quick_sort_by_lookup_uint`-style shared code; because it swaps through `particles.swap(i, j)` rather than the key array, the right primitive is `array_sort_quick(data, score_function, ctx, start, end, swap_operator, swap_context)` (`array_sort_quick.js:17`), which already takes a custom `swap_operator`. The two engine copies are outside this domain but should follow | Low effort / Medium risk — `array_sort_quick` scores through a function call per comparison; measure against the current inline array read before committing (per the reuse-over-micro-opt rule, lead with the measurement) |
|
|
35
|
+
| V9 | LOW | `LineView` duplicates `EmptyView.group` | `editor/view/ecs/components/common/LineView.js:6` `constructor` · `src/view/elements/EmptyView.js:80` `static group` | `LineView({elements, classList})` does `document.createElement('div')` + `addClass('ui-line-view')` + `classList.forEach(addClass)` + `elements.forEach(addChild)`. `EmptyView.group(elements, {classList, tag, css})` is the same construction with the class list supplied by the caller. The only unique content is the `'ui-line-view'` class string | UNJUSTIFIED | Replace `new LineView({elements, classList})` with `EmptyView.group(elements, { classList: ['ui-line-view', ...classList] })`; keep `LineView` as a one-line factory if the class-name convention is worth preserving | Trivial / Low risk — ~6 consumers |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Dismissed leads
|
|
40
|
+
|
|
41
|
+
Shape-boilerplate families deliberately **not** flagged (house rule: typed verbose classes over registries/metaprogramming; the duplication here is in *shape*, not *logic*):
|
|
42
|
+
|
|
43
|
+
- **`static from(...)` constructors across all of `generation/`** (12-way group in brief §A, `[49x12]`). Every one is `assert` + `new X()` + field assignment + `return r`. This is the package's declared idiom; collapsing it requires exactly the string-keyed factory the owner has ruled out.
|
|
44
|
+
- **`initialize`/`execute` fan-out loops in the sequence/group nodes** — `MarkerNodeActionSequence.js:25/36`, `MarkerNodeEntityProcessorSequence.js:38/50`, `MarkerNodeTransformerSequence.js:19/29`, `MarkerNodeEmitterGroup.js:26/37`, `MarkerNodeEmitterFromAction.js:33`, `GridCellActionSequence.js:31/43`, `CellProcessingRuleSet.js:23`, `CellMatcherGridPattern.js:26`. Read them all: each is a bare `for` loop forwarding a *different* method signature on a *different* type hierarchy (`(grid,ecd,seed)`, `(data,seed)`, `(entity,node,data,ecd)`, `(data,x,y,rotation,consumer)`). There is no shared logic to extract without erasing the types.
|
|
45
|
+
- **Boolean combinators `CellMatcherAnd/Or`, `MarkerNodeMatcherAnd/Or`, `GridDataNodePredicateAnd/Any`, `MarkerNodeMatcherAny/None`.** Answer to the "two matcher hierarchies?" question: there are *three*, and they are over genuinely different domains — `CellMatcher.match(grid,x,y,rotation)`, `MarkerNodeMatcher.match(node)` (no grid, no initialize), `GridDataNodePredicate.evaluate(grid,node)`. Only the `CellMatcher`/boolean-`CellFilter` overlap is a real domain collision, and that is reported as **G8**.
|
|
46
|
+
- **`filtering/numeric/math/**` arithmetic nodes** (`Add`/`Subtract`/`Multiply`/`Divide`/`Negate`/`Max2`/`Min2`/`Clamp`/`SmoothStep`/`Lerp`/`InverseLerp`/`Saturate`/`OneMinus`/`Absolute`/`Step`). Checked every one: they all correctly delegate to `core/math` (`max2`, `min2`, `clamp`, `smoothStep`, `lerp`, `inverseLerp`) and carry no inline math. The only real defect in the family is `CellFilterPower` (**G7**). The `dataType`/`assert` preamble repetition is shape.
|
|
47
|
+
- **`theme/TerrainLayerRule.js:25 from`, `theme/cell/CellProcessingRule.js:22 from`, `theme/cell/CellProcessingRuleSet.js:23 initialize`.** Opened all three — two-field assignment and a forwarding loop. Shape.
|
|
48
|
+
- **Neighbour-mask iteration bodies** (`buildDistanceMapToObjective.js:51-75`, `buildPathFromDistanceMap.js:35-61`, `GridTaskConnectRooms.js:181-201` and `:252-272`, `GridTaskGenerateRoads.js:249-269`). The index→(x,y) decode + mask loop + double bounds check is repeated five times, but each body diverges *inside* the loop (flood-fill push, open-set seeding, distance relaxation, best-successor pick, path-network probe with a labelled `continue neighbourhood_loop`). A callback iterator would allocate a closure per cell in the hottest code in the package. **JUSTIFIED** — the shareable part is the mask constants only (**G5**).
|
|
49
|
+
- **`CaveGeneratorCellularAutomata.step`** — single implementation, hand-unrolled 8-neighbour sum with hoisted row bases and validity flags. Hot inner loop, no second copy. Correct as written.
|
|
50
|
+
- **`GridData.js:75 getLayerById`** — a 10-line linear scan over `layers` (typically < 10 entries) called from `initialize` paths only. `AbstractCellFilterSampleGridLayer.js:31` and `GridTaskBuildSourceDistanceMap.js:85` both call it rather than re-deriving it. Nothing to share.
|
|
51
|
+
- **`GridTaskGenerateRoads.js:29 findMarkerNodeByPosition` vs `CellMatcherGridPattern.js:64 getRuleByPosition`** (brief §C, sim 0.891) — both linear `position.x/y` scans, ~10 lines, over unrelated element types in unrelated modules. Below the bar.
|
|
52
|
+
- **`RoadConnection.js:79 test`** (brief §A, paired with `AnimationClipDefinition.hasTag`) — a 10-line `indexOf`-equivalent on a `number[]`. Below the bar.
|
|
53
|
+
- **`core/parser/simple/readReferenceToken.js` vs `view/tooltip/gml/parser/readReferenceToken.js`** (brief §G) — read both: **not a fork.** The core one reads a dotted identifier path (`a.b.c`) into a `Token` list; the view one parses a bracketed tooltip reference `[tag: k=v, k=v]` with its own `ParserError` messages and `TooltipReferenceValue` payload. Name collision only; the view one already reuses `skipWhitespace`, `Token` and `ParserError` from core.
|
|
54
|
+
- **`view/minimap/dom/MinimapCameraView.js:110-112` raw `Math.random()`** (brief §F) — checked for a seeded-RNG bypass: it is a single site, not duplication, so no row. Worth passing to whoever owns determinism, though: the jitter is accumulated into `oX/oY/oZ` (`:99-101`) while the retry at `:106` re-raycasts with the original `originX/originY/originZ`, so the whole jitter loop is inert.
|
|
55
|
+
- **`editor/actions/concrete/Selection{Add,Remove,Clear}Action`, `Component{Add,Remove}Action`, `Entity{Create,Remove}Action`** — opened all six. The shared part is two lines (`this.oldState = editor.selection.clone()` / `revert: editor.selection.copy(this.oldState)`); everything else is per-action. Typed undo commands are exactly the shape the owner prefers.
|
|
56
|
+
- **`editor/process/symbolic/make*SymbolicDisplay.js`** (11 files) — checked `make3DSymbolicDisplay.js`, `makeCameraSymbolicDisplay.js`, `makePositionedIconDisplaySymbol.js`: already correctly factored through `make3DSymbolicDisplay` + `buildThreeJSHelperEntity` + `synchronizeTransform`. Good reuse; nothing to report.
|
|
57
|
+
- **`editor/view/ecs/components/common/AutoCanvasView.js`** — properly `extends CanvasView` and reuses `DomSizeObserver`. Good reuse.
|
|
58
|
+
- **`HTMLElementCacheKey.js:122 #getAttributeContainer`** vs `AttributeGroupSpec.getAttributeByName` / `ParticleSpecification.getByName` (brief §A/§C) — three unrelated 10-line "linear scan by `.name`" loops on three different record types in three different subsystems. Below the bar.
|
|
59
|
+
- **`view/controller/controls/*` importing `editor/view/ecs/components/DatGuiController.js`** — `src/` depending on `editor/` is a layering inversion, but it is not duplication; noted here so it isn't mistaken for an oversight.
|
|
60
|
+
- **Seed derivation for child nodes** — `MarkerNodeActionSequence.js:32` and `MarkerNodeActionSelectWeighted.js:50` decorrelate children with `seed + i`; `MarkerNodeEntityProcessorSequence.js:45`, `GridCellActionSequence.js:39`, `MarkerNodeEmitterGroup.js:33` and `MarkerNodeTransformerSequence.js:25` pass `seed` unchanged. Only two sites use the `+ i` form, so this is a convention gap rather than duplication — but it is the same class of hazard as G1 and should be settled once (`randomSeed(random)` per child, as `TerrainTheme.js:30` and `ThemeEngine.js:215` already do).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Notes — why duplication accumulates here
|
|
65
|
+
|
|
66
|
+
1. **Four parallel node hierarchies over one grid.** `CellFilter` (`grid,x,y,rot → number`), `CellMatcher` (`… → bool`), `GridCellAction` (`grid,x,y,rot → void`) and `MarkerNodeAction`/`MarkerNodeTransformer`/`MarkerNodeEmitter` (`grid,ecd,node`) all model "do something at a place on the grid" with different arity. The typed-classes-over-magic rule makes that a deliberate design, and it means every *combinator* (sequence, weighted-select, and/or/not, displaced-by-offset) gets written once per hierarchy. That is accepted. What is **not** accepted, and what every generation finding above has in common, is the *scalar math and search algorithms* getting copied along with the combinator shape — the rotation formula (G2), the CDF walk (G3), the Sobel taps (G4), the BFS (G1). The dividing line is: forwarding loops are shape, arithmetic and search are logic.
|
|
67
|
+
|
|
68
|
+
2. **`markers/` and `placement/` are not superseding each other.** Both have live spec coverage and both are reachable from `ThemeEngine`; `GridCellActionPlaceMarker` (in `markers/`) even extends `GridCellAction` (in `placement/`), so the two are interleaved by design — `placement/` operates on cells and *emits* markers, `markers/` operates on the emitted nodes. There is no dead branch to delete; the duplication is confined to the two combinators reported in G3.
|
|
69
|
+
|
|
70
|
+
3. **The reuse surface exists but is not discoverable from `generation/`.** `ORTHOGONAL_NEIGHBOURHOOD_MASK` has zero importers while two classes redeclare it; there is no `v2_rotate` at all despite eight callers; `computeCellFilterGradient` sits in the same package as `CellFilterSobel` and is still not used by it. The pattern is "write it where you need it, in the file you already have open".
|
|
71
|
+
|
|
72
|
+
4. **`src/view` and `editor/` are mutually entangled rather than layered.** `src/view/controller/controls/*` imports `editor/view/ecs/components/DatGuiController.js`; `editor/*` imports `src/view/*` everywhere. Because there is no single owner of "widget", the same widget gets built on whichever side the author happened to be working — hence three numeric-interval editors (V2), four vector editors (V6), and a `LineView` that duplicates `EmptyView.group` (V9).
|
|
73
|
+
|
|
74
|
+
5. **Two generations of infrastructure coexist, and only some call sites were migrated.** `ImmutableObjectPool` vs the older `Cache`-of-arrays (V1), `SCRATCH_UINT32_TRAVERSAL_STACK` vs per-call arrays (V8), `View.addChild` vs `GroupView.add` (V4), the `inOpen` BitSet mirror vs `Uint32Heap.insert_or_update` (G1). In every case the migration was applied to one copy and the sibling was never found — which is precisely the failure mode that makes duplication expensive rather than merely verbose.
|