@woosh/meep-engine 2.169.0 → 2.169.1
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/process/symbolic/make3DSymbolicDisplay.js +1 -1
- package/editor/view/ecs/EntityList.js +1 -1
- package/editor/view/ecs/components/particles/ParticleLayerController.js +1 -1
- package/editor/view/makeEntityDecorators.js +1 -1
- package/package.json +1 -1
- 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_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/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/cache/Cache.d.ts +4 -4
- package/src/core/cache/Cache.d.ts.map +1 -1
- package/src/core/cache/Cache.js +3 -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/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/quad-tree/qt_match_data_by_circle.d.ts +2 -2
- 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 +1 -1
- 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/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/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/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/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/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/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/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/vec3/v3_lerp.d.ts +1 -1
- package/src/core/geom/vec3/v3_lerp.js +1 -1
- 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/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/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/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/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 +1 -4
- package/src/engine/animation/TransitionFunctions.d.ts.map +1 -1
- package/src/engine/animation/TransitionFunctions.js +1 -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/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/control/first-person/FirstPersonPlayerControllerSystem.d.ts +0 -10
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- 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/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/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/util/hideEntityGracefully.js +2 -2
- 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/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/light/three/ThreeLightCache.d.ts +1 -1
- package/src/engine/graphics/ecs/light/three/ThreeLightCache.js +1 -1
- 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 +2 -2
- 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/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/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 +1 -1
- 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/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/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_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/lpv/PathTracerProbeRenderer.d.ts +2 -1
- package/src/engine/graphics/sh3/lpv/PathTracerProbeRenderer.d.ts.map +1 -1
- 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/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/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/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/physics/fluid/ecs/FluidObstacleSystem.d.ts +0 -4
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -1
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +0 -4
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts.map +1 -1
- 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.map +1 -1
- package/src/engine/save/storage/IndexedDBStorage.js +11 -10
- package/src/engine/sound/simulation/prototypeAcousticSolver.js +1652 -1652
- 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/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/complex/CellFilterSimplexNoise.d.ts +1 -1
- package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +1 -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/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/transform/MarkerNodeTransformRotateRandom.d.ts +2 -1
- package/src/generation/markers/transform/MarkerNodeTransformRotateRandom.d.ts.map +1 -1
- 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/view/common/VirtualListView.d.ts +1 -1
- package/src/view/common/VirtualListView.js +1 -1
- 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
|
@@ -1,345 +1,345 @@
|
|
|
1
|
-
import { assert } from "../../../core/assert.js";
|
|
2
|
-
import { mix } from "../../../core/math/mix.js";
|
|
3
|
-
import { seededRandom } from "../../../core/math/random/seededRandom.js";
|
|
4
|
-
import { MoveEdge } from "./MoveEdge.js";
|
|
5
|
-
import { StateNode, StateType } from "./StateNode.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* From: A Survey of Monte Carlo Tree Search Methods
|
|
9
|
-
* The value Cp = 1/√2 was shown by Kocsis and Szepesvari [120] to satisfy the Hoeffding ineqality with rewards in the range [0, 1]
|
|
10
|
-
* @type {number}
|
|
11
|
-
*/
|
|
12
|
-
const C_ks = 1 / Math.sqrt(2);
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* UCB1-style score for the parent choosing among its children. The exploitation
|
|
16
|
-
* term is read from whichever component the parent's active player cares about —
|
|
17
|
-
* each decision-maker maximizes their own outcome (maxN selection).
|
|
18
|
-
*
|
|
19
|
-
* @param {StateNode} parent
|
|
20
|
-
* @param {StateNode} child
|
|
21
|
-
* @returns {number}
|
|
22
|
-
*/
|
|
23
|
-
function computeNodeSelectionScore(parent, child) {
|
|
24
|
-
const playouts = child.playouts;
|
|
25
|
-
|
|
26
|
-
if (playouts === 0) {
|
|
27
|
-
// child has 0 playouts, this can only happen if it ended up with no moves, avoid division by 0
|
|
28
|
-
return 0;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const player = parent.activePlayer;
|
|
32
|
-
|
|
33
|
-
// Exploitation heuristic
|
|
34
|
-
const Q = mix(child.totalScore[player] / playouts, child.heuristicValue[player], 0.65);
|
|
35
|
-
|
|
36
|
-
// Based on UCB1
|
|
37
|
-
// exploration heuristic
|
|
38
|
-
const u = Math.sqrt((2 * Math.log(parent.playouts)) / playouts);
|
|
39
|
-
|
|
40
|
-
return Q + C_ks * u;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @template S
|
|
45
|
-
* @author Alex Goldring
|
|
46
|
-
* @copyright Company Named Limited (c) 2026
|
|
47
|
-
*/
|
|
48
|
-
export class MonteCarloTreeSearch {
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
*
|
|
52
|
-
* @type {S}
|
|
53
|
-
*/
|
|
54
|
-
rootState = null;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
*
|
|
58
|
-
* @type {StateNode|null}
|
|
59
|
-
*/
|
|
60
|
-
root = null;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Number of distinct players whose payoffs we track. Set in {@link initialize}.
|
|
64
|
-
* Sizes the per-node `heuristicValue` and `totalScore` buffers.
|
|
65
|
-
* @type {number}
|
|
66
|
-
*/
|
|
67
|
-
numPlayers = 0;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
*
|
|
71
|
-
* @type {
|
|
72
|
-
*/
|
|
73
|
-
computeValidMoves = null;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
* @type {
|
|
78
|
-
*/
|
|
79
|
-
computeTerminalFlag = null;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Returns the player ID (0-based) who chooses the next move from a given state.
|
|
83
|
-
* Called when a non-terminal node is expanded. Players don't need to alternate
|
|
84
|
-
* or follow any fixed pattern — the framework just asks the game.
|
|
85
|
-
* @type {
|
|
86
|
-
*/
|
|
87
|
-
computeActivePlayer = null;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Returns the per-player payoff vector for a terminal state.
|
|
91
|
-
* Indexed by player ID. The game decides the zero-sum/cooperative shape.
|
|
92
|
-
* @type {
|
|
93
|
-
*/
|
|
94
|
-
computeOutcome = null;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
* @type {function(S):S}
|
|
99
|
-
*/
|
|
100
|
-
cloneState = null;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Heuristic estimate of a non-terminal state's per-player value.
|
|
104
|
-
* Optional — if `null`, new nodes start with zero-valued heuristic vectors.
|
|
105
|
-
* @type {null|
|
|
106
|
-
*/
|
|
107
|
-
heuristic = null;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Depth to which plays will be explored
|
|
111
|
-
* @type {number}
|
|
112
|
-
*/
|
|
113
|
-
maxExplorationDepth = 1000;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
*
|
|
117
|
-
* @type {Function}
|
|
118
|
-
*/
|
|
119
|
-
random = seededRandom(0);
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @param {S} rootState
|
|
123
|
-
* @param {number} numPlayers Total number of players whose payoffs are tracked. Use 1 for solitaire / planning.
|
|
124
|
-
* @param {
|
|
125
|
-
* @param {
|
|
126
|
-
* @param {
|
|
127
|
-
* @param {
|
|
128
|
-
* @param {function(S):S} cloneState
|
|
129
|
-
* @param {null|
|
|
130
|
-
*/
|
|
131
|
-
initialize(
|
|
132
|
-
{
|
|
133
|
-
rootState,
|
|
134
|
-
numPlayers,
|
|
135
|
-
computeValidMoves,
|
|
136
|
-
computeTerminalFlag,
|
|
137
|
-
computeActivePlayer,
|
|
138
|
-
computeOutcome,
|
|
139
|
-
cloneState,
|
|
140
|
-
heuristic = null
|
|
141
|
-
}
|
|
142
|
-
) {
|
|
143
|
-
assert.isInteger(numPlayers, `numPlayers`);
|
|
144
|
-
assert.greaterThanOrEqual(numPlayers, 1, `numPlayers`);
|
|
145
|
-
|
|
146
|
-
assert.isFunction(computeValidMoves, `computeValidMoves`);
|
|
147
|
-
assert.isFunction(computeTerminalFlag, `computeTerminalFlag`);
|
|
148
|
-
assert.isFunction(computeActivePlayer, `computeActivePlayer`);
|
|
149
|
-
assert.isFunction(computeOutcome, `computeOutcome`);
|
|
150
|
-
assert.isFunction(cloneState, `cloneState`);
|
|
151
|
-
|
|
152
|
-
this.numPlayers = numPlayers;
|
|
153
|
-
this.computeValidMoves = computeValidMoves;
|
|
154
|
-
this.computeTerminalFlag = computeTerminalFlag;
|
|
155
|
-
this.computeActivePlayer = computeActivePlayer;
|
|
156
|
-
this.computeOutcome = computeOutcome;
|
|
157
|
-
this.cloneState = cloneState;
|
|
158
|
-
this.heuristic = heuristic;
|
|
159
|
-
|
|
160
|
-
this.rootState = rootState;
|
|
161
|
-
|
|
162
|
-
this.root = new StateNode();
|
|
163
|
-
this.root.allocate(numPlayers);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
*
|
|
168
|
-
* @param {StateNode} node
|
|
169
|
-
* @param {S} state
|
|
170
|
-
* @returns {StateNode}
|
|
171
|
-
*/
|
|
172
|
-
selectRandom(node, state) {
|
|
173
|
-
let score;
|
|
174
|
-
|
|
175
|
-
let i, bestScore, bestMove;
|
|
176
|
-
|
|
177
|
-
const random = this.random;
|
|
178
|
-
|
|
179
|
-
while (
|
|
180
|
-
node.isExpanded() &&
|
|
181
|
-
node.moves.length > 0 &&
|
|
182
|
-
!node.isTerminal()
|
|
183
|
-
) {
|
|
184
|
-
|
|
185
|
-
bestScore = Number.NEGATIVE_INFINITY;
|
|
186
|
-
bestMove = null;
|
|
187
|
-
|
|
188
|
-
const moves = node.moves;
|
|
189
|
-
|
|
190
|
-
const numMoves = moves.length;
|
|
191
|
-
|
|
192
|
-
assert.notEqual(numMoves, 0, 'number of moves is 0, this is invalid state');
|
|
193
|
-
|
|
194
|
-
for (i = 0; i < numMoves; i++) {
|
|
195
|
-
|
|
196
|
-
const move = moves[i];
|
|
197
|
-
|
|
198
|
-
const randomRoll = random();
|
|
199
|
-
|
|
200
|
-
if (move.isTargetMaterialized()) {
|
|
201
|
-
|
|
202
|
-
const child = move.target;
|
|
203
|
-
|
|
204
|
-
const s = computeNodeSelectionScore(node, child);
|
|
205
|
-
|
|
206
|
-
assert.notNaN(s, 'computed Node score');
|
|
207
|
-
assert.isFinite(s, `computed Node score`);
|
|
208
|
-
|
|
209
|
-
score = s + randomRoll * 1e-5;
|
|
210
|
-
} else {
|
|
211
|
-
|
|
212
|
-
//use a constant value for unexplored nodes
|
|
213
|
-
score = randomRoll * 100;
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (score > bestScore) {
|
|
219
|
-
bestScore = score;
|
|
220
|
-
bestMove = move;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (!bestMove.isTargetMaterialized()) {
|
|
226
|
-
//materialize the target state
|
|
227
|
-
materializeEdgeTarget(state, node, bestMove, this);
|
|
228
|
-
|
|
229
|
-
} else {
|
|
230
|
-
//just follow the edge
|
|
231
|
-
bestMove.move(state);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
node = bestMove.target;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return node;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Perform a playout from the root node
|
|
243
|
-
* @returns {S} final state of the playout
|
|
244
|
-
*/
|
|
245
|
-
playout() {
|
|
246
|
-
const computeValidMoves = this.computeValidMoves;
|
|
247
|
-
const computeActivePlayer = this.computeActivePlayer;
|
|
248
|
-
const computeOutcome = this.computeOutcome;
|
|
249
|
-
|
|
250
|
-
const state = this.cloneState(this.rootState);
|
|
251
|
-
|
|
252
|
-
assert.notEqual(state, this.rootState, 'cloneState must produce a new state object, instead it produced the same one');
|
|
253
|
-
|
|
254
|
-
let node = this.root;
|
|
255
|
-
|
|
256
|
-
while (!node.isTerminal() && node.depth < this.maxExplorationDepth) {
|
|
257
|
-
|
|
258
|
-
if (!node.isExpanded()) {
|
|
259
|
-
node.expand(state, computeValidMoves, computeActivePlayer, computeOutcome);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const child = this.selectRandom(node, state);
|
|
263
|
-
|
|
264
|
-
if (child === node) {
|
|
265
|
-
// prevent infinite recursion
|
|
266
|
-
// can happen e.g. when expand produced no moves (NoMoves terminal)
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
node = child;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
let outcome;
|
|
274
|
-
|
|
275
|
-
if (!node.isTerminal() && node.depth >= this.maxExplorationDepth) {
|
|
276
|
-
// cap the state by depth, propagate heuristic score as payoff stand-in
|
|
277
|
-
node.type = StateType.DepthCapped;
|
|
278
|
-
outcome = node.heuristicValue;
|
|
279
|
-
} else if (node.outcome !== null) {
|
|
280
|
-
// terminal node with cached payoff
|
|
281
|
-
outcome = node.outcome;
|
|
282
|
-
} else {
|
|
283
|
-
// Defensive: shouldn't reach here under normal control flow, but if
|
|
284
|
-
// a node became terminal without a cached outcome, fall back to heuristic.
|
|
285
|
-
outcome = node.heuristicValue;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
node.backpropagate(outcome);
|
|
289
|
-
|
|
290
|
-
return state;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* @template S
|
|
296
|
-
* @param {S} state
|
|
297
|
-
* @param {StateNode} source
|
|
298
|
-
* @param {MoveEdge} edge
|
|
299
|
-
* @param {MonteCarloTreeSearch} search
|
|
300
|
-
*/
|
|
301
|
-
function materializeEdgeTarget(state, source, edge, search) {
|
|
302
|
-
|
|
303
|
-
const child = new StateNode();
|
|
304
|
-
child.allocate(search.numPlayers);
|
|
305
|
-
child.parent = source;
|
|
306
|
-
child.depth = source.depth + 1;
|
|
307
|
-
|
|
308
|
-
const computedState = edge.move(state);
|
|
309
|
-
|
|
310
|
-
const terminalFlag = search.computeTerminalFlag(computedState);
|
|
311
|
-
|
|
312
|
-
assert.enum(terminalFlag, StateType, 'terminalFlag');
|
|
313
|
-
|
|
314
|
-
child.type = terminalFlag;
|
|
315
|
-
|
|
316
|
-
edge.target = child;
|
|
317
|
-
|
|
318
|
-
if (terminalFlag === StateType.Undecided) {
|
|
319
|
-
// active player will be set when this node is expanded (we don't know
|
|
320
|
-
// yet whether it has any legal moves)
|
|
321
|
-
if (search.heuristic !== null) {
|
|
322
|
-
const h = search.heuristic(child, computedState);
|
|
323
|
-
|
|
324
|
-
assert.notNull(h, 'heuristic returned null');
|
|
325
|
-
|
|
326
|
-
child.heuristicValue.set(h);
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
// terminal — game decides per-player payoff, cache and mirror into heuristicValue.
|
|
330
|
-
// Copy into the node's own buffer so the user is free to reuse one across calls;
|
|
331
|
-
// outcome and heuristicValue share storage within the node (safe since bubble-up
|
|
332
|
-
// skips terminals — see StateNode.aggregateHeuristicScore).
|
|
333
|
-
const out = search.computeOutcome(computedState);
|
|
334
|
-
|
|
335
|
-
child.heuristicValue.set(out);
|
|
336
|
-
child.outcome = child.heuristicValue;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Propagate the new node's value up the tree via maxN backup. This refines
|
|
340
|
-
// ancestors' heuristicValue with information from the freshly-materialized
|
|
341
|
-
// leaf — useful guidance for subsequent UCB selections at those ancestors.
|
|
342
|
-
child.bubbleUpHeuristicScore();
|
|
343
|
-
|
|
344
|
-
return computedState;
|
|
345
|
-
}
|
|
1
|
+
import { assert } from "../../../core/assert.js";
|
|
2
|
+
import { mix } from "../../../core/math/mix.js";
|
|
3
|
+
import { seededRandom } from "../../../core/math/random/seededRandom.js";
|
|
4
|
+
import { MoveEdge } from "./MoveEdge.js";
|
|
5
|
+
import { StateNode, StateType } from "./StateNode.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* From: A Survey of Monte Carlo Tree Search Methods
|
|
9
|
+
* The value Cp = 1/√2 was shown by Kocsis and Szepesvari [120] to satisfy the Hoeffding ineqality with rewards in the range [0, 1]
|
|
10
|
+
* @type {number}
|
|
11
|
+
*/
|
|
12
|
+
const C_ks = 1 / Math.sqrt(2);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* UCB1-style score for the parent choosing among its children. The exploitation
|
|
16
|
+
* term is read from whichever component the parent's active player cares about —
|
|
17
|
+
* each decision-maker maximizes their own outcome (maxN selection).
|
|
18
|
+
*
|
|
19
|
+
* @param {StateNode} parent
|
|
20
|
+
* @param {StateNode} child
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
function computeNodeSelectionScore(parent, child) {
|
|
24
|
+
const playouts = child.playouts;
|
|
25
|
+
|
|
26
|
+
if (playouts === 0) {
|
|
27
|
+
// child has 0 playouts, this can only happen if it ended up with no moves, avoid division by 0
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const player = parent.activePlayer;
|
|
32
|
+
|
|
33
|
+
// Exploitation heuristic
|
|
34
|
+
const Q = mix(child.totalScore[player] / playouts, child.heuristicValue[player], 0.65);
|
|
35
|
+
|
|
36
|
+
// Based on UCB1
|
|
37
|
+
// exploration heuristic
|
|
38
|
+
const u = Math.sqrt((2 * Math.log(parent.playouts)) / playouts);
|
|
39
|
+
|
|
40
|
+
return Q + C_ks * u;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @template S
|
|
45
|
+
* @author Alex Goldring
|
|
46
|
+
* @copyright Company Named Limited (c) 2026
|
|
47
|
+
*/
|
|
48
|
+
export class MonteCarloTreeSearch {
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @type {S}
|
|
53
|
+
*/
|
|
54
|
+
rootState = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @type {StateNode|null}
|
|
59
|
+
*/
|
|
60
|
+
root = null;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Number of distinct players whose payoffs we track. Set in {@link initialize}.
|
|
64
|
+
* Sizes the per-node `heuristicValue` and `totalScore` buffers.
|
|
65
|
+
* @type {number}
|
|
66
|
+
*/
|
|
67
|
+
numPlayers = 0;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @type {(state: S, source: StateNode) => MoveEdge[]}
|
|
72
|
+
*/
|
|
73
|
+
computeValidMoves = null;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
* @type {(state: S) => StateType}
|
|
78
|
+
*/
|
|
79
|
+
computeTerminalFlag = null;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the player ID (0-based) who chooses the next move from a given state.
|
|
83
|
+
* Called when a non-terminal node is expanded. Players don't need to alternate
|
|
84
|
+
* or follow any fixed pattern — the framework just asks the game.
|
|
85
|
+
* @type {(state: S) => number}
|
|
86
|
+
*/
|
|
87
|
+
computeActivePlayer = null;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns the per-player payoff vector for a terminal state.
|
|
91
|
+
* Indexed by player ID. The game decides the zero-sum/cooperative shape.
|
|
92
|
+
* @type {(state: S) => Float64Array}
|
|
93
|
+
*/
|
|
94
|
+
computeOutcome = null;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @type {function(S):S}
|
|
99
|
+
*/
|
|
100
|
+
cloneState = null;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Heuristic estimate of a non-terminal state's per-player value.
|
|
104
|
+
* Optional — if `null`, new nodes start with zero-valued heuristic vectors.
|
|
105
|
+
* @type {null|(node: StateNode, state: S) => Float64Array}
|
|
106
|
+
*/
|
|
107
|
+
heuristic = null;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Depth to which plays will be explored
|
|
111
|
+
* @type {number}
|
|
112
|
+
*/
|
|
113
|
+
maxExplorationDepth = 1000;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
*
|
|
117
|
+
* @type {Function}
|
|
118
|
+
*/
|
|
119
|
+
random = seededRandom(0);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {S} rootState
|
|
123
|
+
* @param {number} numPlayers Total number of players whose payoffs are tracked. Use 1 for solitaire / planning.
|
|
124
|
+
* @param {(state: S, source: StateNode) => MoveEdge[]} computeValidMoves
|
|
125
|
+
* @param {(state: S) => StateType} computeTerminalFlag
|
|
126
|
+
* @param {(state: S) => number} computeActivePlayer
|
|
127
|
+
* @param {(state: S) => Float64Array} computeOutcome called on terminal states; must return a vector of length `numPlayers`
|
|
128
|
+
* @param {function(S):S} cloneState
|
|
129
|
+
* @param {null|(node: StateNode, state: S) => Float64Array} [heuristic] optional per-player estimator for intermediate states
|
|
130
|
+
*/
|
|
131
|
+
initialize(
|
|
132
|
+
{
|
|
133
|
+
rootState,
|
|
134
|
+
numPlayers,
|
|
135
|
+
computeValidMoves,
|
|
136
|
+
computeTerminalFlag,
|
|
137
|
+
computeActivePlayer,
|
|
138
|
+
computeOutcome,
|
|
139
|
+
cloneState,
|
|
140
|
+
heuristic = null
|
|
141
|
+
}
|
|
142
|
+
) {
|
|
143
|
+
assert.isInteger(numPlayers, `numPlayers`);
|
|
144
|
+
assert.greaterThanOrEqual(numPlayers, 1, `numPlayers`);
|
|
145
|
+
|
|
146
|
+
assert.isFunction(computeValidMoves, `computeValidMoves`);
|
|
147
|
+
assert.isFunction(computeTerminalFlag, `computeTerminalFlag`);
|
|
148
|
+
assert.isFunction(computeActivePlayer, `computeActivePlayer`);
|
|
149
|
+
assert.isFunction(computeOutcome, `computeOutcome`);
|
|
150
|
+
assert.isFunction(cloneState, `cloneState`);
|
|
151
|
+
|
|
152
|
+
this.numPlayers = numPlayers;
|
|
153
|
+
this.computeValidMoves = computeValidMoves;
|
|
154
|
+
this.computeTerminalFlag = computeTerminalFlag;
|
|
155
|
+
this.computeActivePlayer = computeActivePlayer;
|
|
156
|
+
this.computeOutcome = computeOutcome;
|
|
157
|
+
this.cloneState = cloneState;
|
|
158
|
+
this.heuristic = heuristic;
|
|
159
|
+
|
|
160
|
+
this.rootState = rootState;
|
|
161
|
+
|
|
162
|
+
this.root = new StateNode();
|
|
163
|
+
this.root.allocate(numPlayers);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
*
|
|
168
|
+
* @param {StateNode} node
|
|
169
|
+
* @param {S} state
|
|
170
|
+
* @returns {StateNode}
|
|
171
|
+
*/
|
|
172
|
+
selectRandom(node, state) {
|
|
173
|
+
let score;
|
|
174
|
+
|
|
175
|
+
let i, bestScore, bestMove;
|
|
176
|
+
|
|
177
|
+
const random = this.random;
|
|
178
|
+
|
|
179
|
+
while (
|
|
180
|
+
node.isExpanded() &&
|
|
181
|
+
node.moves.length > 0 &&
|
|
182
|
+
!node.isTerminal()
|
|
183
|
+
) {
|
|
184
|
+
|
|
185
|
+
bestScore = Number.NEGATIVE_INFINITY;
|
|
186
|
+
bestMove = null;
|
|
187
|
+
|
|
188
|
+
const moves = node.moves;
|
|
189
|
+
|
|
190
|
+
const numMoves = moves.length;
|
|
191
|
+
|
|
192
|
+
assert.notEqual(numMoves, 0, 'number of moves is 0, this is invalid state');
|
|
193
|
+
|
|
194
|
+
for (i = 0; i < numMoves; i++) {
|
|
195
|
+
|
|
196
|
+
const move = moves[i];
|
|
197
|
+
|
|
198
|
+
const randomRoll = random();
|
|
199
|
+
|
|
200
|
+
if (move.isTargetMaterialized()) {
|
|
201
|
+
|
|
202
|
+
const child = move.target;
|
|
203
|
+
|
|
204
|
+
const s = computeNodeSelectionScore(node, child);
|
|
205
|
+
|
|
206
|
+
assert.notNaN(s, 'computed Node score');
|
|
207
|
+
assert.isFinite(s, `computed Node score`);
|
|
208
|
+
|
|
209
|
+
score = s + randomRoll * 1e-5;
|
|
210
|
+
} else {
|
|
211
|
+
|
|
212
|
+
//use a constant value for unexplored nodes
|
|
213
|
+
score = randomRoll * 100;
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if (score > bestScore) {
|
|
219
|
+
bestScore = score;
|
|
220
|
+
bestMove = move;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!bestMove.isTargetMaterialized()) {
|
|
226
|
+
//materialize the target state
|
|
227
|
+
materializeEdgeTarget(state, node, bestMove, this);
|
|
228
|
+
|
|
229
|
+
} else {
|
|
230
|
+
//just follow the edge
|
|
231
|
+
bestMove.move(state);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
node = bestMove.target;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
return node;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Perform a playout from the root node
|
|
243
|
+
* @returns {S} final state of the playout
|
|
244
|
+
*/
|
|
245
|
+
playout() {
|
|
246
|
+
const computeValidMoves = this.computeValidMoves;
|
|
247
|
+
const computeActivePlayer = this.computeActivePlayer;
|
|
248
|
+
const computeOutcome = this.computeOutcome;
|
|
249
|
+
|
|
250
|
+
const state = this.cloneState(this.rootState);
|
|
251
|
+
|
|
252
|
+
assert.notEqual(state, this.rootState, 'cloneState must produce a new state object, instead it produced the same one');
|
|
253
|
+
|
|
254
|
+
let node = this.root;
|
|
255
|
+
|
|
256
|
+
while (!node.isTerminal() && node.depth < this.maxExplorationDepth) {
|
|
257
|
+
|
|
258
|
+
if (!node.isExpanded()) {
|
|
259
|
+
node.expand(state, computeValidMoves, computeActivePlayer, computeOutcome);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const child = this.selectRandom(node, state);
|
|
263
|
+
|
|
264
|
+
if (child === node) {
|
|
265
|
+
// prevent infinite recursion
|
|
266
|
+
// can happen e.g. when expand produced no moves (NoMoves terminal)
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
node = child;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let outcome;
|
|
274
|
+
|
|
275
|
+
if (!node.isTerminal() && node.depth >= this.maxExplorationDepth) {
|
|
276
|
+
// cap the state by depth, propagate heuristic score as payoff stand-in
|
|
277
|
+
node.type = StateType.DepthCapped;
|
|
278
|
+
outcome = node.heuristicValue;
|
|
279
|
+
} else if (node.outcome !== null) {
|
|
280
|
+
// terminal node with cached payoff
|
|
281
|
+
outcome = node.outcome;
|
|
282
|
+
} else {
|
|
283
|
+
// Defensive: shouldn't reach here under normal control flow, but if
|
|
284
|
+
// a node became terminal without a cached outcome, fall back to heuristic.
|
|
285
|
+
outcome = node.heuristicValue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
node.backpropagate(outcome);
|
|
289
|
+
|
|
290
|
+
return state;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @template S
|
|
296
|
+
* @param {S} state
|
|
297
|
+
* @param {StateNode} source
|
|
298
|
+
* @param {MoveEdge} edge
|
|
299
|
+
* @param {MonteCarloTreeSearch} search
|
|
300
|
+
*/
|
|
301
|
+
function materializeEdgeTarget(state, source, edge, search) {
|
|
302
|
+
|
|
303
|
+
const child = new StateNode();
|
|
304
|
+
child.allocate(search.numPlayers);
|
|
305
|
+
child.parent = source;
|
|
306
|
+
child.depth = source.depth + 1;
|
|
307
|
+
|
|
308
|
+
const computedState = edge.move(state);
|
|
309
|
+
|
|
310
|
+
const terminalFlag = search.computeTerminalFlag(computedState);
|
|
311
|
+
|
|
312
|
+
assert.enum(terminalFlag, StateType, 'terminalFlag');
|
|
313
|
+
|
|
314
|
+
child.type = terminalFlag;
|
|
315
|
+
|
|
316
|
+
edge.target = child;
|
|
317
|
+
|
|
318
|
+
if (terminalFlag === StateType.Undecided) {
|
|
319
|
+
// active player will be set when this node is expanded (we don't know
|
|
320
|
+
// yet whether it has any legal moves)
|
|
321
|
+
if (search.heuristic !== null) {
|
|
322
|
+
const h = search.heuristic(child, computedState);
|
|
323
|
+
|
|
324
|
+
assert.notNull(h, 'heuristic returned null');
|
|
325
|
+
|
|
326
|
+
child.heuristicValue.set(h);
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
// terminal — game decides per-player payoff, cache and mirror into heuristicValue.
|
|
330
|
+
// Copy into the node's own buffer so the user is free to reuse one across calls;
|
|
331
|
+
// outcome and heuristicValue share storage within the node (safe since bubble-up
|
|
332
|
+
// skips terminals — see StateNode.aggregateHeuristicScore).
|
|
333
|
+
const out = search.computeOutcome(computedState);
|
|
334
|
+
|
|
335
|
+
child.heuristicValue.set(out);
|
|
336
|
+
child.outcome = child.heuristicValue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Propagate the new node's value up the tree via maxN backup. This refines
|
|
340
|
+
// ancestors' heuristicValue with information from the freshly-materialized
|
|
341
|
+
// leaf — useful guidance for subsequent UCB selections at those ancestors.
|
|
342
|
+
child.bubbleUpHeuristicScore();
|
|
343
|
+
|
|
344
|
+
return computedState;
|
|
345
|
+
}
|