@woosh/meep-engine 3.0.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +188 -188
- package/README.md +152 -145
- package/editor/Editor.js +598 -598
- package/editor/EditorCameraNavigation.js +205 -205
- package/editor/EditorKeyMap.js +182 -182
- package/editor/SelectionVisualizer.js +127 -127
- package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
- package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
- package/editor/camera/camera_framing.js +104 -104
- package/editor/clipboard.js +184 -184
- package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
- package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
- package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
- package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
- package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
- package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
- package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
- package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
- package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
- package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
- package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
- package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
- package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
- package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
- package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
- package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
- package/editor/ecs/component/prototypeObjectEditor.js +265 -265
- package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
- package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
- package/editor/entity_world_bounds.js +27 -27
- package/editor/particles/lut_ops.js +148 -148
- package/editor/particles/particleEditEvents.js +12 -12
- package/editor/particles/rebuildParticleEmitter.js +31 -31
- package/editor/persistence/EditorLayoutStore.js +71 -71
- package/editor/persistence/SceneMetadata.js +52 -52
- package/editor/persistence/SceneSlotManager.js +144 -144
- package/editor/persistence/buildNewSceneDataset.js +67 -67
- package/editor/persistence/files.js +68 -68
- package/editor/persistence/scene_import.js +143 -143
- package/editor/persistence/scene_serialization.js +79 -79
- package/editor/persistence/string_buffer_codec.js +41 -41
- package/editor/process/GridDisplayProcess.js +125 -125
- package/editor/process/SymbolicDisplayProcess.js +106 -108
- package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
- package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
- package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
- package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
- package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
- package/editor/process/symbolic/SymbolicDisplay.js +62 -62
- package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
- package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
- package/editor/prototypeEditorShell.js +156 -156
- package/editor/selection/editor_pick.js +130 -130
- package/editor/selection/screen_rect_frustum.js +119 -119
- package/editor/style/controls.scss +168 -168
- package/editor/style/inspector.scss +223 -223
- package/editor/templates/entity_templates.js +167 -167
- package/editor/tools/GridPaintTool.js +273 -273
- package/editor/tools/SelectionTool.js +307 -307
- package/editor/tools/TransformToolV2.js +378 -378
- package/editor/tools/engine/ToolEngine.js +188 -188
- package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
- package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
- package/editor/tools/v2/TransformControls.js +971 -971
- package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
- package/editor/tools/v2/handle/gizmo_draw.js +399 -399
- package/editor/tools/v2/handle/gizmo_handles.js +364 -364
- package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
- package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
- package/editor/view/ecs/components/DatGuiController.js +2 -2
- package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
- package/editor/view/ecs/components/common/TextController.js +59 -59
- package/editor/view/library/MeshLibraryView.js +175 -175
- package/editor/view/library/mesh_library_drag.js +63 -63
- package/editor/view/library/model_thumbnail_source.js +86 -86
- package/editor/view/makeEntityDecorators.js +125 -125
- package/editor/view/node-graph/NodeGraphSelection.js +89 -89
- package/editor/view/node-graph/NodeView.js +211 -211
- package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
- package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
- package/editor/view/node-graph/connection_wire_geometry.js +107 -107
- package/editor/view/particles/ColorLutGradientView.js +291 -291
- package/editor/view/particles/ScalarLutCurveView.js +279 -279
- package/editor/view/prepareMeshLibrary.js +188 -188
- package/editor/view/shell/SceneTreePanelView.js +119 -119
- package/editor/view/v2/SplitView.js +2 -2
- package/package.json +94 -94
- package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
- package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
- package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
- package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
- package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
- package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
- package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
- package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
- package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
- package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
- package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
- package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
- package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
- package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
- package/src/REVIEW_2026_08_06/adv-core.md +511 -511
- package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
- package/src/REVIEW_2026_08_06/adv-view.md +446 -446
- package/src/REVIEW_2026_08_06/api-core.md +296 -296
- package/src/REVIEW_2026_08_06/api-engine.md +195 -195
- package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
- package/src/REVIEW_2026_08_06/core-binary.md +348 -348
- package/src/REVIEW_2026_08_06/core-collection.md +376 -376
- package/src/REVIEW_2026_08_06/core-math.md +221 -221
- package/src/REVIEW_2026_08_06/core-process.md +266 -266
- package/src/REVIEW_2026_08_06/core-science.md +223 -223
- package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
- package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
- package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
- package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
- package/src/REVIEW_2026_08_06/engine-input.md +197 -197
- package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
- package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
- package/src/REVIEW_2026_08_06/generation.md +176 -176
- package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
- package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
- package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
- package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
- package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
- package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
- package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
- package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
- package/src/REVIEW_2026_08_06/view.md +667 -667
- package/src/REVIEW_2026_08_06.md +610 -610
- package/src/avif/codec/dec/avif_dec.cpp +233 -233
- package/src/avif/codec/dec/avif_dec.js +16 -16
- package/src/core/binary/BitImage2.js +246 -246
- package/src/core/binary/align_32.js +21 -21
- package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
- package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
- package/src/core/binary/hash/XXH128Hash.js +55 -55
- package/src/core/binary/hash/XXH3_SECRET.js +25 -25
- package/src/core/binary/hash/xxh3_128.js +341 -341
- package/src/core/binary/hash/xxh3_64.js +241 -241
- package/src/core/binary/hash/xxh3_common.js +476 -476
- package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
- package/src/core/binary/lz4/lz4_compress_block.js +385 -385
- package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
- package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
- package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
- package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
- package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
- package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
- package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
- package/src/core/binary/meshopt/meshopt_decode.js +73 -73
- package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
- package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
- package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
- package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
- package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
- package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
- package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
- package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
- package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
- package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
- package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
- package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
- package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
- package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
- package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
- package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
- 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_capsule.js +101 -101
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
- 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/visual/BVHGeometry.js +131 -131
- package/src/core/bvh8/BVH8.js +436 -436
- package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
- package/src/core/bvh8/build/BVH8Converter.js +588 -588
- package/src/core/bvh8/build/TriangleCluster.js +122 -122
- package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
- package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
- package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
- package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
- package/src/core/bvh8/build/byte.js +9 -9
- package/src/core/bvh8/build/encode_bounds_e.js +11 -11
- package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
- package/src/core/bvh8/bvh8_count_primitives.js +98 -98
- package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
- package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
- package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
- package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
- package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
- package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
- package/src/core/bvh8/bvh8_quality.js +72 -72
- package/src/core/bvh8/bvh8_validate_structure.js +87 -87
- package/src/core/clipboard/obtainClipBoard.js +29 -29
- package/src/core/clipboard/safeClipboardReadText.js +55 -55
- package/src/core/clipboard/safeClipboardWriteText.js +23 -23
- package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
- package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
- package/src/core/collection/union-find/union_find.js +79 -79
- package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
- package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
- package/src/core/color/colormap/MAGMA_LUT.js +26 -26
- package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
- package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
- package/src/core/color/lab/lab_distance_hyab.js +23 -23
- package/src/core/color/lab/xyz_to_lab.js +62 -62
- package/src/core/color/operations/color_darken.js +24 -24
- package/src/core/color/operations/color_desaturate.js +24 -24
- package/src/core/color/operations/color_lighten.js +24 -24
- package/src/core/color/operations/color_saturate.js +24 -24
- package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
- package/src/core/color/tonemap/tonemap_aces.js +41 -41
- package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
- package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
- package/src/core/dom/isImageBitmap.js +12 -12
- package/src/core/function/frameThrottle.js +23 -23
- package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
- package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
- package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
- package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
- package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
- package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
- package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
- package/src/core/geom/2d/intersect_ray_2d.js +7 -7
- package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
- package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
- package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
- package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
- package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
- package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
- package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
- package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
- package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
- package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
- package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
- package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
- package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
- package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
- package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
- package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
- package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
- package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
- package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
- package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
- package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
- package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
- package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
- package/src/core/geom/2d/v2_morton_encode.js +23 -23
- package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
- package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
- package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
- package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
- package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
- package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
- package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
- package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
- package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
- package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
- package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
- package/src/core/geom/3d/atlas/Chart.js +77 -77
- package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
- package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
- package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
- package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
- package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
- package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
- package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
- package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
- package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
- package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
- package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
- package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
- package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
- package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
- package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
- package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
- package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
- package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
- package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
- package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
- package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
- package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
- package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
- package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
- package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
- package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
- package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
- package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
- package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
- package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
- package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
- package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
- package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
- package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
- package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
- package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
- package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
- package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
- package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
- package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
- package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
- package/src/core/geom/3d/gjk/gjk.js +430 -430
- package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
- package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
- package/src/core/geom/3d/gjk/mpr.js +368 -368
- package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
- package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
- package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
- package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
- package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
- package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
- package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
- package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
- package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
- package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
- package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
- package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
- package/src/core/geom/3d/mat4/m4_compose.js +22 -22
- package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
- package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
- package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
- package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
- package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
- package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
- package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
- package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
- package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
- package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
- package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
- package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
- package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
- package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
- package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
- package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
- package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
- package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
- package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
- package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
- package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
- package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
- package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
- package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
- package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
- package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
- package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
- package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/point/point3_raycast.js +47 -0
- package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
- package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
- package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
- package/src/core/geom/3d/quadric/Quadric3.js +282 -282
- package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
- package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
- package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
- package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
- package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
- package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
- package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
- package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
- package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
- package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
- package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
- package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
- package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
- package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
- package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
- package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
- package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
- package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
- package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
- package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
- package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
- package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
- package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +88 -63
- package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
- package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/Triangle3D.js +26 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
- package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
- package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
- package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
- package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
- package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
- package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
- package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
- package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
- package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
- package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
- package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
- package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
- package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
- package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
- package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
- package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
- package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
- package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
- package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
- package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
- package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
- package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
- package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
- package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
- package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
- package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
- package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
- package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
- package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
- package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
- package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
- package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
- package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
- package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
- package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
- package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
- package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
- package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
- package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
- package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
- package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
- package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
- package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
- package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
- package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
- package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
- package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
- package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
- package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
- package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
- package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
- package/src/core/geom/mat3/m3_cm_invert.js +7 -7
- package/src/core/geom/mat3/m3_make_rotation.js +5 -5
- package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
- package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
- package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
- package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
- package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
- package/src/core/geom/packing/max-rect/cutArea.js +7 -7
- package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
- package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
- package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
- package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
- package/src/core/geom/random/randomPointInBox.js +7 -7
- package/src/core/geom/random/randomPointInCircle.js +7 -7
- package/src/core/geom/random/randomPointInPoint.js +7 -7
- package/src/core/geom/random/randomPointInSphere.js +7 -7
- package/src/core/geom/random/randomPointOnBox.js +7 -7
- package/src/core/geom/random/randomPointOnSphere.js +7 -7
- package/src/core/geom/random/random_orthonormal_basis.js +31 -31
- package/src/core/geom/vec/vector_axpy.js +15 -15
- package/src/core/geom/vec/vector_axpy_offset.js +20 -20
- package/src/core/geom/vec/vector_copy.js +7 -7
- package/src/core/geom/vec/vector_dot_offset.js +25 -25
- package/src/core/geom/vec/vector_scale_array.js +7 -7
- package/src/core/geom/vec2/v2_rotate.js +21 -21
- package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
- package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
- package/src/core/geom/vec3/v3_angle_between.js +7 -7
- package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
- package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
- package/src/core/geom/vec3/v3_array_centroid.js +7 -7
- package/src/core/geom/vec3/v3_array_copy.js +7 -7
- package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
- package/src/core/geom/vec3/v3_array_equals.js +7 -7
- package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
- package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
- package/src/core/geom/vec3/v3_array_normalize.js +7 -7
- package/src/core/geom/vec3/v3_array_scale.js +7 -7
- package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
- package/src/core/geom/vec3/v3_cotangent.js +33 -33
- package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
- package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
- package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
- package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
- package/src/core/geom/vec3/v3_multiply.js +23 -23
- package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
- package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
- package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
- package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
- package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
- package/src/core/geom/vec3/v3_reflect.js +24 -24
- package/src/core/geom/vec3/v3_scale_array.js +18 -18
- package/src/core/geom/vec3/v3_triple_cross.js +45 -45
- package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
- package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
- package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
- package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
- package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
- package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
- package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
- package/src/core/graph/graph_k_means_cluster.js +250 -250
- package/src/core/graph/layout/CircleLayout.js +1 -1
- package/src/core/graph/layout/box/BoxLayouter.js +1 -1
- package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
- package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
- package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
- package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
- package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
- package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
- package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
- package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
- package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
- package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
- package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
- package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
- package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
- package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
- package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
- package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
- package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
- package/src/core/graph/metis/native/refine/project_kway.js +43 -43
- package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
- package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
- package/src/core/math/complex/complex_horner_eval.js +31 -31
- package/src/core/math/computeWholeDivisorLow.js +33 -33
- package/src/core/math/f32_next_down.js +24 -24
- package/src/core/math/f32_next_up.js +27 -27
- package/src/core/math/hash/hash_mix2.js +22 -22
- package/src/core/math/hash/hash_mix3.js +19 -19
- package/src/core/math/linalg/cg/cg_solve.js +106 -106
- package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
- package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
- package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
- package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
- package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
- package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
- package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
- package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
- package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
- package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
- package/src/core/math/linalg/polynomial_add_into.js +29 -29
- package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
- package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
- package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
- package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
- package/src/core/math/linalg/polynomial_multiply.js +41 -41
- package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
- package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
- package/src/core/math/linalg/polynomial_scale_into.js +17 -17
- package/src/core/math/linalg/polynomial_sub_into.js +29 -29
- package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
- package/src/core/math/linalg/sor_optimal_omega.js +42 -42
- package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
- package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
- package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
- package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
- package/src/core/math/lookup/ParameterLookupTable.js +495 -495
- package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
- package/src/core/math/noise/curl_noise_3d.js +18 -18
- package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
- package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
- package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
- package/src/core/math/random/randomSeed.js +16 -16
- package/src/core/math/random/random_pick_weighted_index.js +26 -26
- package/src/core/math/round_half_away_from_zero.js +13 -13
- package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
- package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
- package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
- package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
- package/src/core/math/statistics/hammersley_sequence.js +53 -53
- package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
- package/src/core/path/convertPathToURL.js +107 -107
- package/src/core/process/delay.js +16 -16
- package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
- package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
- package/src/core/science/units/NamedUnit.js +48 -48
- package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
- package/src/core/science/units/UnitDimension.js +28 -28
- package/src/core/science/units/format_quantity.js +74 -74
- package/src/core/science/units/magnitude_prefix.js +127 -127
- package/src/core/science/units/si/AMPERE.js +13 -13
- package/src/core/science/units/si/CANDELA.js +13 -13
- package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
- package/src/core/science/units/si/KELVIN.js +13 -13
- package/src/core/science/units/si/KILOGRAM.js +13 -13
- package/src/core/science/units/si/METER.js +13 -13
- package/src/core/science/units/si/MOLE.js +13 -13
- package/src/core/science/units/si/SECOND.js +13 -13
- package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
- package/src/core/science/units/si/derived/COULOMB.js +13 -13
- package/src/core/science/units/si/derived/FARAD.js +13 -13
- package/src/core/science/units/si/derived/GRAY.js +16 -16
- package/src/core/science/units/si/derived/HENRY.js +13 -13
- package/src/core/science/units/si/derived/HERTZ.js +16 -16
- package/src/core/science/units/si/derived/JOULE.js +13 -13
- package/src/core/science/units/si/derived/KATAL.js +13 -13
- package/src/core/science/units/si/derived/LUMEN.js +14 -14
- package/src/core/science/units/si/derived/LUX.js +13 -13
- package/src/core/science/units/si/derived/NEWTON.js +13 -13
- package/src/core/science/units/si/derived/OHM.js +13 -13
- package/src/core/science/units/si/derived/PASCAL.js +13 -13
- package/src/core/science/units/si/derived/SIEMENS.js +13 -13
- package/src/core/science/units/si/derived/SIEVERT.js +16 -16
- package/src/core/science/units/si/derived/TESLA.js +13 -13
- package/src/core/science/units/si/derived/VOLT.js +13 -13
- package/src/core/science/units/si/derived/WATT.js +13 -13
- package/src/core/science/units/si/derived/WEBER.js +13 -13
- package/src/core/science/units/unit_matrix_to_string.js +403 -403
- package/src/engine/Engine.js +726 -726
- package/src/engine/EngineHarness.js +590 -590
- package/src/engine/REVIEW_2026_07_03.md +211 -211
- package/src/engine/animation/AnimatedActions.js +81 -81
- package/src/engine/animation/AnimationUtils.js +252 -252
- package/src/engine/animation/async/TimeSeries.js +300 -300
- package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
- package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
- package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
- package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
- package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
- package/src/engine/asset/GameAssetType.d.ts +0 -1
- package/src/engine/asset/GameAssetType.js +0 -1
- package/src/engine/asset/load_model_scene_bundle.js +27 -27
- package/src/engine/asset/loaders/font/FontAsset.js +21 -21
- package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
- package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
- package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
- package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
- package/src/engine/control/first-person/DESIGN.md +640 -640
- package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
- package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
- package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
- package/src/engine/control/first-person/math/Spring.js +71 -71
- package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
- package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
- package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
- package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
- package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
- package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
- package/src/engine/control/first-person/math/stepTowards.js +20 -20
- package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
- package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
- package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
- package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
- package/src/engine/ecs/EntityManager.d.ts +7 -3
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +10 -5
- package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
- package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
- package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
- package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
- package/src/engine/ecs/async/WorkerSystem.js +702 -702
- package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
- package/src/engine/ecs/fow/FogOfWar.js +707 -707
- package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
- package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
- package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
- package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
- package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
- package/src/engine/ecs/ik/IKProblem.js +42 -42
- package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
- package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
- package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
- package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
- package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
- package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
- package/src/engine/ecs/parent/EntityNode.js +2 -2
- package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
- package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
- package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
- package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
- package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
- package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
- package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
- package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
- package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
- package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
- package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
- package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
- package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
- package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
- package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
- package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
- package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
- package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
- package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
- package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
- package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
- package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
- package/src/engine/ecs/transform/Transform64.js +814 -814
- package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
- package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
- package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
- package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
- package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
- package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
- package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
- package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
- package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
- package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
- package/src/engine/graphics/FrameThrottle.js +2 -2
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
- package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
- package/src/engine/graphics/ecs/camera/Camera.js +165 -165
- package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
- package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
- package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
- package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
- package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
- package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
- package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
- package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
- package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
- package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
- package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
- package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
- package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
- package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
- package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
- package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
- package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
- package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
- package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
- package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
- package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
- package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
- package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
- package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
- package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
- package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
- package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
- package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
- package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
- package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
- package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
- package/src/engine/graphics/ecs/water/Water.js +104 -104
- package/src/engine/graphics/geometry/AttributeData.js +84 -84
- package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
- package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
- package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
- package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
- package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
- package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
- package/src/engine/graphics/impostors/voxel/README.md +149 -149
- package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
- package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
- package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
- package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
- package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
- package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
- package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
- package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
- package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
- package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
- package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
- package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
- package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
- package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
- package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
- package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
- package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
- package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
- package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
- package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
- package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
- package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
- package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
- package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
- package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
- package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
- package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
- package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
- package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
- package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
- package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
- package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
- package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
- package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
- package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
- package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
- package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
- package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
- package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
- package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
- package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
- package/src/engine/graphics/texture/isImageBitmap.js +2 -2
- package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
- package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
- package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
- package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
- package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
- package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
- package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
- package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
- package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
- package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
- package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
- package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
- package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
- package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
- package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
- package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
- package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
- package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
- package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
- package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
- package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
- package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
- package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
- package/src/engine/graphics/texture/shade_image_preview.js +103 -103
- package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
- package/src/engine/graphics/trail/tube/TubeX.js +573 -573
- package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
- package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
- package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
- package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
- package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
- package/src/engine/graphics3/AnimationSystem3.js +352 -352
- package/src/engine/graphics3/CameraSystem3.js +170 -170
- package/src/engine/graphics3/DecalSystem3.js +441 -441
- package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
- package/src/engine/graphics3/GraphicsEngine3.js +776 -776
- package/src/engine/graphics3/HighlightSystem3.js +241 -241
- package/src/engine/graphics3/LightSystem3.js +211 -211
- package/src/engine/graphics3/MeshSystem3.js +646 -646
- package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
- package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
- package/src/engine/graphics3/PickingQuery.js +48 -48
- package/src/engine/graphics3/PickingResult.js +47 -47
- package/src/engine/graphics3/PickingSystem3.js +273 -273
- package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
- package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
- package/src/engine/graphics3/TerrainSystem3.js +566 -566
- package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
- package/src/engine/graphics3/Trail3DSystem3.js +221 -221
- package/src/engine/graphics3/WaterSystem3.js +293 -293
- package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
- package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
- package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
- package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
- package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
- package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
- package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
- package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
- package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
- package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
- package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
- package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
- package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
- package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
- package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
- package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
- package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
- package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
- package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
- package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
- package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
- package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
- package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
- package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
- package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
- package/src/engine/graphics3/load_cube_environment.js +68 -68
- package/src/engine/graphics3/make_default_environment.js +107 -107
- package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
- package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
- package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
- package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
- package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
- package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
- package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
- package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
- package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
- package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
- package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
- package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
- package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
- package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
- package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
- package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
- package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
- package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
- package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
- package/src/engine/input/devices/GamepadDevice.js +466 -466
- package/src/engine/input/devices/ManualInputDevice.js +152 -152
- package/src/engine/input/devices/PointerCoordinates.js +26 -26
- package/src/engine/input/devices/events/GamepadEvents.js +10 -10
- package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
- package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
- package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
- package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
- package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
- package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
- package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
- package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
- package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
- package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
- package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
- package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
- package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
- package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
- package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
- package/src/engine/input/ecs/components/Input.js +118 -118
- package/src/engine/input/ecs/components/InputController.js +78 -78
- package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
- package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
- package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
- package/src/engine/input/ecs/ism/InputBinding.js +71 -71
- package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
- package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
- package/src/engine/input/ecs/ism/InputDirection.js +33 -33
- package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
- package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
- package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
- package/src/engine/input/ecs/ism/InputSource.js +67 -67
- package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
- package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
- package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
- package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
- package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
- package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
- package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
- package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
- package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
- package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
- package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
- package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
- package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
- package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
- package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
- package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
- package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
- package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
- package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
- package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
- package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
- package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
- package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
- package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
- package/src/engine/input/ecs/systems/InputSystem.js +162 -162
- package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
- package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
- package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
- package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
- package/src/engine/interpolation/Interpoland.js +49 -49
- package/src/engine/interpolation/Interpolated.js +149 -149
- package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
- package/src/engine/interpolation/pose_interpoland.js +27 -27
- package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
- package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
- package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
- package/src/engine/network/CONGESTION_CONTROL.md +87 -87
- package/src/engine/network/README.md +52 -52
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
- package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
- package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
- package/src/engine/network/convertPathToURL.js +2 -2
- package/src/engine/network/core/MalformedPacketError.js +26 -26
- package/src/engine/network/core/quantize/quantize_float.js +73 -73
- package/src/engine/network/core/quantize/quantize_position.js +65 -65
- package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
- package/src/engine/network/core/sequence/seq16.js +69 -69
- package/src/engine/network/core/sequence/seq32.js +73 -73
- package/src/engine/network/ecs/NetworkSystem.js +84 -84
- package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
- package/src/engine/network/ecs/owner_authorization.js +55 -55
- package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
- package/src/engine/network/replication/ScopeFilter.js +71 -71
- package/src/engine/network/sim/ActionLog.js +152 -152
- package/src/engine/network/sim/SimAction.js +273 -273
- package/src/engine/network/sim/Snapshotter.js +222 -222
- package/src/engine/network/state/FrameBufferRing.js +168 -168
- package/src/engine/network/state/InputRing.js +134 -134
- package/src/engine/network/state/PriorityAccumulator.js +180 -180
- package/src/engine/network/time/TimeDilation.js +62 -62
- package/src/engine/network/transport/Channel.js +336 -336
- package/src/engine/network/transport/Transport.js +203 -203
- package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
- package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
- package/src/engine/network/transport/fragments/fragment_send.js +147 -147
- package/src/engine/network/transport/fragments/packet_size.js +101 -101
- package/src/engine/network/xhr.js +23 -23
- package/src/engine/physics/BULLET_REVIEW.md +945 -945
- package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
- package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
- package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
- package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
- package/src/engine/physics/JOLT_REVIEW.md +913 -913
- package/src/engine/physics/PLAN.md +1120 -1094
- package/src/engine/physics/RAPIER_REVIEW.md +934 -934
- package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
- package/src/engine/physics/REVIEW_002.md +151 -151
- package/src/engine/physics/REVIEW_003.md +166 -166
- package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
- package/src/engine/physics/constraint/DofMode.js +35 -35
- package/src/engine/physics/constraint/solve_constraints.js +1 -1
- package/src/engine/physics/contact/combine_material.js +35 -35
- package/src/engine/physics/ecs/BodyKind.js +29 -29
- package/src/engine/physics/ecs/Collider.js +177 -177
- package/src/engine/physics/ecs/ColliderFlags.js +15 -15
- package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
- package/src/engine/physics/ecs/Joint.js +475 -475
- package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
- package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
- package/src/engine/physics/ecs/SleepState.js +12 -12
- package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
- package/src/engine/physics/fluid/FluidField.js +867 -867
- package/src/engine/physics/fluid/FluidSimulator.js +832 -832
- package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
- package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
- package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
- package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
- package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
- package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
- package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
- package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
- package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
- package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
- package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
- package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
- package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
- package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
- package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
- package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
- package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
- package/src/engine/physics/fluid/prototype.js +126 -126
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
- package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
- package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
- package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
- package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
- package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
- package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
- package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
- package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
- package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
- package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
- package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
- package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
- package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
- package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
- package/src/engine/physics/integration/integrate_position.js +97 -97
- package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
- package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
- package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
- package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
- package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
- package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
- package/src/engine/physics/persistence/solver_caches.js +309 -309
- package/src/engine/physics/queries/overlap_shape.js +187 -187
- package/src/engine/physics/queries/raycast.d.ts +7 -4
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +11 -5
- package/src/engine/physics/queries/shape_cast.js +417 -417
- package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
- package/src/engine/physics/solver/friction_cone.js +37 -37
- package/src/engine/physics/solver/solve_contacts.js +1 -1
- package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
- package/src/engine/platform/WebEnginePlatform.js +1 -2
- package/src/engine/scene/SerializedScene.d.ts +2 -2
- package/src/engine/scene/SerializedScene.d.ts.map +1 -1
- package/src/engine/scene/transitionToScene.js +121 -121
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +33 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
- package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
- package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
- package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
- package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
- package/src/engine/sound/simulation/README.md +277 -277
- package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
- package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
- package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
- package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
- package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
- package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
- package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
- package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
- package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
- package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
- package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
- package/src/engine/sound/simulation/core/RayHit.js +50 -50
- package/src/engine/sound/simulation/core/VolumeField.js +149 -149
- package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
- package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
- package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
- package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
- package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
- package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
- package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
- package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
- package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
- package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
- package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
- package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
- package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
- package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
- package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
- package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
- package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
- package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
- package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
- package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
- package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
- package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
- package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
- package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
- package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
- package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
- package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
- package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
- package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
- package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
- package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
- package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
- package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
- package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
- package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
- package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
- package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
- package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
- package/src/engine/sound/sopra/README.md +643 -643
- package/src/engine/sound/sopra/SopraEngine.js +432 -432
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
- package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
- package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
- package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
- package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
- package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
- package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
- package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
- package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
- package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
- package/src/engine/sound/sopra/runtime/Voice.js +145 -145
- package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
- package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
- package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
- package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
- package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
- package/src/engine/ui/DraggableAspect.js +115 -115
- package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
- package/src/generation/COORDINATES.md +54 -54
- package/src/generation/GridGenerationResource.js +24 -24
- package/src/generation/GridTaskGroup.js +214 -214
- package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
- package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
- package/src/generation/automata/CellularAutomata.js +22 -22
- package/src/generation/compute_writes_union.js +25 -25
- package/src/generation/filtering/CellFilter.js +117 -117
- package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
- package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
- package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
- package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
- package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
- package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
- package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
- package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
- package/src/generation/grid/GridData.js +233 -233
- package/src/generation/grid/coords/grid_to_texel.js +10 -10
- package/src/generation/grid/coords/texel_to_grid.js +10 -10
- package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
- package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
- package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
- package/src/generation/grid/generation/GridTaskSequence.js +58 -58
- package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
- package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
- package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
- package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
- package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
- package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
- package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
- package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
- package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
- package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
- package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
- package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
- package/src/generation/placement/action/GridCellAction.js +45 -45
- package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
- package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
- package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
- package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
- package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
- package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
- package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
- package/src/generation/theme/ThemeEngine.js +666 -666
- package/src/shade/RENDERER_CONTRACT.md +568 -0
- package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
- package/src/shade/device/ShadeDeviceFailure.js +104 -104
- package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
- package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
- package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
- package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
- package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
- package/src/shade/device/mock/SoftwareGPUError.js +63 -63
- package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
- package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
- package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
- package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
- package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
- package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
- package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
- package/src/shade/device/mock/texture_copy.js +287 -287
- package/src/shade/device/timing/GPUTimer.js +285 -285
- package/src/shade/device/timing/GPUTimerArray.js +205 -205
- package/src/shade/playground/add_random_lights.js +49 -49
- package/src/shade/playground/add_random_meshes.js +83 -83
- package/src/shade/playground/animation_prototype.js +508 -508
- package/src/shade/playground/animation_prototype_skinning.js +647 -647
- package/src/shade/playground/animation_prototype_stress.js +765 -765
- package/src/shade/playground/enable_picker.js +59 -59
- package/src/shade/playground/gltf_skinned_prototype.js +560 -560
- package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
- package/src/shade/playground/gltf_viewer.js +282 -282
- package/src/shade/playground/load_default_environment.js +19 -19
- package/src/shade/playground/main.js +2727 -2727
- package/src/shade/playground/make_animated_instances.js +117 -117
- package/src/shade/playground/prototype_virtual_texture.js +274 -274
- package/src/shade/playground/prototype_volumetrics_01.js +206 -206
- package/src/shade/playground/sample.js +234 -234
- package/src/shade/playground/sample_load_gltf.js +102 -102
- package/src/shade/playground/spread_node3d_grid.js +19 -19
- package/src/shade/renderer/Renderer.js +2436 -2436
- package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
- package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
- package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
- package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
- package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
- package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
- package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
- package/src/shade/renderer/binding/BindGroupManager.js +282 -282
- package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
- package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
- package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
- package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
- package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
- package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
- package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
- package/src/shade/renderer/camera/Camera.js +281 -281
- package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
- package/src/shade/renderer/camera/orbital/README.md +84 -84
- package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
- package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
- package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
- package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
- package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
- package/src/shade/renderer/extension/FrameContext.js +263 -263
- package/src/shade/renderer/extension/FramePhase.js +104 -104
- package/src/shade/renderer/extension/FrameRecord.js +126 -126
- package/src/shade/renderer/extension/GBufferTextures.js +83 -83
- package/src/shade/renderer/extension/PresentTarget.js +34 -34
- package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
- package/src/shade/renderer/extension/RenderExtension.js +69 -69
- package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
- package/src/shade/renderer/extension/SceneColor.js +40 -40
- package/src/shade/renderer/extension/ViewTextures.js +78 -78
- package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
- package/src/shade/renderer/geometry/Geometry.js +608 -608
- package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
- package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
- package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
- package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
- package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
- package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
- package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
- package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
- package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
- package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
- package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
- package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
- package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
- package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
- package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
- package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
- package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
- package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
- package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
- package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
- package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
- package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
- package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
- package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
- package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
- package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
- package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
- package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
- package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
- package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
- package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
- package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
- package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
- package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
- package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
- package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
- package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
- package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
- package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
- package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
- package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
- package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
- package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
- package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
- package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
- package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
- package/src/shade/renderer/light/make_sunlight.js +35 -35
- package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
- package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
- package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
- package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
- package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
- package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
- package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
- package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
- package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
- package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
- package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
- package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
- package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
- package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
- package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
- package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
- package/src/shade/renderer/loader/usd/UsdError.js +33 -33
- package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
- package/src/shade/renderer/loader/usd/load_usd.js +226 -226
- package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
- package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
- package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
- package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
- package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
- package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
- package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
- package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
- package/src/shade/renderer/material/ShadeMaterial.js +2 -2
- package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
- package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
- package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
- package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
- package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
- package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
- package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
- package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
- package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
- package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
- package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
- package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
- package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
- package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
- package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
- package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
- package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
- package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
- package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
- package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
- package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
- package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
- package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
- package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
- package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
- package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
- package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
- package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
- package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
- package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
- package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
- package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
- package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
- package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
- package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
- package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
- package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
- package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
- package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
- package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
- package/src/shade/renderer/scene/DynamicMesh.js +70 -70
- package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
- package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
- package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
- package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
- package/src/shade/renderer/scene/TransformAuthority.js +33 -33
- package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
- package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
- package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
- package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
- package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
- package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
- package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
- package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
- package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
- package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
- package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
- package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
- package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
- package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
- package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
- package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
- package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
- package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
- package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
- package/src/shade/renderer/texture/virtual/README.md +276 -276
- package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
- package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
- package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
- package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
- package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
- package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
- package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
- package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
- package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
- package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
- package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
- package/src/shade/renderer/water/shader_water.js +246 -246
- package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
- package/src/shade/vitest.setup.mjs +89 -89
- package/src/shade/wgsl/validate_wgsl_source.js +316 -316
- package/src/view/controller/controls/NumericIntervalControl.js +90 -90
- package/src/view/controller/controls/Vector2Control.js +83 -83
- package/src/view/controller/controls/Vector3Control.js +90 -90
- package/src/view/elements/Group.js +56 -56
- package/src/view/elements/drawCheckers.js +33 -33
- package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
- package/src/view/graphics/WebGLContextFailureView.js +76 -76
- package/src/view/layout/DockPanel.js +51 -51
- package/src/view/layout/SplitView.js +365 -365
- package/src/view/layout/show_popup_menu.js +81 -81
- package/src/view/minimap/MinimapCanvasView.js +411 -411
- package/src/view/minimap/MinimapImageCache.js +88 -88
- package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
- package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
- package/src/view/minimap/MinimapMarkerCollection.js +180 -180
- package/src/view/minimap/dom/MinimapCameraView.js +206 -206
- package/src/view/minimap/minimap_focus_area.js +97 -97
- package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
- package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
- package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
- package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
- package/src/engine/ecs/attachment/Attachment.js +0 -57
- package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
- package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
- package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
- package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
- package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
- package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
- package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
- package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
- package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
- package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
- package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
- package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
- package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
- package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
- package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
- package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
- package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
- package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
- package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
- package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
- package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
- package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
- package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
|
@@ -1,1523 +1,1523 @@
|
|
|
1
|
-
# Input System Mapping — design
|
|
2
|
-
|
|
3
|
-
A binding layer for keyboard, pointer buttons and gamepad, replacing `Input`/`InputSystem`, the keyboard half
|
|
4
|
-
of `InputController`/`InputControllerSystem`, `EditorKeyMap` and `Tool.handleKeyboardEvent`.
|
|
5
|
-
|
|
6
|
-
Status: **design, not yet implemented.** The only file in this directory today is
|
|
7
|
-
[`InputBinding.js`](InputBinding.js) (the `{path, event}` record used by
|
|
8
|
-
[`Input`](../components/Input.js)); it is superseded by §4.
|
|
9
|
-
|
|
10
|
-
> **Revision 3.** Rewritten after an adversarial review of revision 2 found three defects that made it
|
|
11
|
-
> unimplementable, and several false claims about the existing codebase. What changed, and why, is in §17.
|
|
12
|
-
> The three that matter: the sequence matcher's reset paths were broken (§5); occlusion could not reach polled
|
|
13
|
-
> reads, so a modal dialog could not stop a camera (§8); and coordinate bindings had no home in either the
|
|
14
|
-
> component or the wire format (§9.1, §10). Two scope decisions were taken at the same time: **pointer
|
|
15
|
-
> gestures are out of scope** (§1.1), and **`SimpleStateMachine` is not used** (§5.6).
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 0. Summary
|
|
20
|
-
|
|
21
|
-
Two decisions carry the design.
|
|
22
|
-
|
|
23
|
-
> **1. Every input this system binds is either a switch or a coordinate.** `InputDeviceSwitch` already exists
|
|
24
|
-
> and is already what keyboard keys, pointer buttons and gamepad buttons *are*. A coordinate is an
|
|
25
|
-
> N-dimensional analog channel — stick deflection, wheel ticks, pointer position.
|
|
26
|
-
>
|
|
27
|
-
> **2. Every trigger is a predicate over (held switches + its own state), and a binding fires on that
|
|
28
|
-
> predicate's rising edge** — evaluated the instant an edge arrives, never on a frame boundary.
|
|
29
|
-
|
|
30
|
-
| Requirement | Falls out of |
|
|
31
|
-
|---|---|
|
|
32
|
-
| Combinations (chords) | A chord is the AND of its children's predicates |
|
|
33
|
-
| Sequences | A sequence is a predicate over a small linear matcher, advanced per edge (§5) |
|
|
34
|
-
| Cross-device combos | A chord cannot tell which device a child came from — there is nothing to make work |
|
|
35
|
-
| Arbitrary devices | A device is anything that hands out switches and coordinates (§3) |
|
|
36
|
-
| Layers, top wins | Cascade over an action table, compiled once per change, like CSS (§7) |
|
|
37
|
-
| Serializable bindings | A binding is `(trigger, action name)`; both halves are plain data (§6.1, §10) |
|
|
38
|
-
| No added latency | Evaluation is eager on the edge; the tick services only time (§2) |
|
|
39
|
-
|
|
40
|
-
Two defects in the current systems that this fixes on the way past:
|
|
41
|
-
|
|
42
|
-
1. **Binding resolution can throw.** `resolvePath` throws on an unresolvable path
|
|
43
|
-
([`resolvePathByArray.js:47`](../../../../core/json/resolvePathByArray.js)), which is why a gamepad binding
|
|
44
|
-
is a load-order landmine (see [`GAMEPAD_API_PROPOSAL.md §3`](../../GAMEPAD_API_PROPOSAL.md)). Source
|
|
45
|
-
resolution here is total.
|
|
46
|
-
2. **Nothing can express a combination.** `Ctrl+Z` is not expressible as a path, which is why
|
|
47
|
-
[`EditorKeyMap`](../../../../../editor/EditorKeyMap.js) exists as a parallel, unserializable, unrebindable
|
|
48
|
-
input system made of `if (event.ctrlKey)`.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## 1. Scope
|
|
53
|
-
|
|
54
|
-
### 1.1 Pointer gestures are out of scope
|
|
55
|
-
|
|
56
|
-
`PointerDevice` synthesizes gestures on top of its buttons — `tap`, `drag`, `dragStart`, `dragEnd`, `move`,
|
|
57
|
-
`wheel`, `pinch`, `pinchStart`, `pinchEnd` — and those are **not** modelled here. They are stateful
|
|
58
|
-
constructions with their own parameters (`tap` fires on *release*, gated on ≤ 10 px of travel and ≤ 1 s;
|
|
59
|
-
`PointerDevice.js:20-40`), not switches, and folding them into the trigger algebra is a separate piece of work.
|
|
60
|
-
|
|
61
|
-
This is a deliberate limit with a measurable cost. Of the pointer bindings in shipping consumers:
|
|
62
|
-
|
|
63
|
-
| `down`/`up` | `tap` | `drag`/`dragStart`/`dragEnd` | `move` | `wheel` | `pinch`/`pinchStart` |
|
|
64
|
-
|---|---|---|---|---|---|
|
|
65
|
-
| 6 | 4 | 4 | 2 | 2 | 2 |
|
|
66
|
-
|
|
67
|
-
Only the first column is expressible. **Consumers bound to gestures stay on `InputController`**, which is
|
|
68
|
-
therefore *reduced*, not retired, by this program (§14). Chief among them:
|
|
69
|
-
`StrategyInteractionController` (17 bindings, including both pinch bindings),
|
|
70
|
-
`makeOrbitalCameraController` (tap + down + drag + wheel), and `RadialContextMenu`.
|
|
71
|
-
|
|
72
|
-
### 1.2 What is replaced
|
|
73
|
-
|
|
74
|
-
| Mechanism | Disposition | Why |
|
|
75
|
-
|---|---|---|
|
|
76
|
-
| [`Input`](../components/Input.js) + [`InputSystem`](../systems/InputSystem.js) | **Retired** (one holdout, §14) | Magic string paths; one source per binding; no combinations; `resolvePath` throws; `serializable = false` |
|
|
77
|
-
| [`EditorKeyMap`](../../../../../editor/EditorKeyMap.js) | **Retired** | Exists only because chords are inexpressible above; hard-coded, unrebindable, undiscoverable |
|
|
78
|
-
| `Tool.handleKeyboardEvent` | **Retired** | An unconditional forward to the active tool, with no gating |
|
|
79
|
-
| [`KeyboardCameraController`](../controllers/KeyboardCameraController.js) | **Retired** | 16 `bind()` calls maintaining a `controls` struct — a 2-D coordinate written by hand (§3.3) |
|
|
80
|
-
| [`InputController`](../components/InputController.js) + [`InputControllerSystem`](../systems/InputControllerSystem.js) | **Reduced** | Keyboard and button bindings move; gesture bindings stay (§1.1) |
|
|
81
|
-
| Direct `engine.devices.*.on.*.add(...)` in gameplay code | **Case by case** | Not bindable, not rebindable, not occludable; but gesture subscriptions have nowhere to go yet |
|
|
82
|
-
|
|
83
|
-
**Not in scope at all:** direct device reads in `prototype*.js` / `test*.js` harnesses. Those are internal
|
|
84
|
-
tools, and polling `pad.buttons[GamepadButtons.a].is_down` remains the blessed path for anything that does not
|
|
85
|
-
want a binding layer. The ISM sits *above* the devices; it does not close them off.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## 2. Evaluation: eager on edges, ticked only for time
|
|
90
|
-
|
|
91
|
-
There is **no input buffer and no added frame of latency.** An edge is evaluated and dispatched inside the
|
|
92
|
-
device signal that produced it.
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
InputDeviceSwitch.down / .up (keyboard/pointer: DOM event. gamepad: poll.)
|
|
96
|
-
|
|
|
97
|
-
v
|
|
98
|
-
ISM edge handler --- synchronous, no queue ---------------------------------
|
|
99
|
-
|
|
|
100
|
-
| 1. stamp arrival time
|
|
101
|
-
| 2. update #held : BitSet
|
|
102
|
-
| 3. append to the recorder, if one is attached (§2.4)
|
|
103
|
-
| 4. walk maps by `order` descending, skipping suppressed maps (§8):
|
|
104
|
-
| for each candidate trigger touching this switch,
|
|
105
|
-
| in specificity order (§4.6):
|
|
106
|
-
| was := trigger.satisfied
|
|
107
|
-
| now := trigger.test(#held)
|
|
108
|
-
| trigger.satisfied := now
|
|
109
|
-
| if (now && !was) consume, dispatch
|
|
110
|
-
| 5. apply changes deferred during dispatch (§2.3)
|
|
111
|
-
v
|
|
112
|
-
dataset.sendEvent(entity, action, event) -- consumer runs here, this instant
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`#held` is exact at dispatch time by construction, because dispatch *is* the edge. No replay, no
|
|
116
|
-
reconstruction.
|
|
117
|
-
|
|
118
|
-
### 2.1 The tick services time, and only time
|
|
119
|
-
|
|
120
|
-
`System.update(dt)` dispatches no edge-driven action. It services the four things that change without an edge:
|
|
121
|
-
|
|
122
|
-
1. **`InputTriggerHold` maturity.** A hold for 0.5 s becomes true 0.5 s after its child did, which no edge
|
|
123
|
-
announces. When it matures it emits a **synthetic edge** into the same walk as step 4 above, carrying the
|
|
124
|
-
hold's own `collectSources()` set so consumption (§8) has something to match against. Latency is bounded by
|
|
125
|
-
the frame, which is inherent: nothing observes time more finely than the loop observing time.
|
|
126
|
-
2. **Sequence step windows** (§5.2) — both the refresh while a previous step still holds, and the expiry.
|
|
127
|
-
3. **`Delta` coordinate reset** (§3.3).
|
|
128
|
-
4. **Suppression recompute** when the linked map set, `order` or `occlusion` changed during the frame (§8).
|
|
129
|
-
|
|
130
|
-
A trigger built only from switches and chords is untouched by `update`.
|
|
131
|
-
|
|
132
|
-
### 2.2 Polled devices are bounded by their own poll rate
|
|
133
|
-
|
|
134
|
-
`GamepadDevice` polls once per animation frame and drives its switches from the result
|
|
135
|
-
(`GamepadDevice.js:370-407`), so gamepad edges cannot be finer-grained than that — a Gamepad API property, not
|
|
136
|
-
one this design imposes. `GamepadDevice.poll()` is callable manually, so a consumer driving a fixed-rate loop
|
|
137
|
-
can raise its own resolution. Keyboard and pointer edges are DOM-driven and carry no such floor.
|
|
138
|
-
|
|
139
|
-
### 2.3 The re-entrancy contract
|
|
140
|
-
|
|
141
|
-
Consumer code runs inside a DOM event handler. Four cases, each with a stated rule:
|
|
142
|
-
|
|
143
|
-
* **Structural change** — a handler adds or removes an entity, and therefore an `InputMap`. Queued, applied at
|
|
144
|
-
step 5, after the current edge finishes.
|
|
145
|
-
* **Binding edits on a linked map** — a handler calls `bind()` / `pushLayer()`, which §9.3 says triggers a
|
|
146
|
-
recompile. The recompile is queued with structural changes and applied at step 5; the walk always runs to
|
|
147
|
-
completion against the program it started with. This path is not exotic: it is the rebinding-capture flow.
|
|
148
|
-
* **Nested edges** — a handler calls `GamepadDevice.poll()` (blessed in §2.2) or synchronously dispatches a DOM
|
|
149
|
-
event (`element.click()`, `.focus()`), re-entering the walk. **Rejected**: a re-entrant edge is queued and
|
|
150
|
-
replayed at step 5 rather than processed inline, because processing it inline would mutate `#held` under the
|
|
151
|
-
outer walk and clobber the shared `InputActionEvent` (§6.3) before the outer handler finished reading it.
|
|
152
|
-
* **A handler that throws** — it throws. `#held` and the recorder are updated *before* dispatch (steps 2–3), so
|
|
153
|
-
ISM state stays consistent and the failure is visible, per the codebase's position on not swallowing errors.
|
|
154
|
-
|
|
155
|
-
*(For contrast, `InputControllerSystem` carries a `deferred` array for the first case that **nothing ever
|
|
156
|
-
populates** — `add()` registers directly under a `//TODO handle case where binding is added mid-dispatch`
|
|
157
|
-
(`InputControllerSystem.js:119-121`). The hazard is real and currently unhandled, not handled badly.)*
|
|
158
|
-
|
|
159
|
-
### 2.4 Ordering, and what it costs
|
|
160
|
-
|
|
161
|
-
Action effects land wherever in the frame the edge arrived, so two actions in one frame are ordered by
|
|
162
|
-
arrival, not by system order. That is the direct consequence of wanting zero latency, and §4.6 and §5.5 record
|
|
163
|
-
the two places it is observable.
|
|
164
|
-
|
|
165
|
-
### 2.5 Recording and replay
|
|
166
|
-
|
|
167
|
-
An optional recorder captures, per frame: the frame's `dt`, every switch edge as
|
|
168
|
-
`(source_index, is_down, time)`, and a sample of every **coordinate channel** any linked map references.
|
|
169
|
-
Coordinates must be recorded because they are read by polling, not driven by edges — without them,
|
|
170
|
-
`InputActionEvent.value` and every `axis*` read diverge on replay. `dt` must be recorded because hold maturity
|
|
171
|
-
and window expiry are evaluated on the tick, so a different frame cadence produces different fire times.
|
|
172
|
-
|
|
173
|
-
Replay injects recorded edges at their recorded positions within each recorded frame and drives the same eager
|
|
174
|
-
path. Interned `source_index` values are stable only within one link generation, so the recorder writes the
|
|
175
|
-
source table alongside and remaps on load.
|
|
176
|
-
|
|
177
|
-
The recorder is an observer, not the mechanism — attach it for a determinism run or a bug report, leave it off
|
|
178
|
-
otherwise.
|
|
179
|
-
|
|
180
|
-
### 2.6 `preventDefault`, and its granularity
|
|
181
|
-
|
|
182
|
-
[`KeyboardDevice`](../../devices/KeyboardDevice.js) calls `event.preventDefault()` when the pressed key's
|
|
183
|
-
`down` signal *has handlers* (`KeyboardDevice.js:112-118`). Since the ISM subscribes to precisely the keys some
|
|
184
|
-
live binding references, browser-default suppression tracks the bound key set automatically.
|
|
185
|
-
|
|
186
|
-
**Granularity is per key, not per chord**, and this is worth knowing before it surprises someone: binding only
|
|
187
|
-
`Ctrl+S` subscribes `s`'s `down` signal, so a *bare* `S` keydown is also `preventDefault`ed anywhere the device
|
|
188
|
-
element sees it. This is inherited behaviour, not new — `InputController` binding `keyboard/keys/s/down` does
|
|
189
|
-
the same — but chords make it newly reachable. Changing it means changing `KeyboardDevice`, which is out of
|
|
190
|
-
scope here.
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## 3. Devices, switches and coordinates
|
|
195
|
-
|
|
196
|
-
### 3.1 The adapter
|
|
197
|
-
|
|
198
|
-
```js
|
|
199
|
-
/**
|
|
200
|
-
* Adapts a concrete input device to the ISM.
|
|
201
|
-
*
|
|
202
|
-
* Resolution must be TOTAL: an adapter answers for every code it declares, whether or not hardware is
|
|
203
|
-
* present. Absent hardware reads as "up" and as zero, never as undefined, and never throws. This is the
|
|
204
|
-
* property `resolvePath` lacked.
|
|
205
|
-
*/
|
|
206
|
-
export class InputDeviceAdapter {
|
|
207
|
-
/** Stable identifier, written into serialized bindings. ASCII, lower_snake_case. @type {string} */
|
|
208
|
-
id = "";
|
|
209
|
-
|
|
210
|
-
/** @param {number} code @returns {InputDeviceSwitch} */
|
|
211
|
-
getSwitch(code) { }
|
|
212
|
-
|
|
213
|
-
/** @param {number[]|Float64Array} result @param {number} offset @param {number} code */
|
|
214
|
-
readCoordinate(result, offset, code) { }
|
|
215
|
-
|
|
216
|
-
/** Begin/stop observing the device. Adapters with nothing to subscribe to leave these alone. */
|
|
217
|
-
startup() { }
|
|
218
|
-
shutdown() { }
|
|
219
|
-
|
|
220
|
-
/** Publish this frame's relative-channel totals and start a new window. See the latch rule in §3.3. */
|
|
221
|
-
latchDeltas() { }
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
A channel's *dimensionality* is not asked of the adapter. It is a static property of the channel code, known
|
|
226
|
-
to the typed constructors (§3.3), so a binding on an unregistered device still knows its own shape.
|
|
227
|
-
|
|
228
|
-
| Adapter | `id` | Switch codes | Coordinate codes |
|
|
229
|
-
|---|---|---|---|
|
|
230
|
-
| `KeyboardInputDeviceAdapter` | `"keyboard"` | [`KeyCodes`](../../devices/KeyCodes.js) | — |
|
|
231
|
-
| `PointerInputDeviceAdapter` | `"pointer"` | `MouseButtons` | `PointerCoordinates` |
|
|
232
|
-
| `GamepadInputDeviceAdapter` | `"gamepad"` | [`GamepadButtons`](../../devices/gamepad/GamepadButtons.js) | `GamepadCoordinates` |
|
|
233
|
-
|
|
234
|
-
Three enums are new, and each needs a stability note because their values become `uint16` codes on disk:
|
|
235
|
-
|
|
236
|
-
* **`MouseButtons`** — `left: 0, middle: 1, right: 2, back: 3, forward: 4`. These are
|
|
237
|
-
**`MouseEvent.button` ordinals**, matching how `PointerDevice.buttons[]` is indexed
|
|
238
|
-
(`PointerDevice.js:300-318`: `mouseButtonLeft` is `[0]`, `mouseButtonMiddle` is `[1]`, `mouseButtonRight` is
|
|
239
|
-
`[2]`). They are deliberately **not** the `MouseEvent.buttons` *bitmask* order, which is left/right/middle —
|
|
240
|
-
taking that order would index `buttons[1]` for a right-click and fire every right-click binding on the
|
|
241
|
-
middle button, into saved data.
|
|
242
|
-
* **`PointerCoordinates`** — `position`, `move`, `wheel`.
|
|
243
|
-
* **`GamepadCoordinates`** — `left_stick`, `right_stick`, `dpad`.
|
|
244
|
-
|
|
245
|
-
Values are append-only once shipped, like `KeyCodes` and `GamepadButtons` before them.
|
|
246
|
-
|
|
247
|
-
A fourth device — MIDI pedal, wheel, eye tracker, a network-driven replay source — is an adapter and a
|
|
248
|
-
registration call. Nothing in the trigger, layer, cascade or serialization layers changes, because none of them
|
|
249
|
-
can tell devices apart.
|
|
250
|
-
|
|
251
|
-
The gamepad adapter binds `GamepadDevice.main`, the persistent handle: it exists before any hardware does,
|
|
252
|
-
survives disconnection, and rebinds to the oldest connected pad on a swap (`GamepadDevice.js:110, 306-344`). A
|
|
253
|
-
serialized gamepad binding therefore resolves at load time and keeps working across a hot-swap — the defect
|
|
254
|
-
`GAMEPAD_API_PROPOSAL.md §3` identified and could not fix from the device side alone.
|
|
255
|
-
|
|
256
|
-
**A device that is not registered** is not an error. A map naming an unregistered `id` links successfully and
|
|
257
|
-
its bindings on that device are inert — the same contract as connected-but-absent hardware, so a saved map
|
|
258
|
-
never fails to load because a peripheral is missing. `registerDevice()` may be called after maps are linked; it
|
|
259
|
-
recompiles every linked map (§9.3), at which point those bindings become live.
|
|
260
|
-
|
|
261
|
-
### 3.2 Switch sources
|
|
262
|
-
|
|
263
|
-
```js
|
|
264
|
-
export class InputSource {
|
|
265
|
-
device = ""; // InputDeviceAdapter#id
|
|
266
|
-
code = 0;
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
One flat value type with `equals`/`hash`/`toBinaryBuffer`/`fromBinaryBuffer`, deliberately *not* a
|
|
271
|
-
`KeySource`/`GamepadButtonSource`/… hierarchy: a chord over `{keyboard:ctrl, pointer:0}` needs no polymorphic
|
|
272
|
-
dispatch, serialization is `(device_index: Uint8, code: Uint16)` against a per-map device table (§10) with no
|
|
273
|
-
type tags, and interning to an integer (§9.3) is a two-field hash after which the hot path never touches the
|
|
274
|
-
object again. The typing that matters to an author lives on the *trigger* constructors (§4.2).
|
|
275
|
-
|
|
276
|
-
### 3.3 Coordinates — analog channels of arbitrary dimensionality
|
|
277
|
-
|
|
278
|
-
An analog channel is not a scalar axis. `WheelEvent` carries `deltaX`, `deltaY` and `deltaZ`, and
|
|
279
|
-
[`PointerDevice`](../../devices/PointerDevice.js) surfaces all three (`PointerDevice.js:398-404`). A 1-D axis
|
|
280
|
-
abstraction would have to either drop `deltaZ` or invent three channels for one control.
|
|
281
|
-
|
|
282
|
-
*(Precisely: `PointerDevice` reports `sign(event.deltaX/Y/Z)`, so components are -1, 0 or 1 and scroll
|
|
283
|
-
magnitude is discarded at the device. A `Delta` wheel channel therefore accumulates **tick counts**, not
|
|
284
|
-
distance. That is the device's existing behaviour; the ISM reports what it is given.)*
|
|
285
|
-
|
|
286
|
-
```js
|
|
287
|
-
export class InputCoordinateSource {
|
|
288
|
-
device = "";
|
|
289
|
-
code = 0;
|
|
290
|
-
/** Component count. 2 for a stick or pointer position, 3 for a wheel. @type {number} */
|
|
291
|
-
dimensions = 1;
|
|
292
|
-
/** @type {InputCoordinateKind} */
|
|
293
|
-
kind = InputCoordinateKind.Normalized;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export const InputCoordinateKind = {
|
|
297
|
-
/** A position in some space. Read at any time, no accumulation. Pointer position. */
|
|
298
|
-
Absolute: 0,
|
|
299
|
-
/** A change. Accumulates within a frame, zeroed at the frame boundary. Wheel. */
|
|
300
|
-
Delta: 1,
|
|
301
|
-
/** Deflection, each component in [-1, 1]. Stick, d-pad. */
|
|
302
|
-
Normalized: 2
|
|
303
|
-
};
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
`kind` is the verb — what the numbers *mean*. `Delta` is the one with behaviour attached, and it is why the
|
|
307
|
-
tick exists at all: two wheel events in one frame must sum.
|
|
308
|
-
|
|
309
|
-
**The `Delta` visibility rule, stated exactly.** A relative channel **latches**; it is not cleared. The
|
|
310
|
-
adapter accumulates arriving events into one buffer, and once per frame `update` publishes that buffer into a
|
|
311
|
-
second one and starts a fresh accumulation window. Readers see the published value.
|
|
312
|
-
|
|
313
|
-
Latching rather than clearing is load-bearing, and the obvious alternative is wrong. Device events arrive
|
|
314
|
-
*between* frames, so an adapter that zeroed its accumulator at the top of a frame would destroy exactly the
|
|
315
|
-
events that frame exists to report. Latching also makes the value **independent of system execution order**:
|
|
316
|
-
every reader in a frame sees the same number whether it runs before or after the ISM, which is what the
|
|
317
|
-
"same value for every reader" guarantee actually requires.
|
|
318
|
-
|
|
319
|
-
An **eager handler** reading a `Delta` channel mid-frame sees the value published for the frame in progress —
|
|
320
|
-
the same one the tick-time readers will see — not a running total of events that have arrived since. Those
|
|
321
|
-
belong to the next frame.
|
|
322
|
-
|
|
323
|
-
**Reading.** The general accessor writes into an indexable numeric buffer; common dimensionalities get typed
|
|
324
|
-
conveniences over the engine's vector types:
|
|
325
|
-
|
|
326
|
-
```js
|
|
327
|
-
map.coordinate(result, offset, 'look'); // Float64Array | number[] - any dimensionality
|
|
328
|
-
map.axis1(v1, 'throttle'); // Vector1
|
|
329
|
-
map.axis2(v2, 'move'); // Vector2
|
|
330
|
-
map.axis3(v3, 'wheel'); // Vector3
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Result-first argument order follows the engine's `f(result, ...)` convention.
|
|
334
|
-
|
|
335
|
-
**Channels.** A coordinate action is driven by a channel, and the consumer cannot tell which kind:
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
InputCoordinateGamepad.from(GamepadCoordinates.left_stick) // hardware channel
|
|
339
|
-
InputCoordinatePointer.from(PointerCoordinates.wheel) // hardware channel, 3-D
|
|
340
|
-
InputCoordinateSwitches.from([ // N-D from 2N switch triggers
|
|
341
|
-
InputTriggerKey.from('a'), InputTriggerKey.from('d'), // x: negative, positive
|
|
342
|
-
InputTriggerKey.from('w'), InputTriggerKey.from('s') // y: negative, positive
|
|
343
|
-
])
|
|
344
|
-
InputCoordinateCompose.from(x_channel, y_channel) // stitch channels
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
`InputCoordinateSwitches` is [`KeyboardCameraController`](../controllers/KeyboardCameraController.js) as data,
|
|
348
|
-
and it generalises past 2-D for free.
|
|
349
|
-
|
|
350
|
-
**Shaping stays at the call site.** No dead zone, no response curve, no sensitivity inside the ISM. It returns
|
|
351
|
-
the composed raw value and the consumer calls
|
|
352
|
-
[`apply_radial_dead_zone`](../../analog/apply_radial_dead_zone.js). This is the position
|
|
353
|
-
`GAMEPAD_API_PROPOSAL.md §1.2` argues at length; reversing it here would put the same policy back in the same
|
|
354
|
-
wrong layer, one floor up.
|
|
355
|
-
|
|
356
|
-
### 3.4 Derived switches: a direction is a coordinate with a threshold
|
|
357
|
-
|
|
358
|
-
`InputTriggerDirection` turns a coordinate into a switch — "the stick is in the *down-right* octant". This is
|
|
359
|
-
what makes motion inputs work (§5.4), and it is not bolted on for them: a d-pad is already a coordinate in this
|
|
360
|
-
codebase (`gamepad_write_dpad_vector`), so one path serves stick and d-pad.
|
|
361
|
-
|
|
362
|
-
The eight directional switches derived from one coordinate are **mutually exclusive by construction**, and the
|
|
363
|
-
derived layer **releases the old direction before pressing the new one**, so exclusivity is never momentarily
|
|
364
|
-
violated. §5.4 depends on both.
|
|
365
|
-
|
|
366
|
-
Three properties an implementer must know, because none is free:
|
|
367
|
-
|
|
368
|
-
* **Threshold.** Producing a boolean from a coordinate requires one by definition. This is not an exception to
|
|
369
|
-
§3.3, which forbids shaping the *analog value* handed to a consumer. Constructor argument, defaulted.
|
|
370
|
-
* **Sampling.** Octant transitions are observed only as often as the channel updates. On a gamepad that is the
|
|
371
|
-
rAF poll (§2.2), so a fast quarter-circle can pass from `Down` to `Right` between two polls without ever
|
|
372
|
-
reporting `DownRight`, and a motion requiring that step silently fails. Sequences over directions are
|
|
373
|
-
reliable at hand speed, not at frame-perfect speed.
|
|
374
|
-
* **Hysteresis.** A stick resting on an octant boundary chatters release/press pairs. The threshold carries an
|
|
375
|
-
angular hysteresis band, defaulted, for the same reason it carries a magnitude threshold: without it a
|
|
376
|
-
boundary rest position resets hold timers continuously.
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
## 4. Triggers
|
|
381
|
-
|
|
382
|
-
### 4.1 One interface, two methods and one field
|
|
383
|
-
|
|
384
|
-
```js
|
|
385
|
-
export class InputTrigger {
|
|
386
|
-
/**
|
|
387
|
-
* Is this trigger's condition currently met?
|
|
388
|
-
* Pure for leaves and combinators. `InputTriggerHold` and `InputTriggerSequence` additionally consult
|
|
389
|
-
* per-instance state, which edges and ticks maintain.
|
|
390
|
-
*
|
|
391
|
-
* @param {BitSet} held interned switch indices currently down
|
|
392
|
-
* @returns {boolean}
|
|
393
|
-
*/
|
|
394
|
-
test(held) { }
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Every switch index this trigger can be affected by. Collected once, at compile time (§9.3).
|
|
398
|
-
* @param {number[]} result
|
|
399
|
-
*/
|
|
400
|
-
collectSources(result) { }
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Last observed value of `test`, per compiled instance. A binding fires when `test` is true and this
|
|
404
|
-
* is false. Initialised at compile time to `test(held-at-compile)` - NOT to false. See §4.5.
|
|
405
|
-
* @type {boolean}
|
|
406
|
-
*/
|
|
407
|
-
satisfied = false;
|
|
408
|
-
}
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
**A binding fires on the rising edge of `test`.** That single rule covers press, release, hold, chord, sequence
|
|
412
|
-
and direction, and it is why there is no `Started`/`Performed`/`Canceled` phase enum and no `edge` field.
|
|
413
|
-
|
|
414
|
-
### 4.2 Leaf triggers — typed constructors
|
|
415
|
-
|
|
416
|
-
```js
|
|
417
|
-
InputTriggerKey.from('space')
|
|
418
|
-
InputTriggerKey.from(KeyCodes.space)
|
|
419
|
-
InputTriggerMouseButton.from(MouseButtons.left)
|
|
420
|
-
InputTriggerGamepadButton.from(GamepadButtons.a)
|
|
421
|
-
InputTriggerDirection.from(stick, InputDirection.DownRight) // stick: any InputCoordinate* channel
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
Each extends `InputTriggerSwitch`, which holds one `InputSource` and whose `test` is `held.get(index)`. The
|
|
425
|
-
subclasses add no fields — they are typed, validating constructors that fix the device and check the code
|
|
426
|
-
against that device's enum. `InputTriggerKey.from('nonexistent')` fails at authoring time under `assert.*`,
|
|
427
|
-
which is compiled out of production, so the typing is free in shipped code.
|
|
428
|
-
|
|
429
|
-
Serialization round-trips the flat form, so a deserialized trigger is an `InputTriggerSwitch`, not an
|
|
430
|
-
`InputTriggerKey`. Nothing needs the subclass identity at runtime — it exists for the author and the assertion.
|
|
431
|
-
|
|
432
|
-
### 4.3 Combinators
|
|
433
|
-
|
|
434
|
-
```js
|
|
435
|
-
InputTriggerChord.from(a, b, c) // test = AND over children
|
|
436
|
-
InputTriggerRelease.from(a) // test = NOT child.test
|
|
437
|
-
InputTriggerHold.from(a, 0.5) // test = child.test AND continuously true for >= 0.5s
|
|
438
|
-
InputTriggerSequence.from(a, b, c) // test = the matcher completed on this edge (§5)
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
Combinators take *triggers*, not sources, which is why the requirements compose:
|
|
442
|
-
|
|
443
|
-
```js
|
|
444
|
-
// "the chord ended"
|
|
445
|
-
InputTriggerRelease.from(InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
446
|
-
InputTriggerKey.from('z')))
|
|
447
|
-
|
|
448
|
-
// "hold Ctrl+Shift for half a second"
|
|
449
|
-
InputTriggerHold.from(InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
450
|
-
InputTriggerKey.from('shift')), 0.5)
|
|
451
|
-
|
|
452
|
-
// cross-device: nothing here knows a device is involved
|
|
453
|
-
InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
454
|
-
InputTriggerMouseButton.from(MouseButtons.left))
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
`InputTriggerRelease` as *negation* rather than an edge kind is what makes it compose — an edge flag on a chord
|
|
458
|
-
could not express "when this chord stops holding".
|
|
459
|
-
|
|
460
|
-
**One composition that does not mean what it looks like:** `InputTriggerRelease.from(sequence)`. A sequence's
|
|
461
|
-
`test` is a single-edge pulse (§5.2 step 5), so negating it yields "true except on the completing edge", which
|
|
462
|
-
rises on the *next* evaluated edge after every completion — an echo of the fire, not "the sequence stopped
|
|
463
|
-
matching". A sequence's partial-match state is deliberately not exposed to `test`. Do not wrap a sequence in
|
|
464
|
-
`Release`.
|
|
465
|
-
|
|
466
|
-
### 4.4 Step timeout lives on the sequence
|
|
467
|
-
|
|
468
|
-
```js
|
|
469
|
-
InputTriggerSequence.from(a, b, c).withStepTimeout(0.25)
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
Default **1.0 s**, which suits editor and menu sequences whose first step is a chord the author must release
|
|
473
|
-
before reaching for the next key. Fighting-game motions want 0.15–0.25 s. §5.2 defines exactly what the window
|
|
474
|
-
measures, which is not what a naive reading suggests.
|
|
475
|
-
|
|
476
|
-
Timing was specified here as **real time**, stamped at edge arrival, on the argument that a paused game must
|
|
477
|
-
not give a chord an infinite window and a game at 0.25× speed must not quarter the player's reflexes.
|
|
478
|
-
|
|
479
|
-
**Superseded.** As built it is *simulation* time, advanced by `update(dt)` and nowhere else. The reason is
|
|
480
|
-
testability: a real-time clock cannot be driven from a spec without faking one, and faking it is a mock. The
|
|
481
|
-
consequences are real and accepted — windows stretch under slow motion and freeze while the game is paused,
|
|
482
|
-
so a hold inside a pause menu can never mature. See the implementation plan's amendments section for the full
|
|
483
|
-
argument.
|
|
484
|
-
|
|
485
|
-
### 4.5 `satisfied` is initialised from state, not from false
|
|
486
|
-
|
|
487
|
-
At compile time each trigger instance sets `satisfied := test(held)` against the currently held switches. It is
|
|
488
|
-
**not** zero-initialised, and the difference is not academic:
|
|
489
|
-
`InputTriggerRelease.from(InputTriggerChord.from(ctrl, z))` has `test` true whenever the chord is *not* held —
|
|
490
|
-
which is almost always. Zero-initialised, the first edge touching `ctrl` or `z` would see `now=true, was=false`
|
|
491
|
-
and fire "the chord ended" before the chord ever existed.
|
|
492
|
-
|
|
493
|
-
This matters on every compile, not just the first: maps link mid-dispatch (§2.3), and `registerDevice` and
|
|
494
|
-
binding edits both recompile live maps while keys are held.
|
|
495
|
-
|
|
496
|
-
### 4.6 Precedence within a layer
|
|
497
|
-
|
|
498
|
-
Candidate triggers on one edge are ordered, and the order is total so it never depends on declaration accident:
|
|
499
|
-
|
|
500
|
-
1. **Sequences first**, longer before shorter (by step count).
|
|
501
|
-
2. Then by **chord width** — the number of switches that must be simultaneously held. `Hold` and `Release`
|
|
502
|
-
rank by their child's width, so `Hold(Chord(a,b), t)` ranks as width 2 and beats `Key(a)`; `Direction` ranks
|
|
503
|
-
as width 1.
|
|
504
|
-
3. Then declaration order within the layer.
|
|
505
|
-
|
|
506
|
-
A fired trigger consumes its switches for the remainder of the current edge (§8), so `Ctrl+Z` firing means `Z`
|
|
507
|
-
does not.
|
|
508
|
-
|
|
509
|
-
**The condition on that guarantee.** Consumption is scoped to one edge, so it arbitrates only triggers that
|
|
510
|
-
complete on the *same* edge. `Ctrl` pressed before `Z` gives one edge (`z↓`) at which both `Ctrl+Z` and `Z` are
|
|
511
|
-
satisfiable, and the chord wins. `Z` pressed **before** `Ctrl` gives two edges: `z↓` fires `Z`'s action (the
|
|
512
|
-
chord is not yet satisfiable, so there is nothing to lose to), then `ctrl↓` fires `undo`. **Both fire.**
|
|
513
|
-
|
|
514
|
-
This is inherent to zero-latency evaluation — suppressing the first would mean either delaying it or retracting
|
|
515
|
-
a dispatched action, and both are excluded. Practically it is rarely reached, because modifier-first is how
|
|
516
|
-
people type chords. Where it matters, do not bind a bare key in the same layer as a chord that contains it.
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
## 5. Sequence semantics
|
|
521
|
-
|
|
522
|
-
### 5.1 State
|
|
523
|
-
|
|
524
|
-
Per sequence binding instance, three numbers, held on the **compiled clone** of the trigger (§9.3):
|
|
525
|
-
|
|
526
|
-
```js
|
|
527
|
-
#step // steps matched so far, 0..N
|
|
528
|
-
#stepSatisfied // last observed value of steps[#step].test
|
|
529
|
-
#window // time the current step's window last refreshed
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
*(An earlier revision put these in typed arrays on the program, indexed by sequence-binding index. Cloning per
|
|
533
|
-
binding is what the compiler does anyway — authored triggers are shared, so evaluation state cannot live on
|
|
534
|
-
them — and once each binding owns a private trigger object, three fields on it are the same thing with less
|
|
535
|
-
bookkeeping. Nothing allocates per evaluation either way.)*
|
|
536
|
-
|
|
537
|
-
Entering a state is one operation, and **every** path into a state uses it. That is the whole of §17's most
|
|
538
|
-
important correction:
|
|
539
|
-
|
|
540
|
-
```js
|
|
541
|
-
function enter(k, held, now) {
|
|
542
|
-
step = k;
|
|
543
|
-
satisfied = (k < N) ? steps[k].test(held) : false; // the entry snapshot
|
|
544
|
-
window = now;
|
|
545
|
-
}
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### 5.2 The rule
|
|
549
|
-
|
|
550
|
-
Evaluated at each edge (device or synthetic) and each tick:
|
|
551
|
-
|
|
552
|
-
```
|
|
553
|
-
1. if (step > 0) {
|
|
554
|
-
if (steps[step - 1].test(held)) window := now // previous step still holds
|
|
555
|
-
else if (now - window > step_timeout) enter(0, held, now) // window expired
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
2. cur := steps[step].test(held)
|
|
559
|
-
|
|
560
|
-
3. if (cur && !satisfied) {
|
|
561
|
-
enter(step + 1, held, now)
|
|
562
|
-
if (step === N) {
|
|
563
|
-
fire() // the sequence's own `test` reads true for this edge
|
|
564
|
-
enter(0, held, now)
|
|
565
|
-
}
|
|
566
|
-
} else {
|
|
567
|
-
satisfied := cur
|
|
568
|
-
}
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
Three properties do all the work:
|
|
572
|
-
|
|
573
|
-
* **Advance on a rising edge, not on satisfaction.** Otherwise one long press of `A` satisfies both steps
|
|
574
|
-
of `[A, A]`.
|
|
575
|
-
* **The entry snapshot, on every entry.** Advance, expiry and post-fire wrap all route through `enter`, so a
|
|
576
|
-
reset can never leave a stale `satisfied` behind. `enter` never indexes `steps[N]`.
|
|
577
|
-
* **The window measures time since the previous step stopped holding**, not time since the advance. Step 1
|
|
578
|
-
refreshes `window` for as long as `steps[step-1]` is still satisfied, which is what makes charge inputs work
|
|
579
|
-
(§5.4) and is why the tick must service sequences even when no edge arrives.
|
|
580
|
-
|
|
581
|
-
**A mismatch does not reset. Only window expiry resets.** A sequence is a leniency window, and the window is
|
|
582
|
-
what §5.2 step 1 defines.
|
|
583
|
-
|
|
584
|
-
### 5.3 Worked: `[A, A, B]` on `A↓ A↑ A↓ A↑ A↓ B↓`
|
|
585
|
-
|
|
586
|
-
| edge | held | step | step-1 holds? | `cur` | `satisfied` | result |
|
|
587
|
-
|---|---|---|---|---|---|---|
|
|
588
|
-
| — | {} | 0 | — | — | false (compile snapshot) | |
|
|
589
|
-
| `A↓` | {A} | 0 | — | true | false | **rise** → `enter(1)`, `satisfied := A.test({A}) = true` |
|
|
590
|
-
| `A↑` | {} | 1 | no | false | true | `satisfied := false` |
|
|
591
|
-
| `A↓` | {A} | 1 | yes → refresh | true | false | **rise** → `enter(2)`, `satisfied := B.test({A}) = false` |
|
|
592
|
-
| `A↑` | {} | 2 | no | false | false | — |
|
|
593
|
-
| `A↓` | {A} | 2 | yes → refresh | false | false | — |
|
|
594
|
-
| `B↓` | {A,B} | 2 | yes → refresh | true | false | **rise** → `enter(3)` = N → **fire**, `enter(0)` |
|
|
595
|
-
|
|
596
|
-
No failure function, no KMP: the entry snapshot alone prevents one press from satisfying two steps.
|
|
597
|
-
|
|
598
|
-
**Post-fire, the case revision 2 got wrong.** `enter(0, {A,B}, now)` snapshots `satisfied := A.test({A,B}) =
|
|
599
|
-
true`. So with `A` still held, tapping `B` again gives `cur = true, satisfied = true` — no rise, no fire.
|
|
600
|
-
Revision 2 left `satisfied` untouched on the post-fire reset, and holding `A` while mashing `B` machine-gunned
|
|
601
|
-
the action off a single `A` press.
|
|
602
|
-
|
|
603
|
-
### 5.4 Worked: chords, motions and charge
|
|
604
|
-
|
|
605
|
-
**`Ctrl+Shift+A > B`** — steps are triggers, and a chord is a trigger, so this is not a special case:
|
|
606
|
-
|
|
607
|
-
```js
|
|
608
|
-
map.bind('promote', InputTriggerSequence.from(
|
|
609
|
-
InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
610
|
-
InputTriggerKey.from('shift'),
|
|
611
|
-
InputTriggerKey.from('a')),
|
|
612
|
-
InputTriggerKey.from('b')
|
|
613
|
-
));
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
`ctrl↓`, `shift↓`: chord false. `a↓`: chord rises → `enter(1)`, `satisfied := b.test({ctrl,shift,a}) = false`.
|
|
617
|
-
Then `b↓` fires — **whether or not the modifiers were released first**, because leaf and chord tests are
|
|
618
|
-
*subset* tests over the held set, and because holding the chord keeps refreshing the window rather than
|
|
619
|
-
starting its clock.
|
|
620
|
-
|
|
621
|
-
**Quarter-circle forward, `↓ ↘ →`** — this is the case that only works because directions are derived from a
|
|
622
|
-
coordinate (§3.4) rather than read off raw d-pad buttons:
|
|
623
|
-
|
|
624
|
-
```js
|
|
625
|
-
const stick = InputCoordinateGamepad.from(GamepadCoordinates.left_stick);
|
|
626
|
-
|
|
627
|
-
map.bind('hadouken', InputTriggerSequence.from(
|
|
628
|
-
InputTriggerDirection.from(stick, InputDirection.Down),
|
|
629
|
-
InputTriggerDirection.from(stick, InputDirection.DownRight),
|
|
630
|
-
InputTriggerDirection.from(stick, InputDirection.Right)
|
|
631
|
-
).withStepTimeout(0.2));
|
|
632
|
-
```
|
|
633
|
-
|
|
634
|
-
| edge | held | step | `cur` | result |
|
|
635
|
-
|---|---|---|---|---|
|
|
636
|
-
| `Down↓` | {Down} | 0 | true | **rise** → `enter(1)`, `satisfied := DownRight.test({Down}) = false` |
|
|
637
|
-
| `Down↑` | {} | 1 | false | — |
|
|
638
|
-
| `DownRight↓` | {DownRight} | 1 | true | **rise** → `enter(2)`, `satisfied := Right.test({DownRight}) = false` |
|
|
639
|
-
| `DownRight↑` | {} | 2 | false | — |
|
|
640
|
-
| `Right↓` | {Right} | 2 | true | **rise** → `enter(3)` = N → **fire** |
|
|
641
|
-
|
|
642
|
-
The two snapshots in the result column are the trick. With raw d-pad buttons `↓` and `→` can be held together,
|
|
643
|
-
`Right.test({Down,Right})` is `true` on entry to state 2, and the motion can never complete. Exclusivity by
|
|
644
|
-
construction is what removes that failure mode — which is why §12.2 tests it in both directions.
|
|
645
|
-
|
|
646
|
-
**Dragon punch, `→ ↓ ↘`** — the same trace with steps permuted; each entry snapshot is false for the same
|
|
647
|
-
reason. Verified also against the realistic rolled input `Right, DownRight, Down, DownRight`, where the
|
|
648
|
-
intermediate `DownRight↓` at step 1 is a non-advancing mismatch (ignored, correctly) and the final one fires.
|
|
649
|
-
|
|
650
|
-
**Charge, `hold ← for 2 s, then →`** — the case revision 2 published as passing and which in fact failed:
|
|
651
|
-
|
|
652
|
-
```js
|
|
653
|
-
InputTriggerSequence.from(
|
|
654
|
-
InputTriggerHold.from(InputTriggerDirection.from(stick, InputDirection.Left), 2.0),
|
|
655
|
-
InputTriggerDirection.from(stick, InputDirection.Right)
|
|
656
|
-
)
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
At t=2.0 the hold matures, emits a synthetic edge (§2.1) and rises → `enter(1)`. The player then keeps holding
|
|
660
|
-
`←`, which is what charge play *is*. Under revision 2 the window ran from the advance, so any charge longer
|
|
661
|
-
than `duration + step_timeout` expired before the flick and the move was silently lost; raising the timeout
|
|
662
|
-
instead produced ghost charges minutes later. Under §5.2 step 1, `steps[0].test` — the hold — is still true
|
|
663
|
-
while `←` is held, so `window` is refreshed on every tick. Release `←` at t=8.0 and the window opens *there*;
|
|
664
|
-
`→` within `step_timeout` fires. Return to neutral instead and the window expires normally, so there is no
|
|
665
|
-
ghost.
|
|
666
|
-
|
|
667
|
-
**Motion plus button, `236 + P`** — append `InputTriggerGamepadButton.from(GamepadButtons.x)` as a fourth step.
|
|
668
|
-
While `→` is held, step 1 keeps refreshing the window, so the button may be pressed at leisure; it fires with
|
|
669
|
-
`→` still held because button steps are subset tests.
|
|
670
|
-
|
|
671
|
-
### 5.5 What this deliberately cannot do
|
|
672
|
-
|
|
673
|
-
**Strict rejection.** `↓ ← →` inside the window fires the quarter-circle, because `←` neither advances nor
|
|
674
|
-
resets. Arcade-grade matchers reject a direction outside the expected set. The shape of the fix is known — an
|
|
675
|
-
optional rejection predicate on the sequence, resetting to 0 when it matches — and it is **deferred until a
|
|
676
|
-
consumer asks**, on the position that the common case should be the only case. For every sequence in this
|
|
677
|
-
codebase, and for menu, cheat-code and editor cases, leniency is the wanted behaviour.
|
|
678
|
-
|
|
679
|
-
**Button buffering.** A button pressed *before* the motion step it follows poisons that step's entry snapshot
|
|
680
|
-
and must be re-pressed. Genre-standard buffering is a fighting-game engine feature, not an input-mapping one.
|
|
681
|
-
|
|
682
|
-
**Tap versus hold on the same switch — accepted as a cost of zero latency.** Given
|
|
683
|
-
`'interact' = Key(e)` and `'loot_all' = Hold(Key(e), 0.5)`, a press-and-hold fires **both**: `'interact'` at
|
|
684
|
-
`e↓`, `'loot_all'` 0.5 s later. They complete on different edges, so §4.6's per-edge consumption cannot
|
|
685
|
-
arbitrate them, and no composition inside the algebra separates them either —
|
|
686
|
-
`Chord(Release(e), Release(Hold(e, 0.5)))` collapses, because `hold.test` falls to false on the same `e↑` edge
|
|
687
|
-
the release rises on.
|
|
688
|
-
|
|
689
|
-
Discriminating them requires *withholding* the short action until the hold window has passed, which is
|
|
690
|
-
precisely the frame-buffered dispatch this design exists to avoid. Paying that architectural cost — a second,
|
|
691
|
-
deferred dispatch path with its own ordering and occlusion semantics — for one interaction pattern is not
|
|
692
|
-
worth it. **Tap-and-hold pairs must use distinct inputs** (`E` and `Shift+E`), or accept that the hold is
|
|
693
|
-
additive to the tap. Revision 2 claimed §4.6 arbitrated this; it does not.
|
|
694
|
-
|
|
695
|
-
**Key autorepeat.** `KeyboardDevice` discards `event.repeat` (`KeyboardDevice.js:93-97`) and
|
|
696
|
-
`InputDeviceSwitch.press()` is idempotent, so a held key produces exactly one edge. Autorepeat is not
|
|
697
|
-
observable anywhere in this design, and a consumer that wants repeat behaviour — held-key menu navigation,
|
|
698
|
-
`EditorKeyMap`'s current repeat-undo — must drive it from `isActive` plus its own timer. Changing this means
|
|
699
|
-
changing `KeyboardDevice`, which is out of scope.
|
|
700
|
-
|
|
701
|
-
### 5.6 Why `SimpleStateMachine` is not used
|
|
702
|
-
|
|
703
|
-
The first revision built this on
|
|
704
|
-
[`SimpleStateMachine`](../../../../core/fsm/simple/SimpleStateMachine.js) and
|
|
705
|
-
[`SimpleStateMachineDescription`](../../../../core/fsm/simple/SimpleStateMachineDescription.js). It does not
|
|
706
|
-
earn its place here, and the reasons are specific rather than a matter of taste:
|
|
707
|
-
|
|
708
|
-
* **The graph is a path.** States `0..N` with edges `i → i+1` plus resets to `0`. There is nothing to search,
|
|
709
|
-
so `findPath` — the description's real capability — is dead weight.
|
|
710
|
-
* **The self-transition is not declarable.** "Stay in the current state on a mismatch" is the common case, and
|
|
711
|
-
the edge set never contains `i → i`. It works only because the edge-existence assertion in `advance()` is
|
|
712
|
-
commented out (`SimpleStateMachine.js:238`) — a latent trap if those assertions are ever restored.
|
|
713
|
-
* **Initial state is `-1`** (`SimpleStateMachine.js:16`), so every instance needs an explicit `setState(0)`.
|
|
714
|
-
* **It is not the cheap object it looks like.** Each instance carries a description reference and two handler
|
|
715
|
-
arrays (`SimpleStateMachine.js:22-30`) that this design never registers into — while `satisfied` and `window`
|
|
716
|
-
have to live outside it regardless. Three typed-array slots replace all of it with no per-instance object.
|
|
717
|
-
|
|
718
|
-
The requirement was to make good use of existing meep constructs. The honest reading is that this construct
|
|
719
|
-
does not fit this problem: the matcher is a path, not a graph. **`SimpleStateMachineDescription` becomes the
|
|
720
|
-
right tool the moment sequences branch** — alternate motions sharing a prefix, resolved by longest match —
|
|
721
|
-
and that is the point at which to adopt it, not before.
|
|
722
|
-
|
|
723
|
-
---
|
|
724
|
-
|
|
725
|
-
## 6. Actions — the bindings/behaviour seam
|
|
726
|
-
|
|
727
|
-
### 6.1 The seam
|
|
728
|
-
|
|
729
|
-
```
|
|
730
|
-
DATA (serializable, no code) | CODE (never serialized)
|
|
731
|
-
----------------------------- | ----------------------
|
|
732
|
-
trigger ----> action name | action name ----> behaviour
|
|
733
|
-
Chord(ctrl, z) "undo" | "undo" ----> editor.actions.undo()
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
The left side is an `InputMap` component. The right side is an ECS event listener. They meet at a string, and
|
|
737
|
-
nothing else crosses. This is the same seam [`InputSystem`](../systems/InputSystem.js) already uses
|
|
738
|
-
(`InputSystem.js:79`), which is why that component is the one worth keeping the shape of. It is explicitly
|
|
739
|
-
*not* a string-keyed factory registry: no behaviour is constructed from the string and no class is looked up
|
|
740
|
-
by it.
|
|
741
|
-
|
|
742
|
-
### 6.2 One event, one level, one coordinate
|
|
743
|
-
|
|
744
|
-
```js
|
|
745
|
-
// discrete — fires on the trigger's rising edge, this instant, no frame delay
|
|
746
|
-
ecd.addEntityEventListener(entity, 'undo', () => editor.actions.undo());
|
|
747
|
-
|
|
748
|
-
// continuous — no listener, no mirrored boolean, no teardown handler
|
|
749
|
-
if (map.isActive('sprint')) { ... }
|
|
750
|
-
map.axis2(v, 'move');
|
|
751
|
-
```
|
|
752
|
-
|
|
753
|
-
Three accessors for three *questions* — "did it happen", "is it happening", "how much" — not three ways to ask
|
|
754
|
-
one question. The level accessor is what deletes
|
|
755
|
-
[`KeyboardCameraController`](../controllers/KeyboardCameraController.js)'s `controls` struct, the 16 `bind()`
|
|
756
|
-
calls maintaining it, and the `clearControls` handler wired to `on.unlinked` that exists solely to stop the
|
|
757
|
-
camera drifting after teardown. The same boilerplate is duplicated almost verbatim in
|
|
758
|
-
[`prototype_first_person_controller.js`](../../../control/first-person/prototype_first_person_controller.js).
|
|
759
|
-
|
|
760
|
-
Two things `isActive` is not: it is effectively always `false` for a sequence action, whose `test` is a
|
|
761
|
-
single-edge pulse; and it returns `false` for every action on a **suppressed** map (§8).
|
|
762
|
-
|
|
763
|
-
*(One behaviour `KeyboardCameraController` has that the coordinate does not reproduce: it sends a `'user-input'`
|
|
764
|
-
entity event on every pan keydown (`KeyboardCameraController.js:54`), consumed elsewhere as an interaction
|
|
765
|
-
signal. Porting it is a `map.bind('user-input', ...)` alongside the coordinate, and §14 lists it rather than
|
|
766
|
-
leaving it to be discovered missing.)*
|
|
767
|
-
|
|
768
|
-
### 6.3 The event payload
|
|
769
|
-
|
|
770
|
-
```js
|
|
771
|
-
export class InputActionEvent {
|
|
772
|
-
action = "";
|
|
773
|
-
/** Driving coordinate, or the pointer position, at the moment the trigger fired. @type {Vector3} */
|
|
774
|
-
value = new Vector3();
|
|
775
|
-
/** The switch whose edge completed the trigger. For a synthetic hold-maturity edge, the first
|
|
776
|
-
* switch in the hold's own source set. @type {InputSource} */
|
|
777
|
-
source = new InputSource();
|
|
778
|
-
/** Arrival timestamp of the completing edge, real time, seconds since ISM start. @type {number} */
|
|
779
|
-
time = 0;
|
|
780
|
-
}
|
|
781
|
-
```
|
|
782
|
-
|
|
783
|
-
One instance, reused across dispatches, mutated in place. Dispatch is synchronous, so a handler that reads it
|
|
784
|
-
inline is correct and one that retains it is not — stated in the JSDoc, as scratch vectors are elsewhere in
|
|
785
|
-
the engine. A nested edge cannot clobber it mid-read, because nested edges are queued rather than processed
|
|
786
|
-
inline (§2.3).
|
|
787
|
-
|
|
788
|
-
**It carries no DOM information**, and that is a real limit worth naming: consumers that today gate on
|
|
789
|
-
`event.target === graphics.domElement` to tell a canvas click from a click on overlaying UI —
|
|
790
|
-
`CombatInteractionStateMachine` does — have no equivalent here. UI-overlay occlusion is a DOM-tree question
|
|
791
|
-
that §8's map occlusion cannot see. Those consumers are in the gesture set that stays on `InputController`
|
|
792
|
-
(§1.1) for now; a DOM-aware gate is part of the deferred gesture work.
|
|
793
|
-
|
|
794
|
-
---
|
|
795
|
-
|
|
796
|
-
## 7. Layers — a stack, addressed by index
|
|
797
|
-
|
|
798
|
-
Layers solve **rebinding**: defaults, then the player's overrides, then a context-specific tweak — one
|
|
799
|
-
conceptual keymap assembled from stacked sources, top wins. They are a stack, addressed by index.
|
|
800
|
-
|
|
801
|
-
```js
|
|
802
|
-
const map = new InputMap();
|
|
803
|
-
|
|
804
|
-
map.bind('jump', InputTriggerKey.from('space')); // layer 0, the base
|
|
805
|
-
map.bind('undo', InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
806
|
-
InputTriggerKey.from('z')));
|
|
807
|
-
|
|
808
|
-
const user = map.pushLayer(); // index 1
|
|
809
|
-
user.bind('jump', InputTriggerGamepadButton.from(GamepadButtons.a)); // REPLACES space
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
```js
|
|
813
|
-
map.layer(i) // InputLayer at index i, asserts existence
|
|
814
|
-
map.pushLayer() // append, return the new top
|
|
815
|
-
map.popLayer() // discard the top
|
|
816
|
-
map.bind(...) // sugar for map.layer(0).bind(...)
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
Rules, and they are CSS's:
|
|
820
|
-
|
|
821
|
-
* Higher index is higher in the cascade.
|
|
822
|
-
* The cascade is **per action name**, and it applies identically to trigger bindings and coordinate bindings
|
|
823
|
-
(§9.1). The highest layer binding an action supplies *all* of that action's triggers or channels; lower
|
|
824
|
-
layers' entries for it are inert.
|
|
825
|
-
* To keep both, bind both **in one layer** — a binding maps one action to a *list*, exactly as
|
|
826
|
-
`font-family: a, b` lists fallbacks in one declaration:
|
|
827
|
-
|
|
828
|
-
```js
|
|
829
|
-
map.bind('jump', InputTriggerKey.from('space'), InputTriggerGamepadButton.from(GamepadButtons.a));
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
Replace-versus-add is a property of *where you write it*, not a flag on `bind`. That is what lets rebinding
|
|
833
|
-
("the user's jump wins") and multi-device ("either device jumps") both be expressible with no mode switch.
|
|
834
|
-
|
|
835
|
-
* An action named in the top layer that binds it, with nothing bound, is **unbound** — how a keymap layer
|
|
836
|
-
removes an inherited binding:
|
|
837
|
-
|
|
838
|
-
```js
|
|
839
|
-
map.layer(1).unbind('undo');
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
* An action name is either a trigger action or a coordinate action, never both — asserted at authoring time,
|
|
843
|
-
because `isActive('move')` and `axis2(v, 'move')` would otherwise disagree about what `'move'` is.
|
|
844
|
-
|
|
845
|
-
Layers carry no names. There is nothing to look up, so nothing to mistype, and serialization is one `uint16`
|
|
846
|
-
count with no strings (§10).
|
|
847
|
-
|
|
848
|
-
---
|
|
849
|
-
|
|
850
|
-
## 8. Occlusion — arbitration across entities
|
|
851
|
-
|
|
852
|
-
Layers are within one map. Modality is across maps, and it is a different question: the cascade is *nominal*
|
|
853
|
-
(over action names), occlusion is *physical* (over switches).
|
|
854
|
-
|
|
855
|
-
```js
|
|
856
|
-
export const InputOcclusion = {
|
|
857
|
-
/** Sees everything, blocks nothing. HUD overlays, debug readouts, recorders. */
|
|
858
|
-
Pass: 0,
|
|
859
|
-
/** Switches consumed by this map's fired triggers are invisible below, for that edge. The default. */
|
|
860
|
-
BlockMatched: 1,
|
|
861
|
-
/** Every map below is suppressed outright. Modal dialogs, pause menus. */
|
|
862
|
-
BlockAll: 2
|
|
863
|
-
};
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
**The two values do different jobs at different scopes, and revision 2's failure to say so was a real bug.**
|
|
867
|
-
|
|
868
|
-
### 8.1 `BlockMatched` — dynamic, per edge, dispatch only
|
|
869
|
-
|
|
870
|
-
Per edge, the walk descends maps by `order`. A `consumed : BitSet` over interned switch indices accumulates as
|
|
871
|
-
it goes; a fired trigger adds its own switches, and a trigger whose switches are already consumed does not
|
|
872
|
-
dispatch.
|
|
873
|
-
|
|
874
|
-
**Consumption gates dispatch, not evaluation** — and this is the correction that matters, because the obvious
|
|
875
|
-
alternative is broken. Hiding a consumed edge from a lower map entirely freezes that map's triggers at their
|
|
876
|
-
last observed values, and the next edge they *do* see no longer looks like a change. Concretely: an upper map
|
|
877
|
-
binding `Release(k)` and a lower map binding `k`. Every release is consumed above, so the lower map never
|
|
878
|
-
observes `k` going up; its press trigger fires once and is then dead forever. So lower maps keep evaluating,
|
|
879
|
-
and only their dispatch is suppressed.
|
|
880
|
-
|
|
881
|
-
The honest consequence, since an earlier revision of this document claimed otherwise: a modal-consumed
|
|
882
|
-
keystroke **does** advance a lower map's sequence and **does** run its hold timers. Only the firing is
|
|
883
|
-
withheld. `BlockAll` is what makes a lower map genuinely inert (§8.2), and that is another reason the two
|
|
884
|
-
values are not degrees of the same thing.
|
|
885
|
-
|
|
886
|
-
Consumption is also **unconditional within a map**. Specificity arbitration — `Ctrl+Z` beating a bare `Z`
|
|
887
|
-
(§4.6) — is a property of the map, not of its occlusion, so it holds in a `Pass` map too. What occlusion
|
|
888
|
-
decides is only whether that consumption is still standing when the next map down is walked.
|
|
889
|
-
|
|
890
|
-
`BlockMatched` does not affect polled reads. A pause menu using it stops the combat scene's `Escape` while the
|
|
891
|
-
camera keeps panning behind it — which is exactly what §8 wants of it.
|
|
892
|
-
|
|
893
|
-
### 8.2 `BlockAll` — static, whole-map, dispatch *and* reads
|
|
894
|
-
|
|
895
|
-
Each linked map carries a computed `suppressed : boolean` — "is there a `BlockAll` map above me". It is
|
|
896
|
-
recomputed when the linked map set, any `order`, or any `occlusion` changes; not per edge.
|
|
897
|
-
|
|
898
|
-
A suppressed map is **inert**: the walk skips it, so nothing dispatches; `isActive` returns `false`; coordinate
|
|
899
|
-
reads return zero; sequence matchers do not advance and hold timers do not run. On un-suppression every
|
|
900
|
-
sequence matcher is re-entered with `enter(0, held, now)` (§5.1), so it resumes against current reality rather
|
|
901
|
-
than stale state.
|
|
902
|
-
|
|
903
|
-
This is the fix for the defect revision 2 shipped. There, occlusion was a per-edge `consumed` set gating
|
|
904
|
-
*firing* only, while `isActive` and the coordinate accessors are **polled** — outside any edge walk, with
|
|
905
|
-
nothing connecting them. So the §11 pause menu stopped edge-driven actions while the §11 camera, which is a
|
|
906
|
-
`Script` polling `map.axis2(pan, 'pan')` over WASD every tick, kept panning: precisely the drifting-world bug
|
|
907
|
-
the modal value exists to prevent. Its two occlusion test cases both passed, because both tested dispatched
|
|
908
|
-
actions only.
|
|
909
|
-
|
|
910
|
-
### 8.3 Consequences worth stating
|
|
911
|
-
|
|
912
|
-
* **Local co-op needs no fourth value.** `BlockMatched` is per switch, so player 2's pad is untouched by player
|
|
913
|
-
1's map — they never reference the same switch. `BlockAll` suppresses whole maps and is therefore wrong for
|
|
914
|
-
split-screen; that is a property of the modal hammer, not a hole in it.
|
|
915
|
-
* **`order` ties are resolved by link order**, oldest first. Deterministic, and mentioned because the format
|
|
916
|
-
clamps `order` (§10) and ties are therefore reachable.
|
|
917
|
-
* **Lifetime is entity lifetime.** This is already how the game scopes input:
|
|
918
|
-
`CombatInteractionStateMachine` registers an entity per FSM state and lets `link`/`unlink` turn bindings on
|
|
919
|
-
and off (`CombatInteractionStateMachine.js:113-160`). Put the `InputMap` on the entity owning the context and
|
|
920
|
-
entering or leaving the state *is* the binding lifecycle — no `enable()`/`disable()` bookkeeping, no
|
|
921
|
-
`clearControls` on teardown.
|
|
922
|
-
|
|
923
|
-
---
|
|
924
|
-
|
|
925
|
-
## 9. The component and the system
|
|
926
|
-
|
|
927
|
-
### 9.1 `InputMap`
|
|
928
|
-
|
|
929
|
-
```js
|
|
930
|
-
export class InputMap {
|
|
931
|
-
static typeName = "InputMap";
|
|
932
|
-
static serializable = true;
|
|
933
|
-
|
|
934
|
-
/** Index 0 is the base. @readonly @type {InputLayer[]} */
|
|
935
|
-
layers = new List();
|
|
936
|
-
|
|
937
|
-
/** Cross-entity arbitration, higher evaluates first. @type {number} */
|
|
938
|
-
order = 0;
|
|
939
|
-
|
|
940
|
-
/** @type {InputOcclusion} */
|
|
941
|
-
occlusion = InputOcclusion.BlockMatched;
|
|
942
|
-
|
|
943
|
-
// ---- authoring -----------------------------------------------------
|
|
944
|
-
bind(action, ...triggers) { } // -> InputMap, writes to layer 0
|
|
945
|
-
bindCoordinate(action, ...channels) { } // -> InputMap, writes to layer 0
|
|
946
|
-
unbind(action) { } // -> InputMap, covers both kinds
|
|
947
|
-
layer(index) { } // -> InputLayer
|
|
948
|
-
pushLayer() { } // -> InputLayer
|
|
949
|
-
popLayer() { }
|
|
950
|
-
|
|
951
|
-
// ---- reading -------------------------------------------------------
|
|
952
|
-
isActive(action) { } // -> boolean
|
|
953
|
-
coordinate(result, offset, action) { } // any dimensionality
|
|
954
|
-
axis1(result, action) { } // Vector1
|
|
955
|
-
axis2(result, action) { } // Vector2
|
|
956
|
-
axis3(result, action) { } // Vector3
|
|
957
|
-
|
|
958
|
-
equals(other) { }
|
|
959
|
-
hash() { }
|
|
960
|
-
}
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
```js
|
|
964
|
-
export class InputLayer {
|
|
965
|
-
/** @readonly @type {InputBinding[]} */
|
|
966
|
-
bindings = new List();
|
|
967
|
-
/** @readonly @type {List<InputCoordinateBinding>} */
|
|
968
|
-
coordinates = new List();
|
|
969
|
-
|
|
970
|
-
bind(action, ...triggers) { }
|
|
971
|
-
bindCoordinate(action, ...channels) { }
|
|
972
|
-
unbind(action) { }
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
export class InputBinding {
|
|
976
|
-
action = "";
|
|
977
|
-
/** @readonly @type {InputTrigger[]} */
|
|
978
|
-
triggers = new List();
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
export class InputCoordinateBinding {
|
|
982
|
-
action = "";
|
|
983
|
-
/** @readonly @type {InputCoordinateChannel[]} */
|
|
984
|
-
channels = new List();
|
|
985
|
-
}
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
**Coordinate bindings are a first-class list on the layer, not triggers.** Revision 2 exposed
|
|
989
|
-
`bindCoordinate()` with nowhere to store the result — `InputBinding` holds triggers, and a channel is not a
|
|
990
|
-
trigger — so coordinate bindings existed in the API, in the examples and in the argument for deleting
|
|
991
|
-
`KeyboardCameraController`, but in neither the component nor the wire format.
|
|
992
|
-
|
|
993
|
-
**Multiple channels on one action resolve by priority: the first channel with any non-zero component wins.**
|
|
994
|
-
Deterministic, allocation-free, and the behaviour consumers already hand-write — `GamepadDevice`'s own
|
|
995
|
-
documented example is "stick wins, else D-pad". It also avoids the alternative's arithmetic nonsense, where
|
|
996
|
-
keyboard `D` plus a stick at 0.3 would sum to 1.3. All channels bound to one action must agree on
|
|
997
|
-
`dimensions`; asserted at authoring time.
|
|
998
|
-
|
|
999
|
-
The simple case never mentions layers, occlusion or order:
|
|
1000
|
-
|
|
1001
|
-
```js
|
|
1002
|
-
const map = new InputMap();
|
|
1003
|
-
map.bind('jump', InputTriggerKey.from('space'));
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
### 9.2 `InputMapSystem`
|
|
1007
|
-
|
|
1008
|
-
```js
|
|
1009
|
-
export class InputMapSystem extends System {
|
|
1010
|
-
dependencies = [InputMap];
|
|
1011
|
-
|
|
1012
|
-
registerDevice(adapter) { } // may be called after maps are linked; recompiles them (§3.1)
|
|
1013
|
-
|
|
1014
|
-
link(map, entity) { } // compile (§9.3), subscribe to newly-referenced switches
|
|
1015
|
-
unlink(map, entity) { } // release; unsubscribe switches no live map references
|
|
1016
|
-
update(dt) { } // time only: hold maturity, sequence windows, Delta reset, suppression
|
|
1017
|
-
|
|
1018
|
-
setRecorder(recorder) { } // §2.5 — DESIGNED, NOT BUILT
|
|
1019
|
-
}
|
|
1020
|
-
```
|
|
1021
|
-
|
|
1022
|
-
Construction takes adapters rather than `engine.devices`, so the system has no dependency on the shape of that
|
|
1023
|
-
object and is constructible in a test with nothing behind it:
|
|
1024
|
-
|
|
1025
|
-
```js
|
|
1026
|
-
new InputMapSystem([
|
|
1027
|
-
new KeyboardInputDeviceAdapter(devices.keyboard),
|
|
1028
|
-
new PointerInputDeviceAdapter(devices.pointer),
|
|
1029
|
-
new GamepadInputDeviceAdapter(devices.gamepad)
|
|
1030
|
-
]);
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
Each interned switch is subscribed once, with a per-switch `SourceBinding {index, system}` passed as the
|
|
1034
|
-
`Signal` handler's context (`Signal.js:151`), so the edge path allocates nothing and needs no closure per
|
|
1035
|
-
event.
|
|
1036
|
-
|
|
1037
|
-
*(As built, the system declares no `components_used`, and does not need to: relative channels LATCH rather
|
|
1038
|
-
than clear (§3.3), so every reader in a frame sees the same value regardless of where the system runs in it.
|
|
1039
|
-
An earlier revision of this section leaned on execution order instead, which was the weaker guarantee.)*
|
|
1040
|
-
|
|
1041
|
-
**Unlinking a map with a sequence mid-match** discards its matcher state along with the compiled program;
|
|
1042
|
-
there is nothing to leak and nothing to resume. Unlinking with switches held is likewise clean: `#held` is
|
|
1043
|
-
owned by the system, not the map, and the map's triggers are simply gone.
|
|
1044
|
-
|
|
1045
|
-
### 9.3 Compilation: the stylesheet / computed-style split
|
|
1046
|
-
|
|
1047
|
-
The `InputMap` component is *authored* data — layered, editable. It is not what the edge path reads. On `link`,
|
|
1048
|
-
on `registerDevice`, and on any structural change, the system compiles it into an internal `InputMapProgram`:
|
|
1049
|
-
|
|
1050
|
-
* cascade resolved — one flat action table per kind, layers already collapsed;
|
|
1051
|
-
* switches interned to `Uint32` indices against a system-wide table;
|
|
1052
|
-
* per switch, the list of triggers it can affect (`collectSources`), pre-sorted into §4.6 specificity order, so
|
|
1053
|
-
an edge touches only triggers that could possibly care;
|
|
1054
|
-
* every trigger's `satisfied` initialised from current state, not from `false` (§4.5);
|
|
1055
|
-
* sequence matcher state allocated as three typed-array slots per sequence binding (§5.1).
|
|
1056
|
-
|
|
1057
|
-
The edge path then touches integers, typed arrays and `BitSet`s only. No string comparison, no `List`
|
|
1058
|
-
traversal, no allocation per event. Recompiles requested during dispatch are queued and applied at the end of
|
|
1059
|
-
the current edge (§2.3), so a walk always completes against the program it started with.
|
|
1060
|
-
|
|
1061
|
-
---
|
|
1062
|
-
|
|
1063
|
-
## 10. Serialization
|
|
1064
|
-
|
|
1065
|
-
```js
|
|
1066
|
-
export class InputMapSerializationAdapter extends BinaryClassSerializationAdapter {
|
|
1067
|
-
klass = InputMap;
|
|
1068
|
-
version = 0;
|
|
1069
|
-
|
|
1070
|
-
serialize(buffer, map) { }
|
|
1071
|
-
deserialize(buffer, map) { }
|
|
1072
|
-
}
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
```
|
|
1076
|
-
InputMap
|
|
1077
|
-
int16 order see below
|
|
1078
|
-
uint8 occlusion InputOcclusion
|
|
1079
|
-
uint8 device_count
|
|
1080
|
-
repeat device_count x utf8 device table - every InputDeviceAdapter#id referenced
|
|
1081
|
-
uint16 layer_count
|
|
1082
|
-
repeat layer_count x InputLayer index 0 first; layers carry no name
|
|
1083
|
-
|
|
1084
|
-
InputLayer
|
|
1085
|
-
uint16 binding_count
|
|
1086
|
-
repeat binding_count x InputBinding
|
|
1087
|
-
uint16 coordinate_binding_count
|
|
1088
|
-
repeat coordinate_binding_count x InputCoordinateBinding
|
|
1089
|
-
|
|
1090
|
-
InputBinding
|
|
1091
|
-
utf8 action
|
|
1092
|
-
uint8 trigger_count
|
|
1093
|
-
repeat trigger_count x InputTrigger
|
|
1094
|
-
|
|
1095
|
-
InputCoordinateBinding
|
|
1096
|
-
utf8 action
|
|
1097
|
-
uint8 channel_count
|
|
1098
|
-
repeat channel_count x InputCoordinateChannel
|
|
1099
|
-
|
|
1100
|
-
InputTrigger
|
|
1101
|
-
uint8 type InputTriggerType
|
|
1102
|
-
Switch : InputSource
|
|
1103
|
-
Direction : InputCoordinateChannel, uint8 direction, float32 threshold, float32 hysteresis
|
|
1104
|
-
Chord : uint8 child_count, children
|
|
1105
|
-
Sequence : uint8 child_count, children, float32 step_timeout
|
|
1106
|
-
Hold : child, float32 duration
|
|
1107
|
-
Release : child
|
|
1108
|
-
|
|
1109
|
-
InputCoordinateChannel
|
|
1110
|
-
uint8 type InputCoordinateChannelType
|
|
1111
|
-
Device : InputCoordinateSource
|
|
1112
|
-
Switches : uint8 count, count x InputTrigger (2 per dimension: negative, positive)
|
|
1113
|
-
Compose : uint8 count, count x InputCoordinateChannel
|
|
1114
|
-
|
|
1115
|
-
InputSource
|
|
1116
|
-
uint8 device_index into the map's device table
|
|
1117
|
-
uint16 code
|
|
1118
|
-
|
|
1119
|
-
InputCoordinateSource
|
|
1120
|
-
uint8 device_index
|
|
1121
|
-
uint16 code
|
|
1122
|
-
uint8 dimensions
|
|
1123
|
-
uint8 kind InputCoordinateKind
|
|
1124
|
-
```
|
|
1125
|
-
|
|
1126
|
-
Decisions, not incidentals:
|
|
1127
|
-
|
|
1128
|
-
* **`order` is `int16`.** Revision 2 wrote it as `uint8 + 128`, range `[-128, 127]` — and its own modal example
|
|
1129
|
-
used `order = 1000`, which clamped to 127 on load and could silently tie with any other high map. `int16`
|
|
1130
|
-
covers the examples and leaves headroom; ties resolve by link order (§8.3).
|
|
1131
|
-
* **Coordinate bindings are reachable from the root.** Revision 2 defined an `InputCoordinateChannel` encoding
|
|
1132
|
-
that nothing referenced, so no map containing a coordinate could round-trip.
|
|
1133
|
-
* **`Direction` serializes a channel, not a source.** It accepts any `InputCoordinate*` channel (§4.2), so
|
|
1134
|
-
writing only an `InputCoordinateSource` made direction-over-composed-channel constructible and
|
|
1135
|
-
unserializable.
|
|
1136
|
-
* **A device table, not a string per source.** A 40-binding keyboard map writes `"keyboard"` once rather than
|
|
1137
|
-
forty times.
|
|
1138
|
-
* **Every string here is an identifier.** `writeUTF8String` is ASCII-only in practice and `readUTF8String` is
|
|
1139
|
-
NUL-terminated by design, so this must be stated: action names and device ids are ASCII identifiers, not
|
|
1140
|
-
localized text. A binding UI shows a label looked up *from* the action name; it never serializes the label.
|
|
1141
|
-
* **Codes come from enums, three of which are new.** `KeyCodes`, `GamepadButtons` and `GamepadAxes` are already
|
|
1142
|
-
stable on-disk quantities; `MouseButtons`, `PointerCoordinates` and `GamepadCoordinates` are introduced here
|
|
1143
|
-
and are append-only once shipped (§3.1). Revision 2 claimed "no new numbering to keep in sync", which was
|
|
1144
|
-
false the moment it introduced them.
|
|
1145
|
-
* **Triggers and channels nest, so serialization recurses.** One `uint8` discriminator each, dispatched by a
|
|
1146
|
-
`switch` — verbose, typed and greppable, over a string-keyed constructor table.
|
|
1147
|
-
|
|
1148
|
-
---
|
|
1149
|
-
|
|
1150
|
-
## 11. End to end
|
|
1151
|
-
|
|
1152
|
-
**Gameplay — camera, mixed devices:**
|
|
1153
|
-
|
|
1154
|
-
```js
|
|
1155
|
-
const map = new InputMap();
|
|
1156
|
-
|
|
1157
|
-
map.bindCoordinate('pan',
|
|
1158
|
-
InputCoordinateGamepad.from(GamepadCoordinates.left_stick), // first non-zero wins
|
|
1159
|
-
InputCoordinateSwitches.from([
|
|
1160
|
-
InputTriggerKey.from('a'), InputTriggerKey.from('d'),
|
|
1161
|
-
InputTriggerKey.from('w'), InputTriggerKey.from('s')
|
|
1162
|
-
]));
|
|
1163
|
-
map.bind('user-input', InputTriggerKey.from('a'), InputTriggerKey.from('d'),
|
|
1164
|
-
InputTriggerKey.from('w'), InputTriggerKey.from('s'));
|
|
1165
|
-
map.bind('focus_selection', InputTriggerKey.from('f'));
|
|
1166
|
-
|
|
1167
|
-
new Entity()
|
|
1168
|
-
.add(map)
|
|
1169
|
-
.add(new Script(dt => {
|
|
1170
|
-
map.axis2(pan, 'pan');
|
|
1171
|
-
apply_radial_dead_zone(pan, pan.x, pan.y, 0.15, 0.95); // policy, at the call site
|
|
1172
|
-
controller.pan(pan, dt);
|
|
1173
|
-
}))
|
|
1174
|
-
.build(ecd);
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
Both channels feed one action in one call, so the cascade replaces them as a unit; the stick wins while
|
|
1178
|
-
deflected and the keys take over when it rests. The `'user-input'` binding is
|
|
1179
|
-
`KeyboardCameraController.js:54`'s interaction signal, carried over explicitly rather than lost.
|
|
1180
|
-
|
|
1181
|
-
**Editor — `EditorKeyMap` as data:**
|
|
1182
|
-
|
|
1183
|
-
```js
|
|
1184
|
-
const ctrl = InputTriggerKey.from('ctrl');
|
|
1185
|
-
|
|
1186
|
-
const map = new InputMap();
|
|
1187
|
-
map.order = 100;
|
|
1188
|
-
|
|
1189
|
-
map.bind('undo', InputTriggerChord.from(ctrl, InputTriggerKey.from('z')));
|
|
1190
|
-
map.bind('redo', InputTriggerChord.from(ctrl, InputTriggerKey.from('y')));
|
|
1191
|
-
map.bind('copy', InputTriggerChord.from(ctrl, InputTriggerKey.from('c')));
|
|
1192
|
-
map.bind('paste', InputTriggerChord.from(ctrl, InputTriggerKey.from('v')));
|
|
1193
|
-
map.bind('save', InputTriggerChord.from(ctrl, InputTriggerKey.from('s')));
|
|
1194
|
-
map.bind('duplicate', InputTriggerChord.from(InputTriggerKey.from('shift'), InputTriggerKey.from('d')));
|
|
1195
|
-
map.bind('delete', InputTriggerKey.from('delete'), InputTriggerKey.from('x'));
|
|
1196
|
-
|
|
1197
|
-
ecd.addEntityEventListener(entity, 'undo', () => editor.actions.undo());
|
|
1198
|
-
```
|
|
1199
|
-
|
|
1200
|
-
`Ctrl+Z` beats the bare `Z` binding under §4.6 (modifier-first; see §4.6's stated condition), and the
|
|
1201
|
-
`if (event.ctrlKey)` split disappears. The bindings are serializable, so the editor gets a rebinding panel for
|
|
1202
|
-
free — the thing that could not exist while they were `if` statements. §14.1 lists the three `EditorKeyMap`
|
|
1203
|
-
behaviours that do *not* come across.
|
|
1204
|
-
|
|
1205
|
-
**Modal — pause menu:**
|
|
1206
|
-
|
|
1207
|
-
```js
|
|
1208
|
-
const map = new InputMap();
|
|
1209
|
-
map.order = 1000;
|
|
1210
|
-
map.occlusion = InputOcclusion.BlockAll;
|
|
1211
|
-
map.bind('resume', InputTriggerKey.from('escape'));
|
|
1212
|
-
```
|
|
1213
|
-
|
|
1214
|
-
Every map below is suppressed: no dispatch, and `isActive`/`axis2` read neutral — so the camera above stops
|
|
1215
|
-
rather than drifting behind the dialog (§8.2).
|
|
1216
|
-
|
|
1217
|
-
**Cross-device chords:**
|
|
1218
|
-
|
|
1219
|
-
```js
|
|
1220
|
-
map.bind('precision_place', InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
1221
|
-
InputTriggerMouseButton.from(MouseButtons.left)));
|
|
1222
|
-
map.bind('pad_reset', InputTriggerChord.from(InputTriggerGamepadButton.from(GamepadButtons.back),
|
|
1223
|
-
InputTriggerKey.from('r')));
|
|
1224
|
-
```
|
|
1225
|
-
|
|
1226
|
-
---
|
|
1227
|
-
|
|
1228
|
-
## 12. Testing
|
|
1229
|
-
|
|
1230
|
-
### 12.1 No mocks, and no DOM
|
|
1231
|
-
|
|
1232
|
-
The seam is `InputDeviceSwitch`: a real object with `press()`/`release()`, constructible in node with nothing
|
|
1233
|
-
beneath it. A test adapter is a *real* adapter over *real* switches — not a mock. Eager evaluation keeps the
|
|
1234
|
-
tests short, because there is no tick to pump for edge-driven behaviour:
|
|
1235
|
-
|
|
1236
|
-
```js
|
|
1237
|
-
const kb = new TestInputDeviceAdapter('keyboard', KeyCodes);
|
|
1238
|
-
const system = new InputMapSystem([kb]);
|
|
1239
|
-
// real EntityComponentDataset, real InputMap, real listeners
|
|
1240
|
-
|
|
1241
|
-
kb.switch(KeyCodes.ctrl).press();
|
|
1242
|
-
kb.switch(KeyCodes.z).press();
|
|
1243
|
-
|
|
1244
|
-
expect(observed).toEqual(['undo']); // no system.update() anywhere
|
|
1245
|
-
```
|
|
1246
|
-
|
|
1247
|
-
`system.update(dt)` appears only in tests for hold maturity, sequence windows, `Delta` reset and suppression —
|
|
1248
|
-
a precise statement of what the tick is for.
|
|
1249
|
-
|
|
1250
|
-
*(Revision 2 justified this by claiming `InputSystem` "cannot be exercised without a focusable DOM element".
|
|
1251
|
-
That was overstated: focusability is `KeyboardDevice`'s requirement (`KeyboardDevice.js:61-66`), and
|
|
1252
|
-
`InputSystem` resolves paths against any object, so a synthetic tree of real Signals would exercise it. The
|
|
1253
|
-
real point is narrower and still worth making — nobody has, and `InputBinding.spec.js` asserts only that a
|
|
1254
|
-
constructor does not throw.)*
|
|
1255
|
-
|
|
1256
|
-
### 12.2 What gets asserted
|
|
1257
|
-
|
|
1258
|
-
Observable behaviour only: dispatched action names and order, `isActive` levels, coordinate values. Never which
|
|
1259
|
-
triggers were evaluated, never how many times a matcher ran, never internal matcher state.
|
|
1260
|
-
|
|
1261
|
-
| | Case | Guards |
|
|
1262
|
-
|---|---|---|
|
|
1263
|
-
| 1 | An action fires before any `update()` runs | §2 eager evaluation — the requirement, as a test |
|
|
1264
|
-
| 2 | Press and release inside one frame both fire; two presses dispatch in press order | §2 |
|
|
1265
|
-
| 3 | `Ctrl` then `Z` fires only `undo`; `Z` then `Ctrl` fires both, in that order | §4.6 and its stated condition |
|
|
1266
|
-
| 4 | One long press of `A` does not satisfy both steps of `[A, A]` | §5.1 entry snapshot |
|
|
1267
|
-
| 5 | `[A,A,B]` fires on `A A A B` | §5.3 |
|
|
1268
|
-
| 6 | Holding `A` and tapping `B` twice fires `[A,B]` **once** | §5.3 post-fire re-entry — the revision-2 machine-gun |
|
|
1269
|
-
| 7 | A window expiry with the first step's key held does not spuriously advance | §5.1 — the revision-2 stale-snapshot bug |
|
|
1270
|
-
| 8 | `Ctrl+Shift+A > B` fires whether or not modifiers are released first | §5.4 |
|
|
1271
|
-
| 9 | `↓ ↘ →` over a stick coordinate fires; the same over raw d-pad *buttons* does not | §5.4 — the exclusivity claim, both directions |
|
|
1272
|
-
| 10 | Charge (`hold ← 2 s > →`) fires after a 10 s hold; returning to neutral instead expires it | §5.2 window refresh — the revision-2 charge failure |
|
|
1273
|
-
| 11 | A sequence window expires only after the previous step stops holding | §5.2 |
|
|
1274
|
-
| 12 | `Release(Chord(a,b))` does not fire on the first edge touching `a` | §4.5 `satisfied` initialisation |
|
|
1275
|
-
| 13 | A handler that removes its own entity, or rebinds its own map, does not corrupt the walk | §2.3 |
|
|
1276
|
-
| 14 | A `BlockAll` map zeroes a lower map's `axis2` and `isActive`, not just its dispatch | §8.2 — the revision-2 drifting camera |
|
|
1277
|
-
| 15 | A `BlockMatched` map does **not** stop a lower map's unrelated action or its coordinate reads | §8.1 — the converse |
|
|
1278
|
-
| 16 | A lower map's press binding still fires after many edges consumed above it | §8.1 — the stale-edge trap |
|
|
1279
|
-
| 17 | Un-suppressing a map re-enters its sequences at 0 rather than resuming stale state | §8.2 |
|
|
1280
|
-
| 18 | The top layer's `jump` replaces the base layer's `jump`; two triggers in one layer both fire | §7 |
|
|
1281
|
-
| 19 | A coordinate action bound to stick + keys reads the stick while deflected, the keys at rest | §9.1 priority |
|
|
1282
|
-
| 20 | A gamepad binding with no pad connected links and never fires; so does an unregistered device id | §3.1 totality |
|
|
1283
|
-
| 21 | `registerDevice` after link makes previously inert bindings live | §3.1, §9.3 |
|
|
1284
|
-
| 22 | Two wheel events in one frame sum for tick-time readers | §3.3 `Delta` |
|
|
1285
|
-
| 23 | A 3-D wheel coordinate round-trips all three components | §3.3 dimensionality |
|
|
1286
|
-
| 24 | Serialize, deserialize, replay a recorded trace, get the same dispatches | §10, §2.5 |
|
|
1287
|
-
|
|
1288
|
-
Cases 6, 7, 10 and 14 are regressions against defects revision 2 actually contained; write them first. Case 9
|
|
1289
|
-
is the only claim in §5.4 that could be wrong in a way the others would not catch.
|
|
1290
|
-
|
|
1291
|
-
---
|
|
1292
|
-
|
|
1293
|
-
## 13. The trigger DSL — planned, deferred, optional
|
|
1294
|
-
|
|
1295
|
-
**Not implemented in this design.** Specified here so the trigger classes are shaped to accept it, and so it
|
|
1296
|
-
is not invented ad hoc later.
|
|
1297
|
-
|
|
1298
|
-
### 13.1 What it is
|
|
1299
|
-
|
|
1300
|
-
A text syntax compiling to the same `InputTrigger` objects §4 defines. It is **an additional entry point,
|
|
1301
|
-
never a required one**: an author can compose triggers by hand or write them as text, and the runtime cannot
|
|
1302
|
-
tell the results apart.
|
|
1303
|
-
|
|
1304
|
-
```js
|
|
1305
|
-
import { InputTriggerDSL } from "./dsl/InputTriggerDSL.js";
|
|
1306
|
-
|
|
1307
|
-
map.bind('promote', InputTriggerDSL.compile("keyboard:ctrl + keyboard:shift + keyboard:a > keyboard:b"));
|
|
1308
|
-
```
|
|
1309
|
-
|
|
1310
|
-
### 13.2 Grammar
|
|
1311
|
-
|
|
1312
|
-
```ebnf
|
|
1313
|
-
trigger = sequence ;
|
|
1314
|
-
sequence = chord , { ">" , chord } , [ "within" , NUMBER ] ;
|
|
1315
|
-
chord = unary , { "+" , unary } ;
|
|
1316
|
-
unary = [ "!" ] , primary ;
|
|
1317
|
-
primary = atom | "(" , trigger , ")" | call ;
|
|
1318
|
-
call = IDENT , "(" , [ trigger , { "," , trigger | NUMBER } ] , ")" ;
|
|
1319
|
-
atom = DEVICE , ":" , BINDPOINT , [ "." , SUBPOINT ] ;
|
|
1320
|
-
DEVICE = IDENT ;
|
|
1321
|
-
BINDPOINT = IDENT | NUMBER ;
|
|
1322
|
-
SUBPOINT = IDENT ;
|
|
1323
|
-
```
|
|
1324
|
-
|
|
1325
|
-
| Form | Means | Compiles to |
|
|
1326
|
-
|---|---|---|
|
|
1327
|
-
| `keyboard:space` | a key | `InputTriggerKey` |
|
|
1328
|
-
| `pointer:left` | a mouse button | `InputTriggerMouseButton` |
|
|
1329
|
-
| `gamepad:a` | a pad button | `InputTriggerGamepadButton` |
|
|
1330
|
-
| `gamepad:left_stick.down` | a direction on a coordinate | `InputTriggerDirection` |
|
|
1331
|
-
| `a + b` | chord | `InputTriggerChord` |
|
|
1332
|
-
| `a > b` | sequence | `InputTriggerSequence` |
|
|
1333
|
-
| `!a` | release / negation | `InputTriggerRelease` |
|
|
1334
|
-
| `hold(a, 0.5)` | held for 0.5 s | `InputTriggerHold` |
|
|
1335
|
-
| `(...)` | grouping | — |
|
|
1336
|
-
| `... within 0.2` | step timeout | `withStepTimeout` |
|
|
1337
|
-
|
|
1338
|
-
**Precedence, tightest first:** `!` → `+` → `>` → `within`, so
|
|
1339
|
-
`keyboard:ctrl + keyboard:shift + keyboard:a > keyboard:b` parses as intended with no parentheses.
|
|
1340
|
-
|
|
1341
|
-
**Keyword collision** is resolved by the `:` — an identifier followed by `:` is a device name, so a device may
|
|
1342
|
-
be called `hold` without ambiguity. Worth stating because it is where small DSLs usually break.
|
|
1343
|
-
|
|
1344
|
-
The §5.4 motion, written out:
|
|
1345
|
-
|
|
1346
|
-
```
|
|
1347
|
-
gamepad:left_stick.down > gamepad:left_stick.down_right > gamepad:left_stick.right + gamepad:x within 0.2
|
|
1348
|
-
```
|
|
1349
|
-
|
|
1350
|
-
### 13.3 Why it stays separate
|
|
1351
|
-
|
|
1352
|
-
* **`ism/dsl/` is its own module**, imported by nothing in the runtime. A game that composes triggers by hand
|
|
1353
|
-
never pays for a parser — it is not in the bundle.
|
|
1354
|
-
* **It is a compiler, not a representation.** Serialization stays binary (§10), so a DSL change can never
|
|
1355
|
-
invalidate saved bindings.
|
|
1356
|
-
* **`InputTriggerDSL.stringify(trigger)` is the inverse**, which is what a rebinding UI wants: it shows a
|
|
1357
|
-
binding in the syntax an author would type, with no second formatting path.
|
|
1358
|
-
|
|
1359
|
-
### 13.4 Deferred until
|
|
1360
|
-
|
|
1361
|
-
Everything in §14 phases 1–8 has landed and at least one consumer has migrated. The DSL is a convenience over a
|
|
1362
|
-
working system; building it first would freeze the trigger vocabulary before the vocabulary has been used.
|
|
1363
|
-
|
|
1364
|
-
---
|
|
1365
|
-
|
|
1366
|
-
## 14. Migration
|
|
1367
|
-
|
|
1368
|
-
The ISM lands beside the existing systems. Both keep working; neither is touched until its consumers move.
|
|
1369
|
-
**`InputController` and `InputControllerSystem` survive this program** — they still own every gesture binding
|
|
1370
|
-
(§1.1).
|
|
1371
|
-
|
|
1372
|
-
*This table is the plan of work, not a progress report. For where each phase actually stands, read the status
|
|
1373
|
-
section of [`ISM_IMPLEMENTATION_PLAN.md`](ISM_IMPLEMENTATION_PLAN.md), which is kept current as phases land.*
|
|
1374
|
-
|
|
1375
|
-
| Phase | Work | Gate |
|
|
1376
|
-
|---|---|---|
|
|
1377
|
-
| **1** | `InputSource`, `InputCoordinateSource`, the `InputTrigger*` classes, `InputMap` / `InputLayer` / `InputBinding` / `InputCoordinateBinding` | Value-object specs: `equals` / `hash` / round-trip |
|
|
1378
|
-
| **2** | `InputDeviceAdapter` and the three adapters; the `MouseButtons`, `PointerCoordinates`, `GamepadCoordinates` enums | Totality: every declared code resolves with no hardware, and with no adapter (§12.2 cases 20–21) |
|
|
1379
|
-
| **3** | `InputMapSystem`: eager edge path, `#held`, compile, switch/chord/release, cascade, `BlockMatched`, ECS dispatch, `isActive` | cases 1–3, 12, 13, 15, 18 |
|
|
1380
|
-
| **4** | `InputTriggerSequence` + `InputTriggerHold`; synthetic maturity edges; window refresh and expiry | cases 4–8, 10, 11, 16 |
|
|
1381
|
-
| **5** | Coordinates: channels, priority resolution, the `Delta` latch, read accessors | cases 19, 22, 23; retires `KeyboardCameraController` |
|
|
1382
|
-
| **6** | `InputTriggerDirection` over coordinates, with threshold and hysteresis | case 9 |
|
|
1383
|
-
| **7** | `BlockAll` suppression across dispatch and reads; un-suppression re-entry | cases 14, 17 |
|
|
1384
|
-
| **8** | `InputMapSerializationAdapter` + `InputRecorder` | case 24 |
|
|
1385
|
-
| **9** | Migrate consumers (below) | existing specs stay green |
|
|
1386
|
-
| **10** | `ism/dsl/` — the §13 compiler and `stringify` | round-trip: `compile(stringify(t))` equals `t` |
|
|
1387
|
-
|
|
1388
|
-
### 14.1 Consumers
|
|
1389
|
-
|
|
1390
|
-
1. **`EditorKeyMap`** — **blocked, not retired.** The engine's `KeyboardDevice` is mounted on
|
|
1391
|
-
`viewStack.el` (`Engine.js:367`) and `Editor.js:563` pushes the entire editor GUI into that same stack, so
|
|
1392
|
-
an editor map would see keys typed into panel text fields — `x` would delete the selected entities, and
|
|
1393
|
-
`preventDefault` would swallow the keystroke. The ISM has no DOM awareness to express the old gate (§6.3),
|
|
1394
|
-
so retiring this needs a design addition. See the implementation plan §5.1. Four behaviours would have to be
|
|
1395
|
-
re-provided in any case, because none of them is in the ISM:
|
|
1396
|
-
* **Focus gating.** `isViewFocused` (`EditorKeyMap.js:12-31`) suppresses all editor bindings when focus is
|
|
1397
|
-
outside the game view. In the ISM this depends on which element the editor's `KeyboardDevice` is mounted
|
|
1398
|
-
on. With bare-key bindings like `x` = delete, getting it wrong is destructive — verify before landing.
|
|
1399
|
-
* **Autorepeat.** Holding `Ctrl+Z` currently repeat-undoes; it will not (§5.5). Decide per action whether
|
|
1400
|
-
that is a loss or a fix.
|
|
1401
|
-
* **`stopPropagation()`** on handled chords — currently prevents the event reaching other listeners.
|
|
1402
|
-
* **The unconditional forward to the active tool** (`EditorKeyMap.js:175-180`), which `Tool.handleKeyboardEvent`
|
|
1403
|
-
relies on: each tool gets its own `InputMap` on its activation entity, at a higher `order`.
|
|
1404
|
-
2. **`KeyboardCameraController`** — retired by §3.3, not ported. Carry the `'user-input'` event (§11).
|
|
1405
|
-
3. **`Input` / `InputSystem`** — **done.** All five consumers moved and both are `@deprecated`.
|
|
1406
|
-
`OverlayPageGUI` (escape), `CombatInteractionController` (p, escape) and two harnesses were straight
|
|
1407
|
-
translations. `StoryManager` was a split: it bound both a tap and the space bar to one action, and only
|
|
1408
|
-
the space bar moved. A tap is not a button press (§1.1) — translating it would change advance-on-release
|
|
1409
|
-
to advance-on-press and fire at the start of every drag — so the tap kept a small `InputController`, which
|
|
1410
|
-
is the decision the earlier revision of this section left open.
|
|
1411
|
-
4. **`InputController` keyboard bindings** — **not started.** The keyboard subset moves; `priority` becomes
|
|
1412
|
-
`order`, `exclusive` becomes `InputOcclusion.BlockMatched`. Gesture bindings stay put, which for
|
|
1413
|
-
`StrategyInteractionController` means most of the file. This is where escape arbitration finally bites
|
|
1414
|
-
(plan §5.4) and where `KeyboardCameraController`'s `'user-input'` entity subtlety has to be handled.
|
|
1415
|
-
5. **`components/InputBinding.js`** (`{path, listener, …}`) is orphaned only when `InputController` finally
|
|
1416
|
-
goes, which is not in this program.
|
|
1417
|
-
|
|
1418
|
-
### 14.2 A naming collision to plan around
|
|
1419
|
-
|
|
1420
|
-
*(Resolved: the new record lives at `ism/map/InputBinding.js`, so the two never shared a filename and the old
|
|
1421
|
-
one kept its path and its importer. What follows is the problem as it stood.)*
|
|
1422
|
-
|
|
1423
|
-
Phase 1 introduces `InputBinding` in `ism/`, and `ism/InputBinding.js` already exists as the old
|
|
1424
|
-
`{path, event}` record imported by [`Input.js:3`](../components/Input.js). The old file must survive until
|
|
1425
|
-
`Input` is retired (phase 9), so the two cannot share a filename. Land the new one as `ism/InputBinding.js`
|
|
1426
|
-
only after the old is deleted; until then it lives beside its siblings under a transitional name, or `Input`
|
|
1427
|
-
is repointed at a copy under `components/`.
|
|
1428
|
-
|
|
1429
|
-
---
|
|
1430
|
-
|
|
1431
|
-
## 15. Explicitly not doing
|
|
1432
|
-
|
|
1433
|
-
* **Pointer gestures** — tap, drag, move, wheel-as-gesture, pinch. §1.1, with the binding counts. Revision 2
|
|
1434
|
-
claimed "nothing in the codebase binds pinch today"; that was false —
|
|
1435
|
-
`StrategyInteractionController.js:437,447` binds `pinchStart` and `pinch`, and it is also the largest
|
|
1436
|
-
migration target.
|
|
1437
|
-
* **Buffering input to a frame boundary.** §2. Revision 1 did, and it bought nothing eager evaluation does not
|
|
1438
|
-
also provide.
|
|
1439
|
-
* **Discriminating tap from hold on one switch.** §5.5 — it requires withholding the short action, which is the
|
|
1440
|
-
buffering above. Accepted as a cost.
|
|
1441
|
-
* **Key autorepeat.** §5.5 — not observable through `InputDeviceSwitch` at all.
|
|
1442
|
-
* **Keeping string paths.** `resolvePath` throws where it should resolve and cannot express two sources at once;
|
|
1443
|
-
`GAMEPAD_API_PROPOSAL.md §5` says not to grow it.
|
|
1444
|
-
* **A trigger class per gesture pattern.** Double-tap is a two-step sequence with a short window; long-press is
|
|
1445
|
-
`InputTriggerHold`.
|
|
1446
|
-
* **Dead zones, sensitivity or response curves on analog output.** §3.3. The direction threshold and hysteresis
|
|
1447
|
-
in §3.4 are not exceptions: producing a boolean from a coordinate cannot be done without them.
|
|
1448
|
-
* **A `Started` / `Performed` / `Canceled` phase enum, or an `edge` field.** §4.1 — every trigger is a
|
|
1449
|
-
predicate and every binding fires on its rising edge.
|
|
1450
|
-
* **A per-binding `consume` flag.** Occlusion is a property of a *context*, not of one binding, and a boolean
|
|
1451
|
-
per binding makes behaviour depend on declaration order.
|
|
1452
|
-
* **Named layers.** §7 — a stack is addressed by index.
|
|
1453
|
-
* **Behaviour in the component.** No function references, no script names, no serialized handler ids (§6.1).
|
|
1454
|
-
* **DOM-target awareness in `InputActionEvent`.** §6.3 — it belongs with the deferred gesture work.
|
|
1455
|
-
* **Strict sequence rejection**, and **button buffering**. §5.5, both deferred with known shapes.
|
|
1456
|
-
* **A generic input node graph.** The trigger classes are already a small algebra; the DSL (§13) is the cheaper
|
|
1457
|
-
first step over it.
|
|
1458
|
-
|
|
1459
|
-
---
|
|
1460
|
-
|
|
1461
|
-
## 16. Resolved decisions
|
|
1462
|
-
|
|
1463
|
-
1. **Analog channels are N-dimensional "coordinates", not axes** — §3.3. `WheelEvent` has three components and
|
|
1464
|
-
`PointerDevice` surfaces all three. `kind` (`Absolute` / `Delta` / `Normalized`) carries the verb.
|
|
1465
|
-
2. **`order` stays author-set** — §8. A GUI-hierarchy-derived order would need no hand-picked numbers but would
|
|
1466
|
-
couple input arbitration to that hierarchy.
|
|
1467
|
-
3. **No sensitivity in bindings** — §3.3. A rebinding UI serializes *which* channel drives an action, never how
|
|
1468
|
-
twitchy it is.
|
|
1469
|
-
4. **Layers are an index-addressed stack, with no names** — §7.
|
|
1470
|
-
5. **Evaluation is eager, on the edge** — §2. No buffer, no added frame. The costs — re-entrant dispatch,
|
|
1471
|
-
arrival-ordering, the §4.6 chord condition, the §5.5 tap-versus-hold limit, and the §3.3 `Delta` split
|
|
1472
|
-
between eager and tick-time readers — are stated where they arise rather than hidden.
|
|
1473
|
-
6. **The trigger DSL is planned, specified and deferred** — §13, phase 10.
|
|
1474
|
-
7. **Pointer gestures are out of scope** — §1.1. `InputController` is reduced, not retired.
|
|
1475
|
-
8. **`SimpleStateMachine` is not used** — §5.6. The matcher is a path, not a graph; the class becomes the right
|
|
1476
|
-
tool if sequences ever branch.
|
|
1477
|
-
|
|
1478
|
-
---
|
|
1479
|
-
|
|
1480
|
-
## 17. What changed in revision 3, and why
|
|
1481
|
-
|
|
1482
|
-
An adversarial review of revision 2 found three defects that made it unimplementable, plus a set of false
|
|
1483
|
-
claims about the existing codebase. Everything below was verified against source before being changed.
|
|
1484
|
-
|
|
1485
|
-
### Defects
|
|
1486
|
-
|
|
1487
|
-
| | Was | Now |
|
|
1488
|
-
|---|---|---|
|
|
1489
|
-
| **Sequence matcher** | Reset paths (window expiry, post-fire) did not re-establish the entry snapshot, and the advance step indexed `steps[N]`. Holding `A` and mashing `B` machine-gunned `[A,B]` off one `A` press; an expiry with the first key held spuriously advanced with no edge. | Every entry routes through `enter(k, held, now)` (§5.1), which snapshots and never indexes `steps[N]`. Cases 6 and 7. |
|
|
1490
|
-
| **Occlusion** | A per-edge `consumed` set gating *firing* only. `isActive` and coordinate reads are polled, so `BlockAll` could not stop them — a pause menu left the camera panning, the exact bug §8 claimed to prevent. Both occlusion tests passed anyway, testing only dispatch. | Two scopes: `BlockMatched` stays dynamic and dispatch-only; `BlockAll` sets a static per-map `suppressed` covering dispatch *and* reads (§8.2). Cases 14, 15, 17. |
|
|
1491
|
-
| **Coordinate bindings** | `bindCoordinate()` existed with nowhere to store its result, and §10's channel encoding was unreachable from the format root. No map with a coordinate could serialize. `bindCoordinate` was also called twice for one action in §11 with no defined meaning. | `InputCoordinateBinding` on the layer, reachable in the format, with priority resolution (§9.1). Case 19. |
|
|
1492
|
-
|
|
1493
|
-
### Corrections
|
|
1494
|
-
|
|
1495
|
-
* **Charge inputs failed.** The step window ran from the advance, so any charge longer than
|
|
1496
|
-
`duration + step_timeout` expired before the flick. It now measures time since the previous step *stopped*
|
|
1497
|
-
holding (§5.2). Case 10.
|
|
1498
|
-
* **`satisfied` initialisation was unspecified**, and zero-init makes `Release(Chord(a,b))` fire before the
|
|
1499
|
-
chord exists. Now snapshotted at compile (§4.5). Case 12.
|
|
1500
|
-
* **`order` was `uint8`-biased**, range `[-128,127]`, while §11's own modal used `1000`. Now `int16` (§10).
|
|
1501
|
-
* **`MouseButtons` cited the wrong standard.** The `MouseEvent.buttons` *bitmask* order is left/right/middle;
|
|
1502
|
-
`PointerDevice.buttons[]` is indexed by `MouseEvent.button` *ordinal*. Following the citation would have
|
|
1503
|
-
fired every right-click binding on the middle button, into saved data (§3.1).
|
|
1504
|
-
* **`Release(sequence)` was claimed to mean "stopped matching".** It cannot — a sequence's `test` is a pulse
|
|
1505
|
-
(§4.3).
|
|
1506
|
-
* **Specificity did not rank `Hold` / `Release` / `Direction`**, so they fell through to declaration order —
|
|
1507
|
-
the accident §4.6 claims to eliminate (§4.6).
|
|
1508
|
-
* **Chord-vs-subset arbitration was stated unconditionally.** It holds only when the modifier arrives first
|
|
1509
|
-
(§4.6). Case 3 now tests both orders.
|
|
1510
|
-
* **The `Delta` "same value for every reader" invariant** was a leftover from the buffered design and is false
|
|
1511
|
-
under eager dispatch (§3.3).
|
|
1512
|
-
* **Re-entrancy covered only structural change.** Nested edges and mid-dispatch binding edits are now specified
|
|
1513
|
-
(§2.3).
|
|
1514
|
-
* **The recorder captured only switch edges**, so no map with a coordinate or a hold could replay. It now
|
|
1515
|
-
records coordinate samples and per-frame `dt` (§2.5).
|
|
1516
|
-
* **Unregistered devices had no defined behaviour** (§3.1).
|
|
1517
|
-
* **`EditorKeyMap` and `KeyboardCameraController` deletions dropped real behaviour** — focus gating,
|
|
1518
|
-
autorepeat, `stopPropagation`, the tool forward, the `'user-input'` event — now inventoried (§14.1).
|
|
1519
|
-
* **Counts and claims:** `KeyboardCameraController` has 16 `bind()` calls, not eight; the `InputController`
|
|
1520
|
-
path-string count flattered a migration that is mostly gestures (§1.1); `InputControllerSystem`'s `deferred`
|
|
1521
|
-
array is dead code, not a working mitigation (§2.3); "`InputSystem` cannot be tested without a DOM element"
|
|
1522
|
-
was overstated (§12.1); "no new numbering to keep in sync" was false (§10); `PointerDevice`'s wheel reports
|
|
1523
|
-
`sign()`, so a `Delta` wheel channel counts ticks rather than distance (§3.3).
|
|
1
|
+
# Input System Mapping — design
|
|
2
|
+
|
|
3
|
+
A binding layer for keyboard, pointer buttons and gamepad, replacing `Input`/`InputSystem`, the keyboard half
|
|
4
|
+
of `InputController`/`InputControllerSystem`, `EditorKeyMap` and `Tool.handleKeyboardEvent`.
|
|
5
|
+
|
|
6
|
+
Status: **design, not yet implemented.** The only file in this directory today is
|
|
7
|
+
[`InputBinding.js`](InputBinding.js) (the `{path, event}` record used by
|
|
8
|
+
[`Input`](../components/Input.js)); it is superseded by §4.
|
|
9
|
+
|
|
10
|
+
> **Revision 3.** Rewritten after an adversarial review of revision 2 found three defects that made it
|
|
11
|
+
> unimplementable, and several false claims about the existing codebase. What changed, and why, is in §17.
|
|
12
|
+
> The three that matter: the sequence matcher's reset paths were broken (§5); occlusion could not reach polled
|
|
13
|
+
> reads, so a modal dialog could not stop a camera (§8); and coordinate bindings had no home in either the
|
|
14
|
+
> component or the wire format (§9.1, §10). Two scope decisions were taken at the same time: **pointer
|
|
15
|
+
> gestures are out of scope** (§1.1), and **`SimpleStateMachine` is not used** (§5.6).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 0. Summary
|
|
20
|
+
|
|
21
|
+
Two decisions carry the design.
|
|
22
|
+
|
|
23
|
+
> **1. Every input this system binds is either a switch or a coordinate.** `InputDeviceSwitch` already exists
|
|
24
|
+
> and is already what keyboard keys, pointer buttons and gamepad buttons *are*. A coordinate is an
|
|
25
|
+
> N-dimensional analog channel — stick deflection, wheel ticks, pointer position.
|
|
26
|
+
>
|
|
27
|
+
> **2. Every trigger is a predicate over (held switches + its own state), and a binding fires on that
|
|
28
|
+
> predicate's rising edge** — evaluated the instant an edge arrives, never on a frame boundary.
|
|
29
|
+
|
|
30
|
+
| Requirement | Falls out of |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Combinations (chords) | A chord is the AND of its children's predicates |
|
|
33
|
+
| Sequences | A sequence is a predicate over a small linear matcher, advanced per edge (§5) |
|
|
34
|
+
| Cross-device combos | A chord cannot tell which device a child came from — there is nothing to make work |
|
|
35
|
+
| Arbitrary devices | A device is anything that hands out switches and coordinates (§3) |
|
|
36
|
+
| Layers, top wins | Cascade over an action table, compiled once per change, like CSS (§7) |
|
|
37
|
+
| Serializable bindings | A binding is `(trigger, action name)`; both halves are plain data (§6.1, §10) |
|
|
38
|
+
| No added latency | Evaluation is eager on the edge; the tick services only time (§2) |
|
|
39
|
+
|
|
40
|
+
Two defects in the current systems that this fixes on the way past:
|
|
41
|
+
|
|
42
|
+
1. **Binding resolution can throw.** `resolvePath` throws on an unresolvable path
|
|
43
|
+
([`resolvePathByArray.js:47`](../../../../core/json/resolvePathByArray.js)), which is why a gamepad binding
|
|
44
|
+
is a load-order landmine (see [`GAMEPAD_API_PROPOSAL.md §3`](../../GAMEPAD_API_PROPOSAL.md)). Source
|
|
45
|
+
resolution here is total.
|
|
46
|
+
2. **Nothing can express a combination.** `Ctrl+Z` is not expressible as a path, which is why
|
|
47
|
+
[`EditorKeyMap`](../../../../../editor/EditorKeyMap.js) exists as a parallel, unserializable, unrebindable
|
|
48
|
+
input system made of `if (event.ctrlKey)`.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 1. Scope
|
|
53
|
+
|
|
54
|
+
### 1.1 Pointer gestures are out of scope
|
|
55
|
+
|
|
56
|
+
`PointerDevice` synthesizes gestures on top of its buttons — `tap`, `drag`, `dragStart`, `dragEnd`, `move`,
|
|
57
|
+
`wheel`, `pinch`, `pinchStart`, `pinchEnd` — and those are **not** modelled here. They are stateful
|
|
58
|
+
constructions with their own parameters (`tap` fires on *release*, gated on ≤ 10 px of travel and ≤ 1 s;
|
|
59
|
+
`PointerDevice.js:20-40`), not switches, and folding them into the trigger algebra is a separate piece of work.
|
|
60
|
+
|
|
61
|
+
This is a deliberate limit with a measurable cost. Of the pointer bindings in shipping consumers:
|
|
62
|
+
|
|
63
|
+
| `down`/`up` | `tap` | `drag`/`dragStart`/`dragEnd` | `move` | `wheel` | `pinch`/`pinchStart` |
|
|
64
|
+
|---|---|---|---|---|---|
|
|
65
|
+
| 6 | 4 | 4 | 2 | 2 | 2 |
|
|
66
|
+
|
|
67
|
+
Only the first column is expressible. **Consumers bound to gestures stay on `InputController`**, which is
|
|
68
|
+
therefore *reduced*, not retired, by this program (§14). Chief among them:
|
|
69
|
+
`StrategyInteractionController` (17 bindings, including both pinch bindings),
|
|
70
|
+
`makeOrbitalCameraController` (tap + down + drag + wheel), and `RadialContextMenu`.
|
|
71
|
+
|
|
72
|
+
### 1.2 What is replaced
|
|
73
|
+
|
|
74
|
+
| Mechanism | Disposition | Why |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| [`Input`](../components/Input.js) + [`InputSystem`](../systems/InputSystem.js) | **Retired** (one holdout, §14) | Magic string paths; one source per binding; no combinations; `resolvePath` throws; `serializable = false` |
|
|
77
|
+
| [`EditorKeyMap`](../../../../../editor/EditorKeyMap.js) | **Retired** | Exists only because chords are inexpressible above; hard-coded, unrebindable, undiscoverable |
|
|
78
|
+
| `Tool.handleKeyboardEvent` | **Retired** | An unconditional forward to the active tool, with no gating |
|
|
79
|
+
| [`KeyboardCameraController`](../controllers/KeyboardCameraController.js) | **Retired** | 16 `bind()` calls maintaining a `controls` struct — a 2-D coordinate written by hand (§3.3) |
|
|
80
|
+
| [`InputController`](../components/InputController.js) + [`InputControllerSystem`](../systems/InputControllerSystem.js) | **Reduced** | Keyboard and button bindings move; gesture bindings stay (§1.1) |
|
|
81
|
+
| Direct `engine.devices.*.on.*.add(...)` in gameplay code | **Case by case** | Not bindable, not rebindable, not occludable; but gesture subscriptions have nowhere to go yet |
|
|
82
|
+
|
|
83
|
+
**Not in scope at all:** direct device reads in `prototype*.js` / `test*.js` harnesses. Those are internal
|
|
84
|
+
tools, and polling `pad.buttons[GamepadButtons.a].is_down` remains the blessed path for anything that does not
|
|
85
|
+
want a binding layer. The ISM sits *above* the devices; it does not close them off.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 2. Evaluation: eager on edges, ticked only for time
|
|
90
|
+
|
|
91
|
+
There is **no input buffer and no added frame of latency.** An edge is evaluated and dispatched inside the
|
|
92
|
+
device signal that produced it.
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
InputDeviceSwitch.down / .up (keyboard/pointer: DOM event. gamepad: poll.)
|
|
96
|
+
|
|
|
97
|
+
v
|
|
98
|
+
ISM edge handler --- synchronous, no queue ---------------------------------
|
|
99
|
+
|
|
|
100
|
+
| 1. stamp arrival time
|
|
101
|
+
| 2. update #held : BitSet
|
|
102
|
+
| 3. append to the recorder, if one is attached (§2.4)
|
|
103
|
+
| 4. walk maps by `order` descending, skipping suppressed maps (§8):
|
|
104
|
+
| for each candidate trigger touching this switch,
|
|
105
|
+
| in specificity order (§4.6):
|
|
106
|
+
| was := trigger.satisfied
|
|
107
|
+
| now := trigger.test(#held)
|
|
108
|
+
| trigger.satisfied := now
|
|
109
|
+
| if (now && !was) consume, dispatch
|
|
110
|
+
| 5. apply changes deferred during dispatch (§2.3)
|
|
111
|
+
v
|
|
112
|
+
dataset.sendEvent(entity, action, event) -- consumer runs here, this instant
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`#held` is exact at dispatch time by construction, because dispatch *is* the edge. No replay, no
|
|
116
|
+
reconstruction.
|
|
117
|
+
|
|
118
|
+
### 2.1 The tick services time, and only time
|
|
119
|
+
|
|
120
|
+
`System.update(dt)` dispatches no edge-driven action. It services the four things that change without an edge:
|
|
121
|
+
|
|
122
|
+
1. **`InputTriggerHold` maturity.** A hold for 0.5 s becomes true 0.5 s after its child did, which no edge
|
|
123
|
+
announces. When it matures it emits a **synthetic edge** into the same walk as step 4 above, carrying the
|
|
124
|
+
hold's own `collectSources()` set so consumption (§8) has something to match against. Latency is bounded by
|
|
125
|
+
the frame, which is inherent: nothing observes time more finely than the loop observing time.
|
|
126
|
+
2. **Sequence step windows** (§5.2) — both the refresh while a previous step still holds, and the expiry.
|
|
127
|
+
3. **`Delta` coordinate reset** (§3.3).
|
|
128
|
+
4. **Suppression recompute** when the linked map set, `order` or `occlusion` changed during the frame (§8).
|
|
129
|
+
|
|
130
|
+
A trigger built only from switches and chords is untouched by `update`.
|
|
131
|
+
|
|
132
|
+
### 2.2 Polled devices are bounded by their own poll rate
|
|
133
|
+
|
|
134
|
+
`GamepadDevice` polls once per animation frame and drives its switches from the result
|
|
135
|
+
(`GamepadDevice.js:370-407`), so gamepad edges cannot be finer-grained than that — a Gamepad API property, not
|
|
136
|
+
one this design imposes. `GamepadDevice.poll()` is callable manually, so a consumer driving a fixed-rate loop
|
|
137
|
+
can raise its own resolution. Keyboard and pointer edges are DOM-driven and carry no such floor.
|
|
138
|
+
|
|
139
|
+
### 2.3 The re-entrancy contract
|
|
140
|
+
|
|
141
|
+
Consumer code runs inside a DOM event handler. Four cases, each with a stated rule:
|
|
142
|
+
|
|
143
|
+
* **Structural change** — a handler adds or removes an entity, and therefore an `InputMap`. Queued, applied at
|
|
144
|
+
step 5, after the current edge finishes.
|
|
145
|
+
* **Binding edits on a linked map** — a handler calls `bind()` / `pushLayer()`, which §9.3 says triggers a
|
|
146
|
+
recompile. The recompile is queued with structural changes and applied at step 5; the walk always runs to
|
|
147
|
+
completion against the program it started with. This path is not exotic: it is the rebinding-capture flow.
|
|
148
|
+
* **Nested edges** — a handler calls `GamepadDevice.poll()` (blessed in §2.2) or synchronously dispatches a DOM
|
|
149
|
+
event (`element.click()`, `.focus()`), re-entering the walk. **Rejected**: a re-entrant edge is queued and
|
|
150
|
+
replayed at step 5 rather than processed inline, because processing it inline would mutate `#held` under the
|
|
151
|
+
outer walk and clobber the shared `InputActionEvent` (§6.3) before the outer handler finished reading it.
|
|
152
|
+
* **A handler that throws** — it throws. `#held` and the recorder are updated *before* dispatch (steps 2–3), so
|
|
153
|
+
ISM state stays consistent and the failure is visible, per the codebase's position on not swallowing errors.
|
|
154
|
+
|
|
155
|
+
*(For contrast, `InputControllerSystem` carries a `deferred` array for the first case that **nothing ever
|
|
156
|
+
populates** — `add()` registers directly under a `//TODO handle case where binding is added mid-dispatch`
|
|
157
|
+
(`InputControllerSystem.js:119-121`). The hazard is real and currently unhandled, not handled badly.)*
|
|
158
|
+
|
|
159
|
+
### 2.4 Ordering, and what it costs
|
|
160
|
+
|
|
161
|
+
Action effects land wherever in the frame the edge arrived, so two actions in one frame are ordered by
|
|
162
|
+
arrival, not by system order. That is the direct consequence of wanting zero latency, and §4.6 and §5.5 record
|
|
163
|
+
the two places it is observable.
|
|
164
|
+
|
|
165
|
+
### 2.5 Recording and replay
|
|
166
|
+
|
|
167
|
+
An optional recorder captures, per frame: the frame's `dt`, every switch edge as
|
|
168
|
+
`(source_index, is_down, time)`, and a sample of every **coordinate channel** any linked map references.
|
|
169
|
+
Coordinates must be recorded because they are read by polling, not driven by edges — without them,
|
|
170
|
+
`InputActionEvent.value` and every `axis*` read diverge on replay. `dt` must be recorded because hold maturity
|
|
171
|
+
and window expiry are evaluated on the tick, so a different frame cadence produces different fire times.
|
|
172
|
+
|
|
173
|
+
Replay injects recorded edges at their recorded positions within each recorded frame and drives the same eager
|
|
174
|
+
path. Interned `source_index` values are stable only within one link generation, so the recorder writes the
|
|
175
|
+
source table alongside and remaps on load.
|
|
176
|
+
|
|
177
|
+
The recorder is an observer, not the mechanism — attach it for a determinism run or a bug report, leave it off
|
|
178
|
+
otherwise.
|
|
179
|
+
|
|
180
|
+
### 2.6 `preventDefault`, and its granularity
|
|
181
|
+
|
|
182
|
+
[`KeyboardDevice`](../../devices/KeyboardDevice.js) calls `event.preventDefault()` when the pressed key's
|
|
183
|
+
`down` signal *has handlers* (`KeyboardDevice.js:112-118`). Since the ISM subscribes to precisely the keys some
|
|
184
|
+
live binding references, browser-default suppression tracks the bound key set automatically.
|
|
185
|
+
|
|
186
|
+
**Granularity is per key, not per chord**, and this is worth knowing before it surprises someone: binding only
|
|
187
|
+
`Ctrl+S` subscribes `s`'s `down` signal, so a *bare* `S` keydown is also `preventDefault`ed anywhere the device
|
|
188
|
+
element sees it. This is inherited behaviour, not new — `InputController` binding `keyboard/keys/s/down` does
|
|
189
|
+
the same — but chords make it newly reachable. Changing it means changing `KeyboardDevice`, which is out of
|
|
190
|
+
scope here.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 3. Devices, switches and coordinates
|
|
195
|
+
|
|
196
|
+
### 3.1 The adapter
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
/**
|
|
200
|
+
* Adapts a concrete input device to the ISM.
|
|
201
|
+
*
|
|
202
|
+
* Resolution must be TOTAL: an adapter answers for every code it declares, whether or not hardware is
|
|
203
|
+
* present. Absent hardware reads as "up" and as zero, never as undefined, and never throws. This is the
|
|
204
|
+
* property `resolvePath` lacked.
|
|
205
|
+
*/
|
|
206
|
+
export class InputDeviceAdapter {
|
|
207
|
+
/** Stable identifier, written into serialized bindings. ASCII, lower_snake_case. @type {string} */
|
|
208
|
+
id = "";
|
|
209
|
+
|
|
210
|
+
/** @param {number} code @returns {InputDeviceSwitch} */
|
|
211
|
+
getSwitch(code) { }
|
|
212
|
+
|
|
213
|
+
/** @param {number[]|Float64Array} result @param {number} offset @param {number} code */
|
|
214
|
+
readCoordinate(result, offset, code) { }
|
|
215
|
+
|
|
216
|
+
/** Begin/stop observing the device. Adapters with nothing to subscribe to leave these alone. */
|
|
217
|
+
startup() { }
|
|
218
|
+
shutdown() { }
|
|
219
|
+
|
|
220
|
+
/** Publish this frame's relative-channel totals and start a new window. See the latch rule in §3.3. */
|
|
221
|
+
latchDeltas() { }
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A channel's *dimensionality* is not asked of the adapter. It is a static property of the channel code, known
|
|
226
|
+
to the typed constructors (§3.3), so a binding on an unregistered device still knows its own shape.
|
|
227
|
+
|
|
228
|
+
| Adapter | `id` | Switch codes | Coordinate codes |
|
|
229
|
+
|---|---|---|---|
|
|
230
|
+
| `KeyboardInputDeviceAdapter` | `"keyboard"` | [`KeyCodes`](../../devices/KeyCodes.js) | — |
|
|
231
|
+
| `PointerInputDeviceAdapter` | `"pointer"` | `MouseButtons` | `PointerCoordinates` |
|
|
232
|
+
| `GamepadInputDeviceAdapter` | `"gamepad"` | [`GamepadButtons`](../../devices/gamepad/GamepadButtons.js) | `GamepadCoordinates` |
|
|
233
|
+
|
|
234
|
+
Three enums are new, and each needs a stability note because their values become `uint16` codes on disk:
|
|
235
|
+
|
|
236
|
+
* **`MouseButtons`** — `left: 0, middle: 1, right: 2, back: 3, forward: 4`. These are
|
|
237
|
+
**`MouseEvent.button` ordinals**, matching how `PointerDevice.buttons[]` is indexed
|
|
238
|
+
(`PointerDevice.js:300-318`: `mouseButtonLeft` is `[0]`, `mouseButtonMiddle` is `[1]`, `mouseButtonRight` is
|
|
239
|
+
`[2]`). They are deliberately **not** the `MouseEvent.buttons` *bitmask* order, which is left/right/middle —
|
|
240
|
+
taking that order would index `buttons[1]` for a right-click and fire every right-click binding on the
|
|
241
|
+
middle button, into saved data.
|
|
242
|
+
* **`PointerCoordinates`** — `position`, `move`, `wheel`.
|
|
243
|
+
* **`GamepadCoordinates`** — `left_stick`, `right_stick`, `dpad`.
|
|
244
|
+
|
|
245
|
+
Values are append-only once shipped, like `KeyCodes` and `GamepadButtons` before them.
|
|
246
|
+
|
|
247
|
+
A fourth device — MIDI pedal, wheel, eye tracker, a network-driven replay source — is an adapter and a
|
|
248
|
+
registration call. Nothing in the trigger, layer, cascade or serialization layers changes, because none of them
|
|
249
|
+
can tell devices apart.
|
|
250
|
+
|
|
251
|
+
The gamepad adapter binds `GamepadDevice.main`, the persistent handle: it exists before any hardware does,
|
|
252
|
+
survives disconnection, and rebinds to the oldest connected pad on a swap (`GamepadDevice.js:110, 306-344`). A
|
|
253
|
+
serialized gamepad binding therefore resolves at load time and keeps working across a hot-swap — the defect
|
|
254
|
+
`GAMEPAD_API_PROPOSAL.md §3` identified and could not fix from the device side alone.
|
|
255
|
+
|
|
256
|
+
**A device that is not registered** is not an error. A map naming an unregistered `id` links successfully and
|
|
257
|
+
its bindings on that device are inert — the same contract as connected-but-absent hardware, so a saved map
|
|
258
|
+
never fails to load because a peripheral is missing. `registerDevice()` may be called after maps are linked; it
|
|
259
|
+
recompiles every linked map (§9.3), at which point those bindings become live.
|
|
260
|
+
|
|
261
|
+
### 3.2 Switch sources
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
export class InputSource {
|
|
265
|
+
device = ""; // InputDeviceAdapter#id
|
|
266
|
+
code = 0;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
One flat value type with `equals`/`hash`/`toBinaryBuffer`/`fromBinaryBuffer`, deliberately *not* a
|
|
271
|
+
`KeySource`/`GamepadButtonSource`/… hierarchy: a chord over `{keyboard:ctrl, pointer:0}` needs no polymorphic
|
|
272
|
+
dispatch, serialization is `(device_index: Uint8, code: Uint16)` against a per-map device table (§10) with no
|
|
273
|
+
type tags, and interning to an integer (§9.3) is a two-field hash after which the hot path never touches the
|
|
274
|
+
object again. The typing that matters to an author lives on the *trigger* constructors (§4.2).
|
|
275
|
+
|
|
276
|
+
### 3.3 Coordinates — analog channels of arbitrary dimensionality
|
|
277
|
+
|
|
278
|
+
An analog channel is not a scalar axis. `WheelEvent` carries `deltaX`, `deltaY` and `deltaZ`, and
|
|
279
|
+
[`PointerDevice`](../../devices/PointerDevice.js) surfaces all three (`PointerDevice.js:398-404`). A 1-D axis
|
|
280
|
+
abstraction would have to either drop `deltaZ` or invent three channels for one control.
|
|
281
|
+
|
|
282
|
+
*(Precisely: `PointerDevice` reports `sign(event.deltaX/Y/Z)`, so components are -1, 0 or 1 and scroll
|
|
283
|
+
magnitude is discarded at the device. A `Delta` wheel channel therefore accumulates **tick counts**, not
|
|
284
|
+
distance. That is the device's existing behaviour; the ISM reports what it is given.)*
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
export class InputCoordinateSource {
|
|
288
|
+
device = "";
|
|
289
|
+
code = 0;
|
|
290
|
+
/** Component count. 2 for a stick or pointer position, 3 for a wheel. @type {number} */
|
|
291
|
+
dimensions = 1;
|
|
292
|
+
/** @type {InputCoordinateKind} */
|
|
293
|
+
kind = InputCoordinateKind.Normalized;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export const InputCoordinateKind = {
|
|
297
|
+
/** A position in some space. Read at any time, no accumulation. Pointer position. */
|
|
298
|
+
Absolute: 0,
|
|
299
|
+
/** A change. Accumulates within a frame, zeroed at the frame boundary. Wheel. */
|
|
300
|
+
Delta: 1,
|
|
301
|
+
/** Deflection, each component in [-1, 1]. Stick, d-pad. */
|
|
302
|
+
Normalized: 2
|
|
303
|
+
};
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`kind` is the verb — what the numbers *mean*. `Delta` is the one with behaviour attached, and it is why the
|
|
307
|
+
tick exists at all: two wheel events in one frame must sum.
|
|
308
|
+
|
|
309
|
+
**The `Delta` visibility rule, stated exactly.** A relative channel **latches**; it is not cleared. The
|
|
310
|
+
adapter accumulates arriving events into one buffer, and once per frame `update` publishes that buffer into a
|
|
311
|
+
second one and starts a fresh accumulation window. Readers see the published value.
|
|
312
|
+
|
|
313
|
+
Latching rather than clearing is load-bearing, and the obvious alternative is wrong. Device events arrive
|
|
314
|
+
*between* frames, so an adapter that zeroed its accumulator at the top of a frame would destroy exactly the
|
|
315
|
+
events that frame exists to report. Latching also makes the value **independent of system execution order**:
|
|
316
|
+
every reader in a frame sees the same number whether it runs before or after the ISM, which is what the
|
|
317
|
+
"same value for every reader" guarantee actually requires.
|
|
318
|
+
|
|
319
|
+
An **eager handler** reading a `Delta` channel mid-frame sees the value published for the frame in progress —
|
|
320
|
+
the same one the tick-time readers will see — not a running total of events that have arrived since. Those
|
|
321
|
+
belong to the next frame.
|
|
322
|
+
|
|
323
|
+
**Reading.** The general accessor writes into an indexable numeric buffer; common dimensionalities get typed
|
|
324
|
+
conveniences over the engine's vector types:
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
map.coordinate(result, offset, 'look'); // Float64Array | number[] - any dimensionality
|
|
328
|
+
map.axis1(v1, 'throttle'); // Vector1
|
|
329
|
+
map.axis2(v2, 'move'); // Vector2
|
|
330
|
+
map.axis3(v3, 'wheel'); // Vector3
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Result-first argument order follows the engine's `f(result, ...)` convention.
|
|
334
|
+
|
|
335
|
+
**Channels.** A coordinate action is driven by a channel, and the consumer cannot tell which kind:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
InputCoordinateGamepad.from(GamepadCoordinates.left_stick) // hardware channel
|
|
339
|
+
InputCoordinatePointer.from(PointerCoordinates.wheel) // hardware channel, 3-D
|
|
340
|
+
InputCoordinateSwitches.from([ // N-D from 2N switch triggers
|
|
341
|
+
InputTriggerKey.from('a'), InputTriggerKey.from('d'), // x: negative, positive
|
|
342
|
+
InputTriggerKey.from('w'), InputTriggerKey.from('s') // y: negative, positive
|
|
343
|
+
])
|
|
344
|
+
InputCoordinateCompose.from(x_channel, y_channel) // stitch channels
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`InputCoordinateSwitches` is [`KeyboardCameraController`](../controllers/KeyboardCameraController.js) as data,
|
|
348
|
+
and it generalises past 2-D for free.
|
|
349
|
+
|
|
350
|
+
**Shaping stays at the call site.** No dead zone, no response curve, no sensitivity inside the ISM. It returns
|
|
351
|
+
the composed raw value and the consumer calls
|
|
352
|
+
[`apply_radial_dead_zone`](../../analog/apply_radial_dead_zone.js). This is the position
|
|
353
|
+
`GAMEPAD_API_PROPOSAL.md §1.2` argues at length; reversing it here would put the same policy back in the same
|
|
354
|
+
wrong layer, one floor up.
|
|
355
|
+
|
|
356
|
+
### 3.4 Derived switches: a direction is a coordinate with a threshold
|
|
357
|
+
|
|
358
|
+
`InputTriggerDirection` turns a coordinate into a switch — "the stick is in the *down-right* octant". This is
|
|
359
|
+
what makes motion inputs work (§5.4), and it is not bolted on for them: a d-pad is already a coordinate in this
|
|
360
|
+
codebase (`gamepad_write_dpad_vector`), so one path serves stick and d-pad.
|
|
361
|
+
|
|
362
|
+
The eight directional switches derived from one coordinate are **mutually exclusive by construction**, and the
|
|
363
|
+
derived layer **releases the old direction before pressing the new one**, so exclusivity is never momentarily
|
|
364
|
+
violated. §5.4 depends on both.
|
|
365
|
+
|
|
366
|
+
Three properties an implementer must know, because none is free:
|
|
367
|
+
|
|
368
|
+
* **Threshold.** Producing a boolean from a coordinate requires one by definition. This is not an exception to
|
|
369
|
+
§3.3, which forbids shaping the *analog value* handed to a consumer. Constructor argument, defaulted.
|
|
370
|
+
* **Sampling.** Octant transitions are observed only as often as the channel updates. On a gamepad that is the
|
|
371
|
+
rAF poll (§2.2), so a fast quarter-circle can pass from `Down` to `Right` between two polls without ever
|
|
372
|
+
reporting `DownRight`, and a motion requiring that step silently fails. Sequences over directions are
|
|
373
|
+
reliable at hand speed, not at frame-perfect speed.
|
|
374
|
+
* **Hysteresis.** A stick resting on an octant boundary chatters release/press pairs. The threshold carries an
|
|
375
|
+
angular hysteresis band, defaulted, for the same reason it carries a magnitude threshold: without it a
|
|
376
|
+
boundary rest position resets hold timers continuously.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 4. Triggers
|
|
381
|
+
|
|
382
|
+
### 4.1 One interface, two methods and one field
|
|
383
|
+
|
|
384
|
+
```js
|
|
385
|
+
export class InputTrigger {
|
|
386
|
+
/**
|
|
387
|
+
* Is this trigger's condition currently met?
|
|
388
|
+
* Pure for leaves and combinators. `InputTriggerHold` and `InputTriggerSequence` additionally consult
|
|
389
|
+
* per-instance state, which edges and ticks maintain.
|
|
390
|
+
*
|
|
391
|
+
* @param {BitSet} held interned switch indices currently down
|
|
392
|
+
* @returns {boolean}
|
|
393
|
+
*/
|
|
394
|
+
test(held) { }
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Every switch index this trigger can be affected by. Collected once, at compile time (§9.3).
|
|
398
|
+
* @param {number[]} result
|
|
399
|
+
*/
|
|
400
|
+
collectSources(result) { }
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Last observed value of `test`, per compiled instance. A binding fires when `test` is true and this
|
|
404
|
+
* is false. Initialised at compile time to `test(held-at-compile)` - NOT to false. See §4.5.
|
|
405
|
+
* @type {boolean}
|
|
406
|
+
*/
|
|
407
|
+
satisfied = false;
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**A binding fires on the rising edge of `test`.** That single rule covers press, release, hold, chord, sequence
|
|
412
|
+
and direction, and it is why there is no `Started`/`Performed`/`Canceled` phase enum and no `edge` field.
|
|
413
|
+
|
|
414
|
+
### 4.2 Leaf triggers — typed constructors
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
InputTriggerKey.from('space')
|
|
418
|
+
InputTriggerKey.from(KeyCodes.space)
|
|
419
|
+
InputTriggerMouseButton.from(MouseButtons.left)
|
|
420
|
+
InputTriggerGamepadButton.from(GamepadButtons.a)
|
|
421
|
+
InputTriggerDirection.from(stick, InputDirection.DownRight) // stick: any InputCoordinate* channel
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Each extends `InputTriggerSwitch`, which holds one `InputSource` and whose `test` is `held.get(index)`. The
|
|
425
|
+
subclasses add no fields — they are typed, validating constructors that fix the device and check the code
|
|
426
|
+
against that device's enum. `InputTriggerKey.from('nonexistent')` fails at authoring time under `assert.*`,
|
|
427
|
+
which is compiled out of production, so the typing is free in shipped code.
|
|
428
|
+
|
|
429
|
+
Serialization round-trips the flat form, so a deserialized trigger is an `InputTriggerSwitch`, not an
|
|
430
|
+
`InputTriggerKey`. Nothing needs the subclass identity at runtime — it exists for the author and the assertion.
|
|
431
|
+
|
|
432
|
+
### 4.3 Combinators
|
|
433
|
+
|
|
434
|
+
```js
|
|
435
|
+
InputTriggerChord.from(a, b, c) // test = AND over children
|
|
436
|
+
InputTriggerRelease.from(a) // test = NOT child.test
|
|
437
|
+
InputTriggerHold.from(a, 0.5) // test = child.test AND continuously true for >= 0.5s
|
|
438
|
+
InputTriggerSequence.from(a, b, c) // test = the matcher completed on this edge (§5)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Combinators take *triggers*, not sources, which is why the requirements compose:
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
// "the chord ended"
|
|
445
|
+
InputTriggerRelease.from(InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
446
|
+
InputTriggerKey.from('z')))
|
|
447
|
+
|
|
448
|
+
// "hold Ctrl+Shift for half a second"
|
|
449
|
+
InputTriggerHold.from(InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
450
|
+
InputTriggerKey.from('shift')), 0.5)
|
|
451
|
+
|
|
452
|
+
// cross-device: nothing here knows a device is involved
|
|
453
|
+
InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
454
|
+
InputTriggerMouseButton.from(MouseButtons.left))
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
`InputTriggerRelease` as *negation* rather than an edge kind is what makes it compose — an edge flag on a chord
|
|
458
|
+
could not express "when this chord stops holding".
|
|
459
|
+
|
|
460
|
+
**One composition that does not mean what it looks like:** `InputTriggerRelease.from(sequence)`. A sequence's
|
|
461
|
+
`test` is a single-edge pulse (§5.2 step 5), so negating it yields "true except on the completing edge", which
|
|
462
|
+
rises on the *next* evaluated edge after every completion — an echo of the fire, not "the sequence stopped
|
|
463
|
+
matching". A sequence's partial-match state is deliberately not exposed to `test`. Do not wrap a sequence in
|
|
464
|
+
`Release`.
|
|
465
|
+
|
|
466
|
+
### 4.4 Step timeout lives on the sequence
|
|
467
|
+
|
|
468
|
+
```js
|
|
469
|
+
InputTriggerSequence.from(a, b, c).withStepTimeout(0.25)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Default **1.0 s**, which suits editor and menu sequences whose first step is a chord the author must release
|
|
473
|
+
before reaching for the next key. Fighting-game motions want 0.15–0.25 s. §5.2 defines exactly what the window
|
|
474
|
+
measures, which is not what a naive reading suggests.
|
|
475
|
+
|
|
476
|
+
Timing was specified here as **real time**, stamped at edge arrival, on the argument that a paused game must
|
|
477
|
+
not give a chord an infinite window and a game at 0.25× speed must not quarter the player's reflexes.
|
|
478
|
+
|
|
479
|
+
**Superseded.** As built it is *simulation* time, advanced by `update(dt)` and nowhere else. The reason is
|
|
480
|
+
testability: a real-time clock cannot be driven from a spec without faking one, and faking it is a mock. The
|
|
481
|
+
consequences are real and accepted — windows stretch under slow motion and freeze while the game is paused,
|
|
482
|
+
so a hold inside a pause menu can never mature. See the implementation plan's amendments section for the full
|
|
483
|
+
argument.
|
|
484
|
+
|
|
485
|
+
### 4.5 `satisfied` is initialised from state, not from false
|
|
486
|
+
|
|
487
|
+
At compile time each trigger instance sets `satisfied := test(held)` against the currently held switches. It is
|
|
488
|
+
**not** zero-initialised, and the difference is not academic:
|
|
489
|
+
`InputTriggerRelease.from(InputTriggerChord.from(ctrl, z))` has `test` true whenever the chord is *not* held —
|
|
490
|
+
which is almost always. Zero-initialised, the first edge touching `ctrl` or `z` would see `now=true, was=false`
|
|
491
|
+
and fire "the chord ended" before the chord ever existed.
|
|
492
|
+
|
|
493
|
+
This matters on every compile, not just the first: maps link mid-dispatch (§2.3), and `registerDevice` and
|
|
494
|
+
binding edits both recompile live maps while keys are held.
|
|
495
|
+
|
|
496
|
+
### 4.6 Precedence within a layer
|
|
497
|
+
|
|
498
|
+
Candidate triggers on one edge are ordered, and the order is total so it never depends on declaration accident:
|
|
499
|
+
|
|
500
|
+
1. **Sequences first**, longer before shorter (by step count).
|
|
501
|
+
2. Then by **chord width** — the number of switches that must be simultaneously held. `Hold` and `Release`
|
|
502
|
+
rank by their child's width, so `Hold(Chord(a,b), t)` ranks as width 2 and beats `Key(a)`; `Direction` ranks
|
|
503
|
+
as width 1.
|
|
504
|
+
3. Then declaration order within the layer.
|
|
505
|
+
|
|
506
|
+
A fired trigger consumes its switches for the remainder of the current edge (§8), so `Ctrl+Z` firing means `Z`
|
|
507
|
+
does not.
|
|
508
|
+
|
|
509
|
+
**The condition on that guarantee.** Consumption is scoped to one edge, so it arbitrates only triggers that
|
|
510
|
+
complete on the *same* edge. `Ctrl` pressed before `Z` gives one edge (`z↓`) at which both `Ctrl+Z` and `Z` are
|
|
511
|
+
satisfiable, and the chord wins. `Z` pressed **before** `Ctrl` gives two edges: `z↓` fires `Z`'s action (the
|
|
512
|
+
chord is not yet satisfiable, so there is nothing to lose to), then `ctrl↓` fires `undo`. **Both fire.**
|
|
513
|
+
|
|
514
|
+
This is inherent to zero-latency evaluation — suppressing the first would mean either delaying it or retracting
|
|
515
|
+
a dispatched action, and both are excluded. Practically it is rarely reached, because modifier-first is how
|
|
516
|
+
people type chords. Where it matters, do not bind a bare key in the same layer as a chord that contains it.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## 5. Sequence semantics
|
|
521
|
+
|
|
522
|
+
### 5.1 State
|
|
523
|
+
|
|
524
|
+
Per sequence binding instance, three numbers, held on the **compiled clone** of the trigger (§9.3):
|
|
525
|
+
|
|
526
|
+
```js
|
|
527
|
+
#step // steps matched so far, 0..N
|
|
528
|
+
#stepSatisfied // last observed value of steps[#step].test
|
|
529
|
+
#window // time the current step's window last refreshed
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
*(An earlier revision put these in typed arrays on the program, indexed by sequence-binding index. Cloning per
|
|
533
|
+
binding is what the compiler does anyway — authored triggers are shared, so evaluation state cannot live on
|
|
534
|
+
them — and once each binding owns a private trigger object, three fields on it are the same thing with less
|
|
535
|
+
bookkeeping. Nothing allocates per evaluation either way.)*
|
|
536
|
+
|
|
537
|
+
Entering a state is one operation, and **every** path into a state uses it. That is the whole of §17's most
|
|
538
|
+
important correction:
|
|
539
|
+
|
|
540
|
+
```js
|
|
541
|
+
function enter(k, held, now) {
|
|
542
|
+
step = k;
|
|
543
|
+
satisfied = (k < N) ? steps[k].test(held) : false; // the entry snapshot
|
|
544
|
+
window = now;
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### 5.2 The rule
|
|
549
|
+
|
|
550
|
+
Evaluated at each edge (device or synthetic) and each tick:
|
|
551
|
+
|
|
552
|
+
```
|
|
553
|
+
1. if (step > 0) {
|
|
554
|
+
if (steps[step - 1].test(held)) window := now // previous step still holds
|
|
555
|
+
else if (now - window > step_timeout) enter(0, held, now) // window expired
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
2. cur := steps[step].test(held)
|
|
559
|
+
|
|
560
|
+
3. if (cur && !satisfied) {
|
|
561
|
+
enter(step + 1, held, now)
|
|
562
|
+
if (step === N) {
|
|
563
|
+
fire() // the sequence's own `test` reads true for this edge
|
|
564
|
+
enter(0, held, now)
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
satisfied := cur
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Three properties do all the work:
|
|
572
|
+
|
|
573
|
+
* **Advance on a rising edge, not on satisfaction.** Otherwise one long press of `A` satisfies both steps
|
|
574
|
+
of `[A, A]`.
|
|
575
|
+
* **The entry snapshot, on every entry.** Advance, expiry and post-fire wrap all route through `enter`, so a
|
|
576
|
+
reset can never leave a stale `satisfied` behind. `enter` never indexes `steps[N]`.
|
|
577
|
+
* **The window measures time since the previous step stopped holding**, not time since the advance. Step 1
|
|
578
|
+
refreshes `window` for as long as `steps[step-1]` is still satisfied, which is what makes charge inputs work
|
|
579
|
+
(§5.4) and is why the tick must service sequences even when no edge arrives.
|
|
580
|
+
|
|
581
|
+
**A mismatch does not reset. Only window expiry resets.** A sequence is a leniency window, and the window is
|
|
582
|
+
what §5.2 step 1 defines.
|
|
583
|
+
|
|
584
|
+
### 5.3 Worked: `[A, A, B]` on `A↓ A↑ A↓ A↑ A↓ B↓`
|
|
585
|
+
|
|
586
|
+
| edge | held | step | step-1 holds? | `cur` | `satisfied` | result |
|
|
587
|
+
|---|---|---|---|---|---|---|
|
|
588
|
+
| — | {} | 0 | — | — | false (compile snapshot) | |
|
|
589
|
+
| `A↓` | {A} | 0 | — | true | false | **rise** → `enter(1)`, `satisfied := A.test({A}) = true` |
|
|
590
|
+
| `A↑` | {} | 1 | no | false | true | `satisfied := false` |
|
|
591
|
+
| `A↓` | {A} | 1 | yes → refresh | true | false | **rise** → `enter(2)`, `satisfied := B.test({A}) = false` |
|
|
592
|
+
| `A↑` | {} | 2 | no | false | false | — |
|
|
593
|
+
| `A↓` | {A} | 2 | yes → refresh | false | false | — |
|
|
594
|
+
| `B↓` | {A,B} | 2 | yes → refresh | true | false | **rise** → `enter(3)` = N → **fire**, `enter(0)` |
|
|
595
|
+
|
|
596
|
+
No failure function, no KMP: the entry snapshot alone prevents one press from satisfying two steps.
|
|
597
|
+
|
|
598
|
+
**Post-fire, the case revision 2 got wrong.** `enter(0, {A,B}, now)` snapshots `satisfied := A.test({A,B}) =
|
|
599
|
+
true`. So with `A` still held, tapping `B` again gives `cur = true, satisfied = true` — no rise, no fire.
|
|
600
|
+
Revision 2 left `satisfied` untouched on the post-fire reset, and holding `A` while mashing `B` machine-gunned
|
|
601
|
+
the action off a single `A` press.
|
|
602
|
+
|
|
603
|
+
### 5.4 Worked: chords, motions and charge
|
|
604
|
+
|
|
605
|
+
**`Ctrl+Shift+A > B`** — steps are triggers, and a chord is a trigger, so this is not a special case:
|
|
606
|
+
|
|
607
|
+
```js
|
|
608
|
+
map.bind('promote', InputTriggerSequence.from(
|
|
609
|
+
InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
610
|
+
InputTriggerKey.from('shift'),
|
|
611
|
+
InputTriggerKey.from('a')),
|
|
612
|
+
InputTriggerKey.from('b')
|
|
613
|
+
));
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
`ctrl↓`, `shift↓`: chord false. `a↓`: chord rises → `enter(1)`, `satisfied := b.test({ctrl,shift,a}) = false`.
|
|
617
|
+
Then `b↓` fires — **whether or not the modifiers were released first**, because leaf and chord tests are
|
|
618
|
+
*subset* tests over the held set, and because holding the chord keeps refreshing the window rather than
|
|
619
|
+
starting its clock.
|
|
620
|
+
|
|
621
|
+
**Quarter-circle forward, `↓ ↘ →`** — this is the case that only works because directions are derived from a
|
|
622
|
+
coordinate (§3.4) rather than read off raw d-pad buttons:
|
|
623
|
+
|
|
624
|
+
```js
|
|
625
|
+
const stick = InputCoordinateGamepad.from(GamepadCoordinates.left_stick);
|
|
626
|
+
|
|
627
|
+
map.bind('hadouken', InputTriggerSequence.from(
|
|
628
|
+
InputTriggerDirection.from(stick, InputDirection.Down),
|
|
629
|
+
InputTriggerDirection.from(stick, InputDirection.DownRight),
|
|
630
|
+
InputTriggerDirection.from(stick, InputDirection.Right)
|
|
631
|
+
).withStepTimeout(0.2));
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
| edge | held | step | `cur` | result |
|
|
635
|
+
|---|---|---|---|---|
|
|
636
|
+
| `Down↓` | {Down} | 0 | true | **rise** → `enter(1)`, `satisfied := DownRight.test({Down}) = false` |
|
|
637
|
+
| `Down↑` | {} | 1 | false | — |
|
|
638
|
+
| `DownRight↓` | {DownRight} | 1 | true | **rise** → `enter(2)`, `satisfied := Right.test({DownRight}) = false` |
|
|
639
|
+
| `DownRight↑` | {} | 2 | false | — |
|
|
640
|
+
| `Right↓` | {Right} | 2 | true | **rise** → `enter(3)` = N → **fire** |
|
|
641
|
+
|
|
642
|
+
The two snapshots in the result column are the trick. With raw d-pad buttons `↓` and `→` can be held together,
|
|
643
|
+
`Right.test({Down,Right})` is `true` on entry to state 2, and the motion can never complete. Exclusivity by
|
|
644
|
+
construction is what removes that failure mode — which is why §12.2 tests it in both directions.
|
|
645
|
+
|
|
646
|
+
**Dragon punch, `→ ↓ ↘`** — the same trace with steps permuted; each entry snapshot is false for the same
|
|
647
|
+
reason. Verified also against the realistic rolled input `Right, DownRight, Down, DownRight`, where the
|
|
648
|
+
intermediate `DownRight↓` at step 1 is a non-advancing mismatch (ignored, correctly) and the final one fires.
|
|
649
|
+
|
|
650
|
+
**Charge, `hold ← for 2 s, then →`** — the case revision 2 published as passing and which in fact failed:
|
|
651
|
+
|
|
652
|
+
```js
|
|
653
|
+
InputTriggerSequence.from(
|
|
654
|
+
InputTriggerHold.from(InputTriggerDirection.from(stick, InputDirection.Left), 2.0),
|
|
655
|
+
InputTriggerDirection.from(stick, InputDirection.Right)
|
|
656
|
+
)
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
At t=2.0 the hold matures, emits a synthetic edge (§2.1) and rises → `enter(1)`. The player then keeps holding
|
|
660
|
+
`←`, which is what charge play *is*. Under revision 2 the window ran from the advance, so any charge longer
|
|
661
|
+
than `duration + step_timeout` expired before the flick and the move was silently lost; raising the timeout
|
|
662
|
+
instead produced ghost charges minutes later. Under §5.2 step 1, `steps[0].test` — the hold — is still true
|
|
663
|
+
while `←` is held, so `window` is refreshed on every tick. Release `←` at t=8.0 and the window opens *there*;
|
|
664
|
+
`→` within `step_timeout` fires. Return to neutral instead and the window expires normally, so there is no
|
|
665
|
+
ghost.
|
|
666
|
+
|
|
667
|
+
**Motion plus button, `236 + P`** — append `InputTriggerGamepadButton.from(GamepadButtons.x)` as a fourth step.
|
|
668
|
+
While `→` is held, step 1 keeps refreshing the window, so the button may be pressed at leisure; it fires with
|
|
669
|
+
`→` still held because button steps are subset tests.
|
|
670
|
+
|
|
671
|
+
### 5.5 What this deliberately cannot do
|
|
672
|
+
|
|
673
|
+
**Strict rejection.** `↓ ← →` inside the window fires the quarter-circle, because `←` neither advances nor
|
|
674
|
+
resets. Arcade-grade matchers reject a direction outside the expected set. The shape of the fix is known — an
|
|
675
|
+
optional rejection predicate on the sequence, resetting to 0 when it matches — and it is **deferred until a
|
|
676
|
+
consumer asks**, on the position that the common case should be the only case. For every sequence in this
|
|
677
|
+
codebase, and for menu, cheat-code and editor cases, leniency is the wanted behaviour.
|
|
678
|
+
|
|
679
|
+
**Button buffering.** A button pressed *before* the motion step it follows poisons that step's entry snapshot
|
|
680
|
+
and must be re-pressed. Genre-standard buffering is a fighting-game engine feature, not an input-mapping one.
|
|
681
|
+
|
|
682
|
+
**Tap versus hold on the same switch — accepted as a cost of zero latency.** Given
|
|
683
|
+
`'interact' = Key(e)` and `'loot_all' = Hold(Key(e), 0.5)`, a press-and-hold fires **both**: `'interact'` at
|
|
684
|
+
`e↓`, `'loot_all'` 0.5 s later. They complete on different edges, so §4.6's per-edge consumption cannot
|
|
685
|
+
arbitrate them, and no composition inside the algebra separates them either —
|
|
686
|
+
`Chord(Release(e), Release(Hold(e, 0.5)))` collapses, because `hold.test` falls to false on the same `e↑` edge
|
|
687
|
+
the release rises on.
|
|
688
|
+
|
|
689
|
+
Discriminating them requires *withholding* the short action until the hold window has passed, which is
|
|
690
|
+
precisely the frame-buffered dispatch this design exists to avoid. Paying that architectural cost — a second,
|
|
691
|
+
deferred dispatch path with its own ordering and occlusion semantics — for one interaction pattern is not
|
|
692
|
+
worth it. **Tap-and-hold pairs must use distinct inputs** (`E` and `Shift+E`), or accept that the hold is
|
|
693
|
+
additive to the tap. Revision 2 claimed §4.6 arbitrated this; it does not.
|
|
694
|
+
|
|
695
|
+
**Key autorepeat.** `KeyboardDevice` discards `event.repeat` (`KeyboardDevice.js:93-97`) and
|
|
696
|
+
`InputDeviceSwitch.press()` is idempotent, so a held key produces exactly one edge. Autorepeat is not
|
|
697
|
+
observable anywhere in this design, and a consumer that wants repeat behaviour — held-key menu navigation,
|
|
698
|
+
`EditorKeyMap`'s current repeat-undo — must drive it from `isActive` plus its own timer. Changing this means
|
|
699
|
+
changing `KeyboardDevice`, which is out of scope.
|
|
700
|
+
|
|
701
|
+
### 5.6 Why `SimpleStateMachine` is not used
|
|
702
|
+
|
|
703
|
+
The first revision built this on
|
|
704
|
+
[`SimpleStateMachine`](../../../../core/fsm/simple/SimpleStateMachine.js) and
|
|
705
|
+
[`SimpleStateMachineDescription`](../../../../core/fsm/simple/SimpleStateMachineDescription.js). It does not
|
|
706
|
+
earn its place here, and the reasons are specific rather than a matter of taste:
|
|
707
|
+
|
|
708
|
+
* **The graph is a path.** States `0..N` with edges `i → i+1` plus resets to `0`. There is nothing to search,
|
|
709
|
+
so `findPath` — the description's real capability — is dead weight.
|
|
710
|
+
* **The self-transition is not declarable.** "Stay in the current state on a mismatch" is the common case, and
|
|
711
|
+
the edge set never contains `i → i`. It works only because the edge-existence assertion in `advance()` is
|
|
712
|
+
commented out (`SimpleStateMachine.js:238`) — a latent trap if those assertions are ever restored.
|
|
713
|
+
* **Initial state is `-1`** (`SimpleStateMachine.js:16`), so every instance needs an explicit `setState(0)`.
|
|
714
|
+
* **It is not the cheap object it looks like.** Each instance carries a description reference and two handler
|
|
715
|
+
arrays (`SimpleStateMachine.js:22-30`) that this design never registers into — while `satisfied` and `window`
|
|
716
|
+
have to live outside it regardless. Three typed-array slots replace all of it with no per-instance object.
|
|
717
|
+
|
|
718
|
+
The requirement was to make good use of existing meep constructs. The honest reading is that this construct
|
|
719
|
+
does not fit this problem: the matcher is a path, not a graph. **`SimpleStateMachineDescription` becomes the
|
|
720
|
+
right tool the moment sequences branch** — alternate motions sharing a prefix, resolved by longest match —
|
|
721
|
+
and that is the point at which to adopt it, not before.
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## 6. Actions — the bindings/behaviour seam
|
|
726
|
+
|
|
727
|
+
### 6.1 The seam
|
|
728
|
+
|
|
729
|
+
```
|
|
730
|
+
DATA (serializable, no code) | CODE (never serialized)
|
|
731
|
+
----------------------------- | ----------------------
|
|
732
|
+
trigger ----> action name | action name ----> behaviour
|
|
733
|
+
Chord(ctrl, z) "undo" | "undo" ----> editor.actions.undo()
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
The left side is an `InputMap` component. The right side is an ECS event listener. They meet at a string, and
|
|
737
|
+
nothing else crosses. This is the same seam [`InputSystem`](../systems/InputSystem.js) already uses
|
|
738
|
+
(`InputSystem.js:79`), which is why that component is the one worth keeping the shape of. It is explicitly
|
|
739
|
+
*not* a string-keyed factory registry: no behaviour is constructed from the string and no class is looked up
|
|
740
|
+
by it.
|
|
741
|
+
|
|
742
|
+
### 6.2 One event, one level, one coordinate
|
|
743
|
+
|
|
744
|
+
```js
|
|
745
|
+
// discrete — fires on the trigger's rising edge, this instant, no frame delay
|
|
746
|
+
ecd.addEntityEventListener(entity, 'undo', () => editor.actions.undo());
|
|
747
|
+
|
|
748
|
+
// continuous — no listener, no mirrored boolean, no teardown handler
|
|
749
|
+
if (map.isActive('sprint')) { ... }
|
|
750
|
+
map.axis2(v, 'move');
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
Three accessors for three *questions* — "did it happen", "is it happening", "how much" — not three ways to ask
|
|
754
|
+
one question. The level accessor is what deletes
|
|
755
|
+
[`KeyboardCameraController`](../controllers/KeyboardCameraController.js)'s `controls` struct, the 16 `bind()`
|
|
756
|
+
calls maintaining it, and the `clearControls` handler wired to `on.unlinked` that exists solely to stop the
|
|
757
|
+
camera drifting after teardown. The same boilerplate is duplicated almost verbatim in
|
|
758
|
+
[`prototype_first_person_controller.js`](../../../control/first-person/prototype_first_person_controller.js).
|
|
759
|
+
|
|
760
|
+
Two things `isActive` is not: it is effectively always `false` for a sequence action, whose `test` is a
|
|
761
|
+
single-edge pulse; and it returns `false` for every action on a **suppressed** map (§8).
|
|
762
|
+
|
|
763
|
+
*(One behaviour `KeyboardCameraController` has that the coordinate does not reproduce: it sends a `'user-input'`
|
|
764
|
+
entity event on every pan keydown (`KeyboardCameraController.js:54`), consumed elsewhere as an interaction
|
|
765
|
+
signal. Porting it is a `map.bind('user-input', ...)` alongside the coordinate, and §14 lists it rather than
|
|
766
|
+
leaving it to be discovered missing.)*
|
|
767
|
+
|
|
768
|
+
### 6.3 The event payload
|
|
769
|
+
|
|
770
|
+
```js
|
|
771
|
+
export class InputActionEvent {
|
|
772
|
+
action = "";
|
|
773
|
+
/** Driving coordinate, or the pointer position, at the moment the trigger fired. @type {Vector3} */
|
|
774
|
+
value = new Vector3();
|
|
775
|
+
/** The switch whose edge completed the trigger. For a synthetic hold-maturity edge, the first
|
|
776
|
+
* switch in the hold's own source set. @type {InputSource} */
|
|
777
|
+
source = new InputSource();
|
|
778
|
+
/** Arrival timestamp of the completing edge, real time, seconds since ISM start. @type {number} */
|
|
779
|
+
time = 0;
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
One instance, reused across dispatches, mutated in place. Dispatch is synchronous, so a handler that reads it
|
|
784
|
+
inline is correct and one that retains it is not — stated in the JSDoc, as scratch vectors are elsewhere in
|
|
785
|
+
the engine. A nested edge cannot clobber it mid-read, because nested edges are queued rather than processed
|
|
786
|
+
inline (§2.3).
|
|
787
|
+
|
|
788
|
+
**It carries no DOM information**, and that is a real limit worth naming: consumers that today gate on
|
|
789
|
+
`event.target === graphics.domElement` to tell a canvas click from a click on overlaying UI —
|
|
790
|
+
`CombatInteractionStateMachine` does — have no equivalent here. UI-overlay occlusion is a DOM-tree question
|
|
791
|
+
that §8's map occlusion cannot see. Those consumers are in the gesture set that stays on `InputController`
|
|
792
|
+
(§1.1) for now; a DOM-aware gate is part of the deferred gesture work.
|
|
793
|
+
|
|
794
|
+
---
|
|
795
|
+
|
|
796
|
+
## 7. Layers — a stack, addressed by index
|
|
797
|
+
|
|
798
|
+
Layers solve **rebinding**: defaults, then the player's overrides, then a context-specific tweak — one
|
|
799
|
+
conceptual keymap assembled from stacked sources, top wins. They are a stack, addressed by index.
|
|
800
|
+
|
|
801
|
+
```js
|
|
802
|
+
const map = new InputMap();
|
|
803
|
+
|
|
804
|
+
map.bind('jump', InputTriggerKey.from('space')); // layer 0, the base
|
|
805
|
+
map.bind('undo', InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
806
|
+
InputTriggerKey.from('z')));
|
|
807
|
+
|
|
808
|
+
const user = map.pushLayer(); // index 1
|
|
809
|
+
user.bind('jump', InputTriggerGamepadButton.from(GamepadButtons.a)); // REPLACES space
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
```js
|
|
813
|
+
map.layer(i) // InputLayer at index i, asserts existence
|
|
814
|
+
map.pushLayer() // append, return the new top
|
|
815
|
+
map.popLayer() // discard the top
|
|
816
|
+
map.bind(...) // sugar for map.layer(0).bind(...)
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Rules, and they are CSS's:
|
|
820
|
+
|
|
821
|
+
* Higher index is higher in the cascade.
|
|
822
|
+
* The cascade is **per action name**, and it applies identically to trigger bindings and coordinate bindings
|
|
823
|
+
(§9.1). The highest layer binding an action supplies *all* of that action's triggers or channels; lower
|
|
824
|
+
layers' entries for it are inert.
|
|
825
|
+
* To keep both, bind both **in one layer** — a binding maps one action to a *list*, exactly as
|
|
826
|
+
`font-family: a, b` lists fallbacks in one declaration:
|
|
827
|
+
|
|
828
|
+
```js
|
|
829
|
+
map.bind('jump', InputTriggerKey.from('space'), InputTriggerGamepadButton.from(GamepadButtons.a));
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
Replace-versus-add is a property of *where you write it*, not a flag on `bind`. That is what lets rebinding
|
|
833
|
+
("the user's jump wins") and multi-device ("either device jumps") both be expressible with no mode switch.
|
|
834
|
+
|
|
835
|
+
* An action named in the top layer that binds it, with nothing bound, is **unbound** — how a keymap layer
|
|
836
|
+
removes an inherited binding:
|
|
837
|
+
|
|
838
|
+
```js
|
|
839
|
+
map.layer(1).unbind('undo');
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
* An action name is either a trigger action or a coordinate action, never both — asserted at authoring time,
|
|
843
|
+
because `isActive('move')` and `axis2(v, 'move')` would otherwise disagree about what `'move'` is.
|
|
844
|
+
|
|
845
|
+
Layers carry no names. There is nothing to look up, so nothing to mistype, and serialization is one `uint16`
|
|
846
|
+
count with no strings (§10).
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## 8. Occlusion — arbitration across entities
|
|
851
|
+
|
|
852
|
+
Layers are within one map. Modality is across maps, and it is a different question: the cascade is *nominal*
|
|
853
|
+
(over action names), occlusion is *physical* (over switches).
|
|
854
|
+
|
|
855
|
+
```js
|
|
856
|
+
export const InputOcclusion = {
|
|
857
|
+
/** Sees everything, blocks nothing. HUD overlays, debug readouts, recorders. */
|
|
858
|
+
Pass: 0,
|
|
859
|
+
/** Switches consumed by this map's fired triggers are invisible below, for that edge. The default. */
|
|
860
|
+
BlockMatched: 1,
|
|
861
|
+
/** Every map below is suppressed outright. Modal dialogs, pause menus. */
|
|
862
|
+
BlockAll: 2
|
|
863
|
+
};
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
**The two values do different jobs at different scopes, and revision 2's failure to say so was a real bug.**
|
|
867
|
+
|
|
868
|
+
### 8.1 `BlockMatched` — dynamic, per edge, dispatch only
|
|
869
|
+
|
|
870
|
+
Per edge, the walk descends maps by `order`. A `consumed : BitSet` over interned switch indices accumulates as
|
|
871
|
+
it goes; a fired trigger adds its own switches, and a trigger whose switches are already consumed does not
|
|
872
|
+
dispatch.
|
|
873
|
+
|
|
874
|
+
**Consumption gates dispatch, not evaluation** — and this is the correction that matters, because the obvious
|
|
875
|
+
alternative is broken. Hiding a consumed edge from a lower map entirely freezes that map's triggers at their
|
|
876
|
+
last observed values, and the next edge they *do* see no longer looks like a change. Concretely: an upper map
|
|
877
|
+
binding `Release(k)` and a lower map binding `k`. Every release is consumed above, so the lower map never
|
|
878
|
+
observes `k` going up; its press trigger fires once and is then dead forever. So lower maps keep evaluating,
|
|
879
|
+
and only their dispatch is suppressed.
|
|
880
|
+
|
|
881
|
+
The honest consequence, since an earlier revision of this document claimed otherwise: a modal-consumed
|
|
882
|
+
keystroke **does** advance a lower map's sequence and **does** run its hold timers. Only the firing is
|
|
883
|
+
withheld. `BlockAll` is what makes a lower map genuinely inert (§8.2), and that is another reason the two
|
|
884
|
+
values are not degrees of the same thing.
|
|
885
|
+
|
|
886
|
+
Consumption is also **unconditional within a map**. Specificity arbitration — `Ctrl+Z` beating a bare `Z`
|
|
887
|
+
(§4.6) — is a property of the map, not of its occlusion, so it holds in a `Pass` map too. What occlusion
|
|
888
|
+
decides is only whether that consumption is still standing when the next map down is walked.
|
|
889
|
+
|
|
890
|
+
`BlockMatched` does not affect polled reads. A pause menu using it stops the combat scene's `Escape` while the
|
|
891
|
+
camera keeps panning behind it — which is exactly what §8 wants of it.
|
|
892
|
+
|
|
893
|
+
### 8.2 `BlockAll` — static, whole-map, dispatch *and* reads
|
|
894
|
+
|
|
895
|
+
Each linked map carries a computed `suppressed : boolean` — "is there a `BlockAll` map above me". It is
|
|
896
|
+
recomputed when the linked map set, any `order`, or any `occlusion` changes; not per edge.
|
|
897
|
+
|
|
898
|
+
A suppressed map is **inert**: the walk skips it, so nothing dispatches; `isActive` returns `false`; coordinate
|
|
899
|
+
reads return zero; sequence matchers do not advance and hold timers do not run. On un-suppression every
|
|
900
|
+
sequence matcher is re-entered with `enter(0, held, now)` (§5.1), so it resumes against current reality rather
|
|
901
|
+
than stale state.
|
|
902
|
+
|
|
903
|
+
This is the fix for the defect revision 2 shipped. There, occlusion was a per-edge `consumed` set gating
|
|
904
|
+
*firing* only, while `isActive` and the coordinate accessors are **polled** — outside any edge walk, with
|
|
905
|
+
nothing connecting them. So the §11 pause menu stopped edge-driven actions while the §11 camera, which is a
|
|
906
|
+
`Script` polling `map.axis2(pan, 'pan')` over WASD every tick, kept panning: precisely the drifting-world bug
|
|
907
|
+
the modal value exists to prevent. Its two occlusion test cases both passed, because both tested dispatched
|
|
908
|
+
actions only.
|
|
909
|
+
|
|
910
|
+
### 8.3 Consequences worth stating
|
|
911
|
+
|
|
912
|
+
* **Local co-op needs no fourth value.** `BlockMatched` is per switch, so player 2's pad is untouched by player
|
|
913
|
+
1's map — they never reference the same switch. `BlockAll` suppresses whole maps and is therefore wrong for
|
|
914
|
+
split-screen; that is a property of the modal hammer, not a hole in it.
|
|
915
|
+
* **`order` ties are resolved by link order**, oldest first. Deterministic, and mentioned because the format
|
|
916
|
+
clamps `order` (§10) and ties are therefore reachable.
|
|
917
|
+
* **Lifetime is entity lifetime.** This is already how the game scopes input:
|
|
918
|
+
`CombatInteractionStateMachine` registers an entity per FSM state and lets `link`/`unlink` turn bindings on
|
|
919
|
+
and off (`CombatInteractionStateMachine.js:113-160`). Put the `InputMap` on the entity owning the context and
|
|
920
|
+
entering or leaving the state *is* the binding lifecycle — no `enable()`/`disable()` bookkeeping, no
|
|
921
|
+
`clearControls` on teardown.
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
## 9. The component and the system
|
|
926
|
+
|
|
927
|
+
### 9.1 `InputMap`
|
|
928
|
+
|
|
929
|
+
```js
|
|
930
|
+
export class InputMap {
|
|
931
|
+
static typeName = "InputMap";
|
|
932
|
+
static serializable = true;
|
|
933
|
+
|
|
934
|
+
/** Index 0 is the base. @readonly @type {InputLayer[]} */
|
|
935
|
+
layers = new List();
|
|
936
|
+
|
|
937
|
+
/** Cross-entity arbitration, higher evaluates first. @type {number} */
|
|
938
|
+
order = 0;
|
|
939
|
+
|
|
940
|
+
/** @type {InputOcclusion} */
|
|
941
|
+
occlusion = InputOcclusion.BlockMatched;
|
|
942
|
+
|
|
943
|
+
// ---- authoring -----------------------------------------------------
|
|
944
|
+
bind(action, ...triggers) { } // -> InputMap, writes to layer 0
|
|
945
|
+
bindCoordinate(action, ...channels) { } // -> InputMap, writes to layer 0
|
|
946
|
+
unbind(action) { } // -> InputMap, covers both kinds
|
|
947
|
+
layer(index) { } // -> InputLayer
|
|
948
|
+
pushLayer() { } // -> InputLayer
|
|
949
|
+
popLayer() { }
|
|
950
|
+
|
|
951
|
+
// ---- reading -------------------------------------------------------
|
|
952
|
+
isActive(action) { } // -> boolean
|
|
953
|
+
coordinate(result, offset, action) { } // any dimensionality
|
|
954
|
+
axis1(result, action) { } // Vector1
|
|
955
|
+
axis2(result, action) { } // Vector2
|
|
956
|
+
axis3(result, action) { } // Vector3
|
|
957
|
+
|
|
958
|
+
equals(other) { }
|
|
959
|
+
hash() { }
|
|
960
|
+
}
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
```js
|
|
964
|
+
export class InputLayer {
|
|
965
|
+
/** @readonly @type {InputBinding[]} */
|
|
966
|
+
bindings = new List();
|
|
967
|
+
/** @readonly @type {List<InputCoordinateBinding>} */
|
|
968
|
+
coordinates = new List();
|
|
969
|
+
|
|
970
|
+
bind(action, ...triggers) { }
|
|
971
|
+
bindCoordinate(action, ...channels) { }
|
|
972
|
+
unbind(action) { }
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export class InputBinding {
|
|
976
|
+
action = "";
|
|
977
|
+
/** @readonly @type {InputTrigger[]} */
|
|
978
|
+
triggers = new List();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export class InputCoordinateBinding {
|
|
982
|
+
action = "";
|
|
983
|
+
/** @readonly @type {InputCoordinateChannel[]} */
|
|
984
|
+
channels = new List();
|
|
985
|
+
}
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
**Coordinate bindings are a first-class list on the layer, not triggers.** Revision 2 exposed
|
|
989
|
+
`bindCoordinate()` with nowhere to store the result — `InputBinding` holds triggers, and a channel is not a
|
|
990
|
+
trigger — so coordinate bindings existed in the API, in the examples and in the argument for deleting
|
|
991
|
+
`KeyboardCameraController`, but in neither the component nor the wire format.
|
|
992
|
+
|
|
993
|
+
**Multiple channels on one action resolve by priority: the first channel with any non-zero component wins.**
|
|
994
|
+
Deterministic, allocation-free, and the behaviour consumers already hand-write — `GamepadDevice`'s own
|
|
995
|
+
documented example is "stick wins, else D-pad". It also avoids the alternative's arithmetic nonsense, where
|
|
996
|
+
keyboard `D` plus a stick at 0.3 would sum to 1.3. All channels bound to one action must agree on
|
|
997
|
+
`dimensions`; asserted at authoring time.
|
|
998
|
+
|
|
999
|
+
The simple case never mentions layers, occlusion or order:
|
|
1000
|
+
|
|
1001
|
+
```js
|
|
1002
|
+
const map = new InputMap();
|
|
1003
|
+
map.bind('jump', InputTriggerKey.from('space'));
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
### 9.2 `InputMapSystem`
|
|
1007
|
+
|
|
1008
|
+
```js
|
|
1009
|
+
export class InputMapSystem extends System {
|
|
1010
|
+
dependencies = [InputMap];
|
|
1011
|
+
|
|
1012
|
+
registerDevice(adapter) { } // may be called after maps are linked; recompiles them (§3.1)
|
|
1013
|
+
|
|
1014
|
+
link(map, entity) { } // compile (§9.3), subscribe to newly-referenced switches
|
|
1015
|
+
unlink(map, entity) { } // release; unsubscribe switches no live map references
|
|
1016
|
+
update(dt) { } // time only: hold maturity, sequence windows, Delta reset, suppression
|
|
1017
|
+
|
|
1018
|
+
setRecorder(recorder) { } // §2.5 — DESIGNED, NOT BUILT
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
Construction takes adapters rather than `engine.devices`, so the system has no dependency on the shape of that
|
|
1023
|
+
object and is constructible in a test with nothing behind it:
|
|
1024
|
+
|
|
1025
|
+
```js
|
|
1026
|
+
new InputMapSystem([
|
|
1027
|
+
new KeyboardInputDeviceAdapter(devices.keyboard),
|
|
1028
|
+
new PointerInputDeviceAdapter(devices.pointer),
|
|
1029
|
+
new GamepadInputDeviceAdapter(devices.gamepad)
|
|
1030
|
+
]);
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
Each interned switch is subscribed once, with a per-switch `SourceBinding {index, system}` passed as the
|
|
1034
|
+
`Signal` handler's context (`Signal.js:151`), so the edge path allocates nothing and needs no closure per
|
|
1035
|
+
event.
|
|
1036
|
+
|
|
1037
|
+
*(As built, the system declares no `components_used`, and does not need to: relative channels LATCH rather
|
|
1038
|
+
than clear (§3.3), so every reader in a frame sees the same value regardless of where the system runs in it.
|
|
1039
|
+
An earlier revision of this section leaned on execution order instead, which was the weaker guarantee.)*
|
|
1040
|
+
|
|
1041
|
+
**Unlinking a map with a sequence mid-match** discards its matcher state along with the compiled program;
|
|
1042
|
+
there is nothing to leak and nothing to resume. Unlinking with switches held is likewise clean: `#held` is
|
|
1043
|
+
owned by the system, not the map, and the map's triggers are simply gone.
|
|
1044
|
+
|
|
1045
|
+
### 9.3 Compilation: the stylesheet / computed-style split
|
|
1046
|
+
|
|
1047
|
+
The `InputMap` component is *authored* data — layered, editable. It is not what the edge path reads. On `link`,
|
|
1048
|
+
on `registerDevice`, and on any structural change, the system compiles it into an internal `InputMapProgram`:
|
|
1049
|
+
|
|
1050
|
+
* cascade resolved — one flat action table per kind, layers already collapsed;
|
|
1051
|
+
* switches interned to `Uint32` indices against a system-wide table;
|
|
1052
|
+
* per switch, the list of triggers it can affect (`collectSources`), pre-sorted into §4.6 specificity order, so
|
|
1053
|
+
an edge touches only triggers that could possibly care;
|
|
1054
|
+
* every trigger's `satisfied` initialised from current state, not from `false` (§4.5);
|
|
1055
|
+
* sequence matcher state allocated as three typed-array slots per sequence binding (§5.1).
|
|
1056
|
+
|
|
1057
|
+
The edge path then touches integers, typed arrays and `BitSet`s only. No string comparison, no `List`
|
|
1058
|
+
traversal, no allocation per event. Recompiles requested during dispatch are queued and applied at the end of
|
|
1059
|
+
the current edge (§2.3), so a walk always completes against the program it started with.
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
1063
|
+
## 10. Serialization
|
|
1064
|
+
|
|
1065
|
+
```js
|
|
1066
|
+
export class InputMapSerializationAdapter extends BinaryClassSerializationAdapter {
|
|
1067
|
+
klass = InputMap;
|
|
1068
|
+
version = 0;
|
|
1069
|
+
|
|
1070
|
+
serialize(buffer, map) { }
|
|
1071
|
+
deserialize(buffer, map) { }
|
|
1072
|
+
}
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
```
|
|
1076
|
+
InputMap
|
|
1077
|
+
int16 order see below
|
|
1078
|
+
uint8 occlusion InputOcclusion
|
|
1079
|
+
uint8 device_count
|
|
1080
|
+
repeat device_count x utf8 device table - every InputDeviceAdapter#id referenced
|
|
1081
|
+
uint16 layer_count
|
|
1082
|
+
repeat layer_count x InputLayer index 0 first; layers carry no name
|
|
1083
|
+
|
|
1084
|
+
InputLayer
|
|
1085
|
+
uint16 binding_count
|
|
1086
|
+
repeat binding_count x InputBinding
|
|
1087
|
+
uint16 coordinate_binding_count
|
|
1088
|
+
repeat coordinate_binding_count x InputCoordinateBinding
|
|
1089
|
+
|
|
1090
|
+
InputBinding
|
|
1091
|
+
utf8 action
|
|
1092
|
+
uint8 trigger_count
|
|
1093
|
+
repeat trigger_count x InputTrigger
|
|
1094
|
+
|
|
1095
|
+
InputCoordinateBinding
|
|
1096
|
+
utf8 action
|
|
1097
|
+
uint8 channel_count
|
|
1098
|
+
repeat channel_count x InputCoordinateChannel
|
|
1099
|
+
|
|
1100
|
+
InputTrigger
|
|
1101
|
+
uint8 type InputTriggerType
|
|
1102
|
+
Switch : InputSource
|
|
1103
|
+
Direction : InputCoordinateChannel, uint8 direction, float32 threshold, float32 hysteresis
|
|
1104
|
+
Chord : uint8 child_count, children
|
|
1105
|
+
Sequence : uint8 child_count, children, float32 step_timeout
|
|
1106
|
+
Hold : child, float32 duration
|
|
1107
|
+
Release : child
|
|
1108
|
+
|
|
1109
|
+
InputCoordinateChannel
|
|
1110
|
+
uint8 type InputCoordinateChannelType
|
|
1111
|
+
Device : InputCoordinateSource
|
|
1112
|
+
Switches : uint8 count, count x InputTrigger (2 per dimension: negative, positive)
|
|
1113
|
+
Compose : uint8 count, count x InputCoordinateChannel
|
|
1114
|
+
|
|
1115
|
+
InputSource
|
|
1116
|
+
uint8 device_index into the map's device table
|
|
1117
|
+
uint16 code
|
|
1118
|
+
|
|
1119
|
+
InputCoordinateSource
|
|
1120
|
+
uint8 device_index
|
|
1121
|
+
uint16 code
|
|
1122
|
+
uint8 dimensions
|
|
1123
|
+
uint8 kind InputCoordinateKind
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
Decisions, not incidentals:
|
|
1127
|
+
|
|
1128
|
+
* **`order` is `int16`.** Revision 2 wrote it as `uint8 + 128`, range `[-128, 127]` — and its own modal example
|
|
1129
|
+
used `order = 1000`, which clamped to 127 on load and could silently tie with any other high map. `int16`
|
|
1130
|
+
covers the examples and leaves headroom; ties resolve by link order (§8.3).
|
|
1131
|
+
* **Coordinate bindings are reachable from the root.** Revision 2 defined an `InputCoordinateChannel` encoding
|
|
1132
|
+
that nothing referenced, so no map containing a coordinate could round-trip.
|
|
1133
|
+
* **`Direction` serializes a channel, not a source.** It accepts any `InputCoordinate*` channel (§4.2), so
|
|
1134
|
+
writing only an `InputCoordinateSource` made direction-over-composed-channel constructible and
|
|
1135
|
+
unserializable.
|
|
1136
|
+
* **A device table, not a string per source.** A 40-binding keyboard map writes `"keyboard"` once rather than
|
|
1137
|
+
forty times.
|
|
1138
|
+
* **Every string here is an identifier.** `writeUTF8String` is ASCII-only in practice and `readUTF8String` is
|
|
1139
|
+
NUL-terminated by design, so this must be stated: action names and device ids are ASCII identifiers, not
|
|
1140
|
+
localized text. A binding UI shows a label looked up *from* the action name; it never serializes the label.
|
|
1141
|
+
* **Codes come from enums, three of which are new.** `KeyCodes`, `GamepadButtons` and `GamepadAxes` are already
|
|
1142
|
+
stable on-disk quantities; `MouseButtons`, `PointerCoordinates` and `GamepadCoordinates` are introduced here
|
|
1143
|
+
and are append-only once shipped (§3.1). Revision 2 claimed "no new numbering to keep in sync", which was
|
|
1144
|
+
false the moment it introduced them.
|
|
1145
|
+
* **Triggers and channels nest, so serialization recurses.** One `uint8` discriminator each, dispatched by a
|
|
1146
|
+
`switch` — verbose, typed and greppable, over a string-keyed constructor table.
|
|
1147
|
+
|
|
1148
|
+
---
|
|
1149
|
+
|
|
1150
|
+
## 11. End to end
|
|
1151
|
+
|
|
1152
|
+
**Gameplay — camera, mixed devices:**
|
|
1153
|
+
|
|
1154
|
+
```js
|
|
1155
|
+
const map = new InputMap();
|
|
1156
|
+
|
|
1157
|
+
map.bindCoordinate('pan',
|
|
1158
|
+
InputCoordinateGamepad.from(GamepadCoordinates.left_stick), // first non-zero wins
|
|
1159
|
+
InputCoordinateSwitches.from([
|
|
1160
|
+
InputTriggerKey.from('a'), InputTriggerKey.from('d'),
|
|
1161
|
+
InputTriggerKey.from('w'), InputTriggerKey.from('s')
|
|
1162
|
+
]));
|
|
1163
|
+
map.bind('user-input', InputTriggerKey.from('a'), InputTriggerKey.from('d'),
|
|
1164
|
+
InputTriggerKey.from('w'), InputTriggerKey.from('s'));
|
|
1165
|
+
map.bind('focus_selection', InputTriggerKey.from('f'));
|
|
1166
|
+
|
|
1167
|
+
new Entity()
|
|
1168
|
+
.add(map)
|
|
1169
|
+
.add(new Script(dt => {
|
|
1170
|
+
map.axis2(pan, 'pan');
|
|
1171
|
+
apply_radial_dead_zone(pan, pan.x, pan.y, 0.15, 0.95); // policy, at the call site
|
|
1172
|
+
controller.pan(pan, dt);
|
|
1173
|
+
}))
|
|
1174
|
+
.build(ecd);
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
Both channels feed one action in one call, so the cascade replaces them as a unit; the stick wins while
|
|
1178
|
+
deflected and the keys take over when it rests. The `'user-input'` binding is
|
|
1179
|
+
`KeyboardCameraController.js:54`'s interaction signal, carried over explicitly rather than lost.
|
|
1180
|
+
|
|
1181
|
+
**Editor — `EditorKeyMap` as data:**
|
|
1182
|
+
|
|
1183
|
+
```js
|
|
1184
|
+
const ctrl = InputTriggerKey.from('ctrl');
|
|
1185
|
+
|
|
1186
|
+
const map = new InputMap();
|
|
1187
|
+
map.order = 100;
|
|
1188
|
+
|
|
1189
|
+
map.bind('undo', InputTriggerChord.from(ctrl, InputTriggerKey.from('z')));
|
|
1190
|
+
map.bind('redo', InputTriggerChord.from(ctrl, InputTriggerKey.from('y')));
|
|
1191
|
+
map.bind('copy', InputTriggerChord.from(ctrl, InputTriggerKey.from('c')));
|
|
1192
|
+
map.bind('paste', InputTriggerChord.from(ctrl, InputTriggerKey.from('v')));
|
|
1193
|
+
map.bind('save', InputTriggerChord.from(ctrl, InputTriggerKey.from('s')));
|
|
1194
|
+
map.bind('duplicate', InputTriggerChord.from(InputTriggerKey.from('shift'), InputTriggerKey.from('d')));
|
|
1195
|
+
map.bind('delete', InputTriggerKey.from('delete'), InputTriggerKey.from('x'));
|
|
1196
|
+
|
|
1197
|
+
ecd.addEntityEventListener(entity, 'undo', () => editor.actions.undo());
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
`Ctrl+Z` beats the bare `Z` binding under §4.6 (modifier-first; see §4.6's stated condition), and the
|
|
1201
|
+
`if (event.ctrlKey)` split disappears. The bindings are serializable, so the editor gets a rebinding panel for
|
|
1202
|
+
free — the thing that could not exist while they were `if` statements. §14.1 lists the three `EditorKeyMap`
|
|
1203
|
+
behaviours that do *not* come across.
|
|
1204
|
+
|
|
1205
|
+
**Modal — pause menu:**
|
|
1206
|
+
|
|
1207
|
+
```js
|
|
1208
|
+
const map = new InputMap();
|
|
1209
|
+
map.order = 1000;
|
|
1210
|
+
map.occlusion = InputOcclusion.BlockAll;
|
|
1211
|
+
map.bind('resume', InputTriggerKey.from('escape'));
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
Every map below is suppressed: no dispatch, and `isActive`/`axis2` read neutral — so the camera above stops
|
|
1215
|
+
rather than drifting behind the dialog (§8.2).
|
|
1216
|
+
|
|
1217
|
+
**Cross-device chords:**
|
|
1218
|
+
|
|
1219
|
+
```js
|
|
1220
|
+
map.bind('precision_place', InputTriggerChord.from(InputTriggerKey.from('ctrl'),
|
|
1221
|
+
InputTriggerMouseButton.from(MouseButtons.left)));
|
|
1222
|
+
map.bind('pad_reset', InputTriggerChord.from(InputTriggerGamepadButton.from(GamepadButtons.back),
|
|
1223
|
+
InputTriggerKey.from('r')));
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
---
|
|
1227
|
+
|
|
1228
|
+
## 12. Testing
|
|
1229
|
+
|
|
1230
|
+
### 12.1 No mocks, and no DOM
|
|
1231
|
+
|
|
1232
|
+
The seam is `InputDeviceSwitch`: a real object with `press()`/`release()`, constructible in node with nothing
|
|
1233
|
+
beneath it. A test adapter is a *real* adapter over *real* switches — not a mock. Eager evaluation keeps the
|
|
1234
|
+
tests short, because there is no tick to pump for edge-driven behaviour:
|
|
1235
|
+
|
|
1236
|
+
```js
|
|
1237
|
+
const kb = new TestInputDeviceAdapter('keyboard', KeyCodes);
|
|
1238
|
+
const system = new InputMapSystem([kb]);
|
|
1239
|
+
// real EntityComponentDataset, real InputMap, real listeners
|
|
1240
|
+
|
|
1241
|
+
kb.switch(KeyCodes.ctrl).press();
|
|
1242
|
+
kb.switch(KeyCodes.z).press();
|
|
1243
|
+
|
|
1244
|
+
expect(observed).toEqual(['undo']); // no system.update() anywhere
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
`system.update(dt)` appears only in tests for hold maturity, sequence windows, `Delta` reset and suppression —
|
|
1248
|
+
a precise statement of what the tick is for.
|
|
1249
|
+
|
|
1250
|
+
*(Revision 2 justified this by claiming `InputSystem` "cannot be exercised without a focusable DOM element".
|
|
1251
|
+
That was overstated: focusability is `KeyboardDevice`'s requirement (`KeyboardDevice.js:61-66`), and
|
|
1252
|
+
`InputSystem` resolves paths against any object, so a synthetic tree of real Signals would exercise it. The
|
|
1253
|
+
real point is narrower and still worth making — nobody has, and `InputBinding.spec.js` asserts only that a
|
|
1254
|
+
constructor does not throw.)*
|
|
1255
|
+
|
|
1256
|
+
### 12.2 What gets asserted
|
|
1257
|
+
|
|
1258
|
+
Observable behaviour only: dispatched action names and order, `isActive` levels, coordinate values. Never which
|
|
1259
|
+
triggers were evaluated, never how many times a matcher ran, never internal matcher state.
|
|
1260
|
+
|
|
1261
|
+
| | Case | Guards |
|
|
1262
|
+
|---|---|---|
|
|
1263
|
+
| 1 | An action fires before any `update()` runs | §2 eager evaluation — the requirement, as a test |
|
|
1264
|
+
| 2 | Press and release inside one frame both fire; two presses dispatch in press order | §2 |
|
|
1265
|
+
| 3 | `Ctrl` then `Z` fires only `undo`; `Z` then `Ctrl` fires both, in that order | §4.6 and its stated condition |
|
|
1266
|
+
| 4 | One long press of `A` does not satisfy both steps of `[A, A]` | §5.1 entry snapshot |
|
|
1267
|
+
| 5 | `[A,A,B]` fires on `A A A B` | §5.3 |
|
|
1268
|
+
| 6 | Holding `A` and tapping `B` twice fires `[A,B]` **once** | §5.3 post-fire re-entry — the revision-2 machine-gun |
|
|
1269
|
+
| 7 | A window expiry with the first step's key held does not spuriously advance | §5.1 — the revision-2 stale-snapshot bug |
|
|
1270
|
+
| 8 | `Ctrl+Shift+A > B` fires whether or not modifiers are released first | §5.4 |
|
|
1271
|
+
| 9 | `↓ ↘ →` over a stick coordinate fires; the same over raw d-pad *buttons* does not | §5.4 — the exclusivity claim, both directions |
|
|
1272
|
+
| 10 | Charge (`hold ← 2 s > →`) fires after a 10 s hold; returning to neutral instead expires it | §5.2 window refresh — the revision-2 charge failure |
|
|
1273
|
+
| 11 | A sequence window expires only after the previous step stops holding | §5.2 |
|
|
1274
|
+
| 12 | `Release(Chord(a,b))` does not fire on the first edge touching `a` | §4.5 `satisfied` initialisation |
|
|
1275
|
+
| 13 | A handler that removes its own entity, or rebinds its own map, does not corrupt the walk | §2.3 |
|
|
1276
|
+
| 14 | A `BlockAll` map zeroes a lower map's `axis2` and `isActive`, not just its dispatch | §8.2 — the revision-2 drifting camera |
|
|
1277
|
+
| 15 | A `BlockMatched` map does **not** stop a lower map's unrelated action or its coordinate reads | §8.1 — the converse |
|
|
1278
|
+
| 16 | A lower map's press binding still fires after many edges consumed above it | §8.1 — the stale-edge trap |
|
|
1279
|
+
| 17 | Un-suppressing a map re-enters its sequences at 0 rather than resuming stale state | §8.2 |
|
|
1280
|
+
| 18 | The top layer's `jump` replaces the base layer's `jump`; two triggers in one layer both fire | §7 |
|
|
1281
|
+
| 19 | A coordinate action bound to stick + keys reads the stick while deflected, the keys at rest | §9.1 priority |
|
|
1282
|
+
| 20 | A gamepad binding with no pad connected links and never fires; so does an unregistered device id | §3.1 totality |
|
|
1283
|
+
| 21 | `registerDevice` after link makes previously inert bindings live | §3.1, §9.3 |
|
|
1284
|
+
| 22 | Two wheel events in one frame sum for tick-time readers | §3.3 `Delta` |
|
|
1285
|
+
| 23 | A 3-D wheel coordinate round-trips all three components | §3.3 dimensionality |
|
|
1286
|
+
| 24 | Serialize, deserialize, replay a recorded trace, get the same dispatches | §10, §2.5 |
|
|
1287
|
+
|
|
1288
|
+
Cases 6, 7, 10 and 14 are regressions against defects revision 2 actually contained; write them first. Case 9
|
|
1289
|
+
is the only claim in §5.4 that could be wrong in a way the others would not catch.
|
|
1290
|
+
|
|
1291
|
+
---
|
|
1292
|
+
|
|
1293
|
+
## 13. The trigger DSL — planned, deferred, optional
|
|
1294
|
+
|
|
1295
|
+
**Not implemented in this design.** Specified here so the trigger classes are shaped to accept it, and so it
|
|
1296
|
+
is not invented ad hoc later.
|
|
1297
|
+
|
|
1298
|
+
### 13.1 What it is
|
|
1299
|
+
|
|
1300
|
+
A text syntax compiling to the same `InputTrigger` objects §4 defines. It is **an additional entry point,
|
|
1301
|
+
never a required one**: an author can compose triggers by hand or write them as text, and the runtime cannot
|
|
1302
|
+
tell the results apart.
|
|
1303
|
+
|
|
1304
|
+
```js
|
|
1305
|
+
import { InputTriggerDSL } from "./dsl/InputTriggerDSL.js";
|
|
1306
|
+
|
|
1307
|
+
map.bind('promote', InputTriggerDSL.compile("keyboard:ctrl + keyboard:shift + keyboard:a > keyboard:b"));
|
|
1308
|
+
```
|
|
1309
|
+
|
|
1310
|
+
### 13.2 Grammar
|
|
1311
|
+
|
|
1312
|
+
```ebnf
|
|
1313
|
+
trigger = sequence ;
|
|
1314
|
+
sequence = chord , { ">" , chord } , [ "within" , NUMBER ] ;
|
|
1315
|
+
chord = unary , { "+" , unary } ;
|
|
1316
|
+
unary = [ "!" ] , primary ;
|
|
1317
|
+
primary = atom | "(" , trigger , ")" | call ;
|
|
1318
|
+
call = IDENT , "(" , [ trigger , { "," , trigger | NUMBER } ] , ")" ;
|
|
1319
|
+
atom = DEVICE , ":" , BINDPOINT , [ "." , SUBPOINT ] ;
|
|
1320
|
+
DEVICE = IDENT ;
|
|
1321
|
+
BINDPOINT = IDENT | NUMBER ;
|
|
1322
|
+
SUBPOINT = IDENT ;
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
| Form | Means | Compiles to |
|
|
1326
|
+
|---|---|---|
|
|
1327
|
+
| `keyboard:space` | a key | `InputTriggerKey` |
|
|
1328
|
+
| `pointer:left` | a mouse button | `InputTriggerMouseButton` |
|
|
1329
|
+
| `gamepad:a` | a pad button | `InputTriggerGamepadButton` |
|
|
1330
|
+
| `gamepad:left_stick.down` | a direction on a coordinate | `InputTriggerDirection` |
|
|
1331
|
+
| `a + b` | chord | `InputTriggerChord` |
|
|
1332
|
+
| `a > b` | sequence | `InputTriggerSequence` |
|
|
1333
|
+
| `!a` | release / negation | `InputTriggerRelease` |
|
|
1334
|
+
| `hold(a, 0.5)` | held for 0.5 s | `InputTriggerHold` |
|
|
1335
|
+
| `(...)` | grouping | — |
|
|
1336
|
+
| `... within 0.2` | step timeout | `withStepTimeout` |
|
|
1337
|
+
|
|
1338
|
+
**Precedence, tightest first:** `!` → `+` → `>` → `within`, so
|
|
1339
|
+
`keyboard:ctrl + keyboard:shift + keyboard:a > keyboard:b` parses as intended with no parentheses.
|
|
1340
|
+
|
|
1341
|
+
**Keyword collision** is resolved by the `:` — an identifier followed by `:` is a device name, so a device may
|
|
1342
|
+
be called `hold` without ambiguity. Worth stating because it is where small DSLs usually break.
|
|
1343
|
+
|
|
1344
|
+
The §5.4 motion, written out:
|
|
1345
|
+
|
|
1346
|
+
```
|
|
1347
|
+
gamepad:left_stick.down > gamepad:left_stick.down_right > gamepad:left_stick.right + gamepad:x within 0.2
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
### 13.3 Why it stays separate
|
|
1351
|
+
|
|
1352
|
+
* **`ism/dsl/` is its own module**, imported by nothing in the runtime. A game that composes triggers by hand
|
|
1353
|
+
never pays for a parser — it is not in the bundle.
|
|
1354
|
+
* **It is a compiler, not a representation.** Serialization stays binary (§10), so a DSL change can never
|
|
1355
|
+
invalidate saved bindings.
|
|
1356
|
+
* **`InputTriggerDSL.stringify(trigger)` is the inverse**, which is what a rebinding UI wants: it shows a
|
|
1357
|
+
binding in the syntax an author would type, with no second formatting path.
|
|
1358
|
+
|
|
1359
|
+
### 13.4 Deferred until
|
|
1360
|
+
|
|
1361
|
+
Everything in §14 phases 1–8 has landed and at least one consumer has migrated. The DSL is a convenience over a
|
|
1362
|
+
working system; building it first would freeze the trigger vocabulary before the vocabulary has been used.
|
|
1363
|
+
|
|
1364
|
+
---
|
|
1365
|
+
|
|
1366
|
+
## 14. Migration
|
|
1367
|
+
|
|
1368
|
+
The ISM lands beside the existing systems. Both keep working; neither is touched until its consumers move.
|
|
1369
|
+
**`InputController` and `InputControllerSystem` survive this program** — they still own every gesture binding
|
|
1370
|
+
(§1.1).
|
|
1371
|
+
|
|
1372
|
+
*This table is the plan of work, not a progress report. For where each phase actually stands, read the status
|
|
1373
|
+
section of [`ISM_IMPLEMENTATION_PLAN.md`](ISM_IMPLEMENTATION_PLAN.md), which is kept current as phases land.*
|
|
1374
|
+
|
|
1375
|
+
| Phase | Work | Gate |
|
|
1376
|
+
|---|---|---|
|
|
1377
|
+
| **1** | `InputSource`, `InputCoordinateSource`, the `InputTrigger*` classes, `InputMap` / `InputLayer` / `InputBinding` / `InputCoordinateBinding` | Value-object specs: `equals` / `hash` / round-trip |
|
|
1378
|
+
| **2** | `InputDeviceAdapter` and the three adapters; the `MouseButtons`, `PointerCoordinates`, `GamepadCoordinates` enums | Totality: every declared code resolves with no hardware, and with no adapter (§12.2 cases 20–21) |
|
|
1379
|
+
| **3** | `InputMapSystem`: eager edge path, `#held`, compile, switch/chord/release, cascade, `BlockMatched`, ECS dispatch, `isActive` | cases 1–3, 12, 13, 15, 18 |
|
|
1380
|
+
| **4** | `InputTriggerSequence` + `InputTriggerHold`; synthetic maturity edges; window refresh and expiry | cases 4–8, 10, 11, 16 |
|
|
1381
|
+
| **5** | Coordinates: channels, priority resolution, the `Delta` latch, read accessors | cases 19, 22, 23; retires `KeyboardCameraController` |
|
|
1382
|
+
| **6** | `InputTriggerDirection` over coordinates, with threshold and hysteresis | case 9 |
|
|
1383
|
+
| **7** | `BlockAll` suppression across dispatch and reads; un-suppression re-entry | cases 14, 17 |
|
|
1384
|
+
| **8** | `InputMapSerializationAdapter` + `InputRecorder` | case 24 |
|
|
1385
|
+
| **9** | Migrate consumers (below) | existing specs stay green |
|
|
1386
|
+
| **10** | `ism/dsl/` — the §13 compiler and `stringify` | round-trip: `compile(stringify(t))` equals `t` |
|
|
1387
|
+
|
|
1388
|
+
### 14.1 Consumers
|
|
1389
|
+
|
|
1390
|
+
1. **`EditorKeyMap`** — **blocked, not retired.** The engine's `KeyboardDevice` is mounted on
|
|
1391
|
+
`viewStack.el` (`Engine.js:367`) and `Editor.js:563` pushes the entire editor GUI into that same stack, so
|
|
1392
|
+
an editor map would see keys typed into panel text fields — `x` would delete the selected entities, and
|
|
1393
|
+
`preventDefault` would swallow the keystroke. The ISM has no DOM awareness to express the old gate (§6.3),
|
|
1394
|
+
so retiring this needs a design addition. See the implementation plan §5.1. Four behaviours would have to be
|
|
1395
|
+
re-provided in any case, because none of them is in the ISM:
|
|
1396
|
+
* **Focus gating.** `isViewFocused` (`EditorKeyMap.js:12-31`) suppresses all editor bindings when focus is
|
|
1397
|
+
outside the game view. In the ISM this depends on which element the editor's `KeyboardDevice` is mounted
|
|
1398
|
+
on. With bare-key bindings like `x` = delete, getting it wrong is destructive — verify before landing.
|
|
1399
|
+
* **Autorepeat.** Holding `Ctrl+Z` currently repeat-undoes; it will not (§5.5). Decide per action whether
|
|
1400
|
+
that is a loss or a fix.
|
|
1401
|
+
* **`stopPropagation()`** on handled chords — currently prevents the event reaching other listeners.
|
|
1402
|
+
* **The unconditional forward to the active tool** (`EditorKeyMap.js:175-180`), which `Tool.handleKeyboardEvent`
|
|
1403
|
+
relies on: each tool gets its own `InputMap` on its activation entity, at a higher `order`.
|
|
1404
|
+
2. **`KeyboardCameraController`** — retired by §3.3, not ported. Carry the `'user-input'` event (§11).
|
|
1405
|
+
3. **`Input` / `InputSystem`** — **done.** All five consumers moved and both are `@deprecated`.
|
|
1406
|
+
`OverlayPageGUI` (escape), `CombatInteractionController` (p, escape) and two harnesses were straight
|
|
1407
|
+
translations. `StoryManager` was a split: it bound both a tap and the space bar to one action, and only
|
|
1408
|
+
the space bar moved. A tap is not a button press (§1.1) — translating it would change advance-on-release
|
|
1409
|
+
to advance-on-press and fire at the start of every drag — so the tap kept a small `InputController`, which
|
|
1410
|
+
is the decision the earlier revision of this section left open.
|
|
1411
|
+
4. **`InputController` keyboard bindings** — **not started.** The keyboard subset moves; `priority` becomes
|
|
1412
|
+
`order`, `exclusive` becomes `InputOcclusion.BlockMatched`. Gesture bindings stay put, which for
|
|
1413
|
+
`StrategyInteractionController` means most of the file. This is where escape arbitration finally bites
|
|
1414
|
+
(plan §5.4) and where `KeyboardCameraController`'s `'user-input'` entity subtlety has to be handled.
|
|
1415
|
+
5. **`components/InputBinding.js`** (`{path, listener, …}`) is orphaned only when `InputController` finally
|
|
1416
|
+
goes, which is not in this program.
|
|
1417
|
+
|
|
1418
|
+
### 14.2 A naming collision to plan around
|
|
1419
|
+
|
|
1420
|
+
*(Resolved: the new record lives at `ism/map/InputBinding.js`, so the two never shared a filename and the old
|
|
1421
|
+
one kept its path and its importer. What follows is the problem as it stood.)*
|
|
1422
|
+
|
|
1423
|
+
Phase 1 introduces `InputBinding` in `ism/`, and `ism/InputBinding.js` already exists as the old
|
|
1424
|
+
`{path, event}` record imported by [`Input.js:3`](../components/Input.js). The old file must survive until
|
|
1425
|
+
`Input` is retired (phase 9), so the two cannot share a filename. Land the new one as `ism/InputBinding.js`
|
|
1426
|
+
only after the old is deleted; until then it lives beside its siblings under a transitional name, or `Input`
|
|
1427
|
+
is repointed at a copy under `components/`.
|
|
1428
|
+
|
|
1429
|
+
---
|
|
1430
|
+
|
|
1431
|
+
## 15. Explicitly not doing
|
|
1432
|
+
|
|
1433
|
+
* **Pointer gestures** — tap, drag, move, wheel-as-gesture, pinch. §1.1, with the binding counts. Revision 2
|
|
1434
|
+
claimed "nothing in the codebase binds pinch today"; that was false —
|
|
1435
|
+
`StrategyInteractionController.js:437,447` binds `pinchStart` and `pinch`, and it is also the largest
|
|
1436
|
+
migration target.
|
|
1437
|
+
* **Buffering input to a frame boundary.** §2. Revision 1 did, and it bought nothing eager evaluation does not
|
|
1438
|
+
also provide.
|
|
1439
|
+
* **Discriminating tap from hold on one switch.** §5.5 — it requires withholding the short action, which is the
|
|
1440
|
+
buffering above. Accepted as a cost.
|
|
1441
|
+
* **Key autorepeat.** §5.5 — not observable through `InputDeviceSwitch` at all.
|
|
1442
|
+
* **Keeping string paths.** `resolvePath` throws where it should resolve and cannot express two sources at once;
|
|
1443
|
+
`GAMEPAD_API_PROPOSAL.md §5` says not to grow it.
|
|
1444
|
+
* **A trigger class per gesture pattern.** Double-tap is a two-step sequence with a short window; long-press is
|
|
1445
|
+
`InputTriggerHold`.
|
|
1446
|
+
* **Dead zones, sensitivity or response curves on analog output.** §3.3. The direction threshold and hysteresis
|
|
1447
|
+
in §3.4 are not exceptions: producing a boolean from a coordinate cannot be done without them.
|
|
1448
|
+
* **A `Started` / `Performed` / `Canceled` phase enum, or an `edge` field.** §4.1 — every trigger is a
|
|
1449
|
+
predicate and every binding fires on its rising edge.
|
|
1450
|
+
* **A per-binding `consume` flag.** Occlusion is a property of a *context*, not of one binding, and a boolean
|
|
1451
|
+
per binding makes behaviour depend on declaration order.
|
|
1452
|
+
* **Named layers.** §7 — a stack is addressed by index.
|
|
1453
|
+
* **Behaviour in the component.** No function references, no script names, no serialized handler ids (§6.1).
|
|
1454
|
+
* **DOM-target awareness in `InputActionEvent`.** §6.3 — it belongs with the deferred gesture work.
|
|
1455
|
+
* **Strict sequence rejection**, and **button buffering**. §5.5, both deferred with known shapes.
|
|
1456
|
+
* **A generic input node graph.** The trigger classes are already a small algebra; the DSL (§13) is the cheaper
|
|
1457
|
+
first step over it.
|
|
1458
|
+
|
|
1459
|
+
---
|
|
1460
|
+
|
|
1461
|
+
## 16. Resolved decisions
|
|
1462
|
+
|
|
1463
|
+
1. **Analog channels are N-dimensional "coordinates", not axes** — §3.3. `WheelEvent` has three components and
|
|
1464
|
+
`PointerDevice` surfaces all three. `kind` (`Absolute` / `Delta` / `Normalized`) carries the verb.
|
|
1465
|
+
2. **`order` stays author-set** — §8. A GUI-hierarchy-derived order would need no hand-picked numbers but would
|
|
1466
|
+
couple input arbitration to that hierarchy.
|
|
1467
|
+
3. **No sensitivity in bindings** — §3.3. A rebinding UI serializes *which* channel drives an action, never how
|
|
1468
|
+
twitchy it is.
|
|
1469
|
+
4. **Layers are an index-addressed stack, with no names** — §7.
|
|
1470
|
+
5. **Evaluation is eager, on the edge** — §2. No buffer, no added frame. The costs — re-entrant dispatch,
|
|
1471
|
+
arrival-ordering, the §4.6 chord condition, the §5.5 tap-versus-hold limit, and the §3.3 `Delta` split
|
|
1472
|
+
between eager and tick-time readers — are stated where they arise rather than hidden.
|
|
1473
|
+
6. **The trigger DSL is planned, specified and deferred** — §13, phase 10.
|
|
1474
|
+
7. **Pointer gestures are out of scope** — §1.1. `InputController` is reduced, not retired.
|
|
1475
|
+
8. **`SimpleStateMachine` is not used** — §5.6. The matcher is a path, not a graph; the class becomes the right
|
|
1476
|
+
tool if sequences ever branch.
|
|
1477
|
+
|
|
1478
|
+
---
|
|
1479
|
+
|
|
1480
|
+
## 17. What changed in revision 3, and why
|
|
1481
|
+
|
|
1482
|
+
An adversarial review of revision 2 found three defects that made it unimplementable, plus a set of false
|
|
1483
|
+
claims about the existing codebase. Everything below was verified against source before being changed.
|
|
1484
|
+
|
|
1485
|
+
### Defects
|
|
1486
|
+
|
|
1487
|
+
| | Was | Now |
|
|
1488
|
+
|---|---|---|
|
|
1489
|
+
| **Sequence matcher** | Reset paths (window expiry, post-fire) did not re-establish the entry snapshot, and the advance step indexed `steps[N]`. Holding `A` and mashing `B` machine-gunned `[A,B]` off one `A` press; an expiry with the first key held spuriously advanced with no edge. | Every entry routes through `enter(k, held, now)` (§5.1), which snapshots and never indexes `steps[N]`. Cases 6 and 7. |
|
|
1490
|
+
| **Occlusion** | A per-edge `consumed` set gating *firing* only. `isActive` and coordinate reads are polled, so `BlockAll` could not stop them — a pause menu left the camera panning, the exact bug §8 claimed to prevent. Both occlusion tests passed anyway, testing only dispatch. | Two scopes: `BlockMatched` stays dynamic and dispatch-only; `BlockAll` sets a static per-map `suppressed` covering dispatch *and* reads (§8.2). Cases 14, 15, 17. |
|
|
1491
|
+
| **Coordinate bindings** | `bindCoordinate()` existed with nowhere to store its result, and §10's channel encoding was unreachable from the format root. No map with a coordinate could serialize. `bindCoordinate` was also called twice for one action in §11 with no defined meaning. | `InputCoordinateBinding` on the layer, reachable in the format, with priority resolution (§9.1). Case 19. |
|
|
1492
|
+
|
|
1493
|
+
### Corrections
|
|
1494
|
+
|
|
1495
|
+
* **Charge inputs failed.** The step window ran from the advance, so any charge longer than
|
|
1496
|
+
`duration + step_timeout` expired before the flick. It now measures time since the previous step *stopped*
|
|
1497
|
+
holding (§5.2). Case 10.
|
|
1498
|
+
* **`satisfied` initialisation was unspecified**, and zero-init makes `Release(Chord(a,b))` fire before the
|
|
1499
|
+
chord exists. Now snapshotted at compile (§4.5). Case 12.
|
|
1500
|
+
* **`order` was `uint8`-biased**, range `[-128,127]`, while §11's own modal used `1000`. Now `int16` (§10).
|
|
1501
|
+
* **`MouseButtons` cited the wrong standard.** The `MouseEvent.buttons` *bitmask* order is left/right/middle;
|
|
1502
|
+
`PointerDevice.buttons[]` is indexed by `MouseEvent.button` *ordinal*. Following the citation would have
|
|
1503
|
+
fired every right-click binding on the middle button, into saved data (§3.1).
|
|
1504
|
+
* **`Release(sequence)` was claimed to mean "stopped matching".** It cannot — a sequence's `test` is a pulse
|
|
1505
|
+
(§4.3).
|
|
1506
|
+
* **Specificity did not rank `Hold` / `Release` / `Direction`**, so they fell through to declaration order —
|
|
1507
|
+
the accident §4.6 claims to eliminate (§4.6).
|
|
1508
|
+
* **Chord-vs-subset arbitration was stated unconditionally.** It holds only when the modifier arrives first
|
|
1509
|
+
(§4.6). Case 3 now tests both orders.
|
|
1510
|
+
* **The `Delta` "same value for every reader" invariant** was a leftover from the buffered design and is false
|
|
1511
|
+
under eager dispatch (§3.3).
|
|
1512
|
+
* **Re-entrancy covered only structural change.** Nested edges and mid-dispatch binding edits are now specified
|
|
1513
|
+
(§2.3).
|
|
1514
|
+
* **The recorder captured only switch edges**, so no map with a coordinate or a hold could replay. It now
|
|
1515
|
+
records coordinate samples and per-frame `dt` (§2.5).
|
|
1516
|
+
* **Unregistered devices had no defined behaviour** (§3.1).
|
|
1517
|
+
* **`EditorKeyMap` and `KeyboardCameraController` deletions dropped real behaviour** — focus gating,
|
|
1518
|
+
autorepeat, `stopPropagation`, the tool forward, the `'user-input'` event — now inventoried (§14.1).
|
|
1519
|
+
* **Counts and claims:** `KeyboardCameraController` has 16 `bind()` calls, not eight; the `InputController`
|
|
1520
|
+
path-string count flattered a migration that is mostly gestures (§1.1); `InputControllerSystem`'s `deferred`
|
|
1521
|
+
array is dead code, not a working mitigation (§2.3); "`InputSystem` cannot be tested without a DOM element"
|
|
1522
|
+
was overstated (§12.1); "no new numbering to keep in sync" was false (§10); `PointerDevice`'s wheel reports
|
|
1523
|
+
`sign()`, so a `Delta` wheel channel counts ticks rather than distance (§3.3).
|