@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.
Files changed (1823) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -145
  3. package/editor/Editor.js +598 -598
  4. package/editor/EditorCameraNavigation.js +205 -205
  5. package/editor/EditorKeyMap.js +182 -182
  6. package/editor/SelectionVisualizer.js +127 -127
  7. package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
  8. package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
  9. package/editor/camera/camera_framing.js +104 -104
  10. package/editor/clipboard.js +184 -184
  11. package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
  12. package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
  13. package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
  14. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
  15. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
  16. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  17. package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
  18. package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
  19. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
  20. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
  21. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
  22. package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
  23. package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
  24. package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
  25. package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
  26. package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
  27. package/editor/ecs/component/prototypeObjectEditor.js +265 -265
  28. package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
  29. package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
  30. package/editor/entity_world_bounds.js +27 -27
  31. package/editor/particles/lut_ops.js +148 -148
  32. package/editor/particles/particleEditEvents.js +12 -12
  33. package/editor/particles/rebuildParticleEmitter.js +31 -31
  34. package/editor/persistence/EditorLayoutStore.js +71 -71
  35. package/editor/persistence/SceneMetadata.js +52 -52
  36. package/editor/persistence/SceneSlotManager.js +144 -144
  37. package/editor/persistence/buildNewSceneDataset.js +67 -67
  38. package/editor/persistence/files.js +68 -68
  39. package/editor/persistence/scene_import.js +143 -143
  40. package/editor/persistence/scene_serialization.js +79 -79
  41. package/editor/persistence/string_buffer_codec.js +41 -41
  42. package/editor/process/GridDisplayProcess.js +125 -125
  43. package/editor/process/SymbolicDisplayProcess.js +106 -108
  44. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  45. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  46. package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
  47. package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
  48. package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
  49. package/editor/process/symbolic/SymbolicDisplay.js +62 -62
  50. package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
  51. package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
  52. package/editor/prototypeEditorShell.js +156 -156
  53. package/editor/selection/editor_pick.js +130 -130
  54. package/editor/selection/screen_rect_frustum.js +119 -119
  55. package/editor/style/controls.scss +168 -168
  56. package/editor/style/inspector.scss +223 -223
  57. package/editor/templates/entity_templates.js +167 -167
  58. package/editor/tools/GridPaintTool.js +273 -273
  59. package/editor/tools/SelectionTool.js +307 -307
  60. package/editor/tools/TransformToolV2.js +378 -378
  61. package/editor/tools/engine/ToolEngine.js +188 -188
  62. package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
  63. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
  64. package/editor/tools/v2/TransformControls.js +971 -971
  65. package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
  66. package/editor/tools/v2/handle/gizmo_draw.js +399 -399
  67. package/editor/tools/v2/handle/gizmo_handles.js +364 -364
  68. package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
  69. package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
  70. package/editor/view/ecs/components/DatGuiController.js +2 -2
  71. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
  72. package/editor/view/ecs/components/common/TextController.js +59 -59
  73. package/editor/view/library/MeshLibraryView.js +175 -175
  74. package/editor/view/library/mesh_library_drag.js +63 -63
  75. package/editor/view/library/model_thumbnail_source.js +86 -86
  76. package/editor/view/makeEntityDecorators.js +125 -125
  77. package/editor/view/node-graph/NodeGraphSelection.js +89 -89
  78. package/editor/view/node-graph/NodeView.js +211 -211
  79. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
  80. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
  81. package/editor/view/node-graph/connection_wire_geometry.js +107 -107
  82. package/editor/view/particles/ColorLutGradientView.js +291 -291
  83. package/editor/view/particles/ScalarLutCurveView.js +279 -279
  84. package/editor/view/prepareMeshLibrary.js +188 -188
  85. package/editor/view/shell/SceneTreePanelView.js +119 -119
  86. package/editor/view/v2/SplitView.js +2 -2
  87. package/package.json +94 -94
  88. package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
  89. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
  90. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
  91. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
  92. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
  93. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
  94. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
  95. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
  96. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
  97. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
  98. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
  99. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
  100. package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
  101. package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
  102. package/src/REVIEW_2026_08_06/adv-core.md +511 -511
  103. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
  104. package/src/REVIEW_2026_08_06/adv-view.md +446 -446
  105. package/src/REVIEW_2026_08_06/api-core.md +296 -296
  106. package/src/REVIEW_2026_08_06/api-engine.md +195 -195
  107. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
  108. package/src/REVIEW_2026_08_06/core-binary.md +348 -348
  109. package/src/REVIEW_2026_08_06/core-collection.md +376 -376
  110. package/src/REVIEW_2026_08_06/core-math.md +221 -221
  111. package/src/REVIEW_2026_08_06/core-process.md +266 -266
  112. package/src/REVIEW_2026_08_06/core-science.md +223 -223
  113. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
  114. package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
  115. package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
  116. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
  117. package/src/REVIEW_2026_08_06/engine-input.md +197 -197
  118. package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
  119. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
  120. package/src/REVIEW_2026_08_06/generation.md +176 -176
  121. package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
  122. package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
  123. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
  124. package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
  125. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
  126. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
  127. package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
  128. package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
  129. package/src/REVIEW_2026_08_06/view.md +667 -667
  130. package/src/REVIEW_2026_08_06.md +610 -610
  131. package/src/avif/codec/dec/avif_dec.cpp +233 -233
  132. package/src/avif/codec/dec/avif_dec.js +16 -16
  133. package/src/core/binary/BitImage2.js +246 -246
  134. package/src/core/binary/align_32.js +21 -21
  135. package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
  136. package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
  137. package/src/core/binary/hash/XXH128Hash.js +55 -55
  138. package/src/core/binary/hash/XXH3_SECRET.js +25 -25
  139. package/src/core/binary/hash/xxh3_128.js +341 -341
  140. package/src/core/binary/hash/xxh3_64.js +241 -241
  141. package/src/core/binary/hash/xxh3_common.js +476 -476
  142. package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
  143. package/src/core/binary/lz4/lz4_compress_block.js +385 -385
  144. package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
  145. package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
  146. package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
  147. package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
  148. package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
  149. package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
  150. package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
  151. package/src/core/binary/meshopt/meshopt_decode.js +73 -73
  152. package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
  153. package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
  154. package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
  155. package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
  156. package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
  157. package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
  158. package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
  159. package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
  160. package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
  161. package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
  162. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
  163. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
  164. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
  165. package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
  166. package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
  167. package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
  168. package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
  169. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
  170. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
  171. package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
  172. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
  173. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
  174. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
  175. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
  176. package/src/core/bvh2/visual/BVHGeometry.js +131 -131
  177. package/src/core/bvh8/BVH8.js +436 -436
  178. package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
  179. package/src/core/bvh8/build/BVH8Converter.js +588 -588
  180. package/src/core/bvh8/build/TriangleCluster.js +122 -122
  181. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
  182. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
  183. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
  184. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
  185. package/src/core/bvh8/build/byte.js +9 -9
  186. package/src/core/bvh8/build/encode_bounds_e.js +11 -11
  187. package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
  188. package/src/core/bvh8/bvh8_count_primitives.js +98 -98
  189. package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
  190. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
  191. package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
  192. package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
  193. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
  194. package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
  195. package/src/core/bvh8/bvh8_quality.js +72 -72
  196. package/src/core/bvh8/bvh8_validate_structure.js +87 -87
  197. package/src/core/clipboard/obtainClipBoard.js +29 -29
  198. package/src/core/clipboard/safeClipboardReadText.js +55 -55
  199. package/src/core/clipboard/safeClipboardWriteText.js +23 -23
  200. package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
  201. package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
  202. package/src/core/collection/union-find/union_find.js +79 -79
  203. package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
  204. package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
  205. package/src/core/color/colormap/MAGMA_LUT.js +26 -26
  206. package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
  207. package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
  208. package/src/core/color/lab/lab_distance_hyab.js +23 -23
  209. package/src/core/color/lab/xyz_to_lab.js +62 -62
  210. package/src/core/color/operations/color_darken.js +24 -24
  211. package/src/core/color/operations/color_desaturate.js +24 -24
  212. package/src/core/color/operations/color_lighten.js +24 -24
  213. package/src/core/color/operations/color_saturate.js +24 -24
  214. package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
  215. package/src/core/color/tonemap/tonemap_aces.js +41 -41
  216. package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
  217. package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
  218. package/src/core/dom/isImageBitmap.js +12 -12
  219. package/src/core/function/frameThrottle.js +23 -23
  220. package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
  221. package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
  222. package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
  223. package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
  224. package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
  225. package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
  226. package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
  227. package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
  228. package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
  229. package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
  230. package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
  231. package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
  232. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
  233. package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
  234. package/src/core/geom/2d/intersect_ray_2d.js +7 -7
  235. package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
  236. package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
  237. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
  238. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
  239. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
  240. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
  241. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
  242. package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
  243. package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
  244. package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
  245. package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
  246. package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
  247. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
  248. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
  249. package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
  250. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
  251. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
  252. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
  253. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
  254. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
  255. package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
  256. package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
  257. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
  258. package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
  259. package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
  260. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
  261. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
  262. package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
  263. package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
  264. package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
  265. package/src/core/geom/2d/v2_morton_encode.js +23 -23
  266. package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
  267. package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
  268. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
  269. package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
  270. package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
  271. package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
  272. package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
  273. package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
  274. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
  275. package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
  276. package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
  277. package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
  278. package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
  279. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
  280. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
  281. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
  282. package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
  283. package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
  284. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
  285. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
  286. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
  287. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
  288. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
  289. package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
  290. package/src/core/geom/3d/atlas/Chart.js +77 -77
  291. package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
  292. package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
  293. package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
  294. package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
  295. package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
  296. package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
  297. package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
  298. package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
  299. package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
  300. package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
  301. package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
  302. package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
  303. package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
  304. package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
  305. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
  306. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
  307. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
  308. package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
  309. package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
  310. package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
  311. package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
  312. package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
  313. package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
  314. package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
  315. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
  316. package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
  317. package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
  318. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
  319. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
  320. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
  321. package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
  322. package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
  323. package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
  324. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
  325. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
  326. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
  327. package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
  328. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
  329. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
  330. package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
  331. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
  332. package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
  333. package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
  334. package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
  335. package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
  336. package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
  337. package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
  338. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
  339. package/src/core/geom/3d/gjk/gjk.js +430 -430
  340. package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
  341. package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
  342. package/src/core/geom/3d/gjk/mpr.js +368 -368
  343. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
  344. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
  345. package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
  346. package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
  347. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
  348. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
  349. package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
  350. package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
  351. package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
  352. package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
  353. package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
  354. package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
  355. package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
  356. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
  357. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
  358. package/src/core/geom/3d/mat4/m4_compose.js +22 -22
  359. package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
  360. package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
  361. package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
  362. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
  363. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
  364. package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
  365. package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
  366. package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
  367. package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
  368. package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
  369. package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
  370. package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
  371. package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
  372. package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
  373. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
  374. package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
  375. package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
  376. package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
  377. package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
  378. package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
  379. package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
  380. package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
  381. package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
  382. package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
  383. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
  384. package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
  385. package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
  386. package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
  387. package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
  388. package/src/core/geom/3d/point/point3_raycast.js +47 -0
  389. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
  390. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
  391. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
  392. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
  393. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
  394. package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
  395. package/src/core/geom/3d/quadric/Quadric3.js +282 -282
  396. package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
  397. package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
  398. package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
  399. package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
  400. package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
  401. package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
  402. package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
  403. package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
  404. package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
  405. package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
  406. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
  407. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
  408. package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
  409. package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
  410. package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
  411. package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
  412. package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
  413. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
  414. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
  415. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
  416. package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
  417. package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
  418. package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
  419. package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
  420. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
  421. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  422. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
  423. package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
  424. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  425. package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
  426. package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
  427. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  428. package/src/core/geom/3d/shape/PointShape3D.js +88 -63
  429. package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
  430. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  431. package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
  432. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
  433. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  434. package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
  435. package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
  436. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  437. package/src/core/geom/3d/shape/Triangle3D.js +26 -0
  438. package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
  439. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  440. package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
  441. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
  442. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
  443. package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
  444. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
  445. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
  446. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
  447. package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
  448. package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
  449. package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
  450. package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
  451. package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
  452. package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
  453. package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
  454. package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
  455. package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
  456. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
  457. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
  458. package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
  459. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
  460. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
  461. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
  462. package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
  463. package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
  464. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
  465. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
  466. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
  467. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
  468. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
  469. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
  470. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
  471. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
  472. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
  473. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
  474. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
  475. package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
  476. package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
  477. package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
  478. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
  479. package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
  480. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
  481. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
  482. package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
  483. package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
  484. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
  485. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
  486. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
  487. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
  488. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
  489. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
  490. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
  491. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
  492. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
  493. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
  494. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
  495. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
  496. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
  497. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
  498. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
  499. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
  500. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
  501. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
  502. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
  503. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
  504. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
  505. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
  506. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
  507. package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
  508. package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
  509. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
  510. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
  511. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
  512. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
  513. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
  514. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
  515. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
  516. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
  517. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
  518. package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
  519. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
  520. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
  521. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
  522. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
  523. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
  524. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
  525. package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
  526. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
  527. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
  528. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
  529. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
  530. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
  531. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
  532. package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
  533. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
  534. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
  535. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
  536. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
  537. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
  538. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
  539. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
  540. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
  541. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
  542. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
  543. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
  544. package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
  545. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
  546. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
  547. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
  548. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
  549. package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
  550. package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
  551. package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
  552. package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
  553. package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
  554. package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
  555. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
  556. package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
  557. package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
  558. package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
  559. package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
  560. package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
  561. package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
  562. package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
  563. package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
  564. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
  565. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
  566. package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
  567. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
  568. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
  569. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
  570. package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
  571. package/src/core/geom/mat3/m3_cm_invert.js +7 -7
  572. package/src/core/geom/mat3/m3_make_rotation.js +5 -5
  573. package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
  574. package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
  575. package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
  576. package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
  577. package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
  578. package/src/core/geom/packing/max-rect/cutArea.js +7 -7
  579. package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
  580. package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
  581. package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
  582. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
  583. package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
  584. package/src/core/geom/random/randomPointInBox.js +7 -7
  585. package/src/core/geom/random/randomPointInCircle.js +7 -7
  586. package/src/core/geom/random/randomPointInPoint.js +7 -7
  587. package/src/core/geom/random/randomPointInSphere.js +7 -7
  588. package/src/core/geom/random/randomPointOnBox.js +7 -7
  589. package/src/core/geom/random/randomPointOnSphere.js +7 -7
  590. package/src/core/geom/random/random_orthonormal_basis.js +31 -31
  591. package/src/core/geom/vec/vector_axpy.js +15 -15
  592. package/src/core/geom/vec/vector_axpy_offset.js +20 -20
  593. package/src/core/geom/vec/vector_copy.js +7 -7
  594. package/src/core/geom/vec/vector_dot_offset.js +25 -25
  595. package/src/core/geom/vec/vector_scale_array.js +7 -7
  596. package/src/core/geom/vec2/v2_rotate.js +21 -21
  597. package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
  598. package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
  599. package/src/core/geom/vec3/v3_angle_between.js +7 -7
  600. package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
  601. package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
  602. package/src/core/geom/vec3/v3_array_centroid.js +7 -7
  603. package/src/core/geom/vec3/v3_array_copy.js +7 -7
  604. package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
  605. package/src/core/geom/vec3/v3_array_equals.js +7 -7
  606. package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
  607. package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
  608. package/src/core/geom/vec3/v3_array_normalize.js +7 -7
  609. package/src/core/geom/vec3/v3_array_scale.js +7 -7
  610. package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
  611. package/src/core/geom/vec3/v3_cotangent.js +33 -33
  612. package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
  613. package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
  614. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
  615. package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
  616. package/src/core/geom/vec3/v3_multiply.js +23 -23
  617. package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
  618. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
  619. package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
  620. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
  621. package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
  622. package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
  623. package/src/core/geom/vec3/v3_reflect.js +24 -24
  624. package/src/core/geom/vec3/v3_scale_array.js +18 -18
  625. package/src/core/geom/vec3/v3_triple_cross.js +45 -45
  626. package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
  627. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
  628. package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
  629. package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
  630. package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
  631. package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
  632. package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
  633. package/src/core/graph/graph_k_means_cluster.js +250 -250
  634. package/src/core/graph/layout/CircleLayout.js +1 -1
  635. package/src/core/graph/layout/box/BoxLayouter.js +1 -1
  636. package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
  637. package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
  638. package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
  639. package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
  640. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
  641. package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
  642. package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
  643. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
  644. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
  645. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
  646. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
  647. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
  648. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
  649. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
  650. package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
  651. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
  652. package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
  653. package/src/core/graph/metis/native/refine/project_kway.js +43 -43
  654. package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
  655. package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
  656. package/src/core/math/complex/complex_horner_eval.js +31 -31
  657. package/src/core/math/computeWholeDivisorLow.js +33 -33
  658. package/src/core/math/f32_next_down.js +24 -24
  659. package/src/core/math/f32_next_up.js +27 -27
  660. package/src/core/math/hash/hash_mix2.js +22 -22
  661. package/src/core/math/hash/hash_mix3.js +19 -19
  662. package/src/core/math/linalg/cg/cg_solve.js +106 -106
  663. package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
  664. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
  665. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
  666. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
  667. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
  668. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
  669. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
  670. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
  671. package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
  672. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  673. package/src/core/math/linalg/polynomial_add_into.js +29 -29
  674. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
  675. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
  676. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
  677. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
  678. package/src/core/math/linalg/polynomial_multiply.js +41 -41
  679. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
  680. package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
  681. package/src/core/math/linalg/polynomial_scale_into.js +17 -17
  682. package/src/core/math/linalg/polynomial_sub_into.js +29 -29
  683. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
  684. package/src/core/math/linalg/sor_optimal_omega.js +42 -42
  685. package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
  686. package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
  687. package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
  688. package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
  689. package/src/core/math/lookup/ParameterLookupTable.js +495 -495
  690. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
  691. package/src/core/math/noise/curl_noise_3d.js +18 -18
  692. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
  693. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
  694. package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
  695. package/src/core/math/random/randomSeed.js +16 -16
  696. package/src/core/math/random/random_pick_weighted_index.js +26 -26
  697. package/src/core/math/round_half_away_from_zero.js +13 -13
  698. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
  699. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
  700. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
  701. package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
  702. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
  703. package/src/core/math/statistics/hammersley_sequence.js +53 -53
  704. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  705. package/src/core/path/convertPathToURL.js +107 -107
  706. package/src/core/process/delay.js +16 -16
  707. package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
  708. package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
  709. package/src/core/science/units/NamedUnit.js +48 -48
  710. package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
  711. package/src/core/science/units/UnitDimension.js +28 -28
  712. package/src/core/science/units/format_quantity.js +74 -74
  713. package/src/core/science/units/magnitude_prefix.js +127 -127
  714. package/src/core/science/units/si/AMPERE.js +13 -13
  715. package/src/core/science/units/si/CANDELA.js +13 -13
  716. package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
  717. package/src/core/science/units/si/KELVIN.js +13 -13
  718. package/src/core/science/units/si/KILOGRAM.js +13 -13
  719. package/src/core/science/units/si/METER.js +13 -13
  720. package/src/core/science/units/si/MOLE.js +13 -13
  721. package/src/core/science/units/si/SECOND.js +13 -13
  722. package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
  723. package/src/core/science/units/si/derived/COULOMB.js +13 -13
  724. package/src/core/science/units/si/derived/FARAD.js +13 -13
  725. package/src/core/science/units/si/derived/GRAY.js +16 -16
  726. package/src/core/science/units/si/derived/HENRY.js +13 -13
  727. package/src/core/science/units/si/derived/HERTZ.js +16 -16
  728. package/src/core/science/units/si/derived/JOULE.js +13 -13
  729. package/src/core/science/units/si/derived/KATAL.js +13 -13
  730. package/src/core/science/units/si/derived/LUMEN.js +14 -14
  731. package/src/core/science/units/si/derived/LUX.js +13 -13
  732. package/src/core/science/units/si/derived/NEWTON.js +13 -13
  733. package/src/core/science/units/si/derived/OHM.js +13 -13
  734. package/src/core/science/units/si/derived/PASCAL.js +13 -13
  735. package/src/core/science/units/si/derived/SIEMENS.js +13 -13
  736. package/src/core/science/units/si/derived/SIEVERT.js +16 -16
  737. package/src/core/science/units/si/derived/TESLA.js +13 -13
  738. package/src/core/science/units/si/derived/VOLT.js +13 -13
  739. package/src/core/science/units/si/derived/WATT.js +13 -13
  740. package/src/core/science/units/si/derived/WEBER.js +13 -13
  741. package/src/core/science/units/unit_matrix_to_string.js +403 -403
  742. package/src/engine/Engine.js +726 -726
  743. package/src/engine/EngineHarness.js +590 -590
  744. package/src/engine/REVIEW_2026_07_03.md +211 -211
  745. package/src/engine/animation/AnimatedActions.js +81 -81
  746. package/src/engine/animation/AnimationUtils.js +252 -252
  747. package/src/engine/animation/async/TimeSeries.js +300 -300
  748. package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
  749. package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
  750. package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
  751. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
  752. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
  753. package/src/engine/asset/GameAssetType.d.ts +0 -1
  754. package/src/engine/asset/GameAssetType.js +0 -1
  755. package/src/engine/asset/load_model_scene_bundle.js +27 -27
  756. package/src/engine/asset/loaders/font/FontAsset.js +21 -21
  757. package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
  758. package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
  759. package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
  760. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
  761. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
  762. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
  763. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
  764. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
  765. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
  766. package/src/engine/control/first-person/DESIGN.md +640 -640
  767. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
  768. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
  769. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
  770. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
  771. package/src/engine/control/first-person/math/Spring.js +71 -71
  772. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
  773. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
  774. package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
  775. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
  776. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
  777. package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
  778. package/src/engine/control/first-person/math/stepTowards.js +20 -20
  779. package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
  780. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
  781. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
  782. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
  783. package/src/engine/ecs/EntityManager.d.ts +7 -3
  784. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  785. package/src/engine/ecs/EntityManager.js +10 -5
  786. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  787. package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
  788. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
  789. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
  790. package/src/engine/ecs/async/WorkerSystem.js +702 -702
  791. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
  792. package/src/engine/ecs/fow/FogOfWar.js +707 -707
  793. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
  794. package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
  795. package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
  796. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
  797. package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
  798. package/src/engine/ecs/ik/IKProblem.js +42 -42
  799. package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
  800. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
  801. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
  802. package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
  803. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
  804. package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
  805. package/src/engine/ecs/parent/EntityNode.js +2 -2
  806. package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
  807. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  808. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  809. package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
  810. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
  811. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
  812. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
  813. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
  814. package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
  815. package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
  816. package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
  817. package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
  818. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
  819. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
  820. package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
  821. package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
  822. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
  823. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  824. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  825. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
  826. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
  827. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
  828. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
  829. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
  830. package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
  831. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
  832. package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
  833. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  834. package/src/engine/ecs/transform/Transform64.js +814 -814
  835. package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
  836. package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
  837. package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
  838. package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
  839. package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
  840. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
  841. package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
  842. package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
  843. package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
  844. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
  845. package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
  846. package/src/engine/graphics/FrameThrottle.js +2 -2
  847. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
  848. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
  849. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
  850. package/src/engine/graphics/ecs/camera/Camera.js +165 -165
  851. package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
  852. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  853. package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
  854. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
  855. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
  856. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
  857. package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
  858. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
  859. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
  860. package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
  861. package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
  862. package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
  863. package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
  864. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  865. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
  866. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
  867. package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
  868. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
  869. package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
  870. package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
  871. package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
  872. package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
  873. package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
  874. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
  875. package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
  876. package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
  877. package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
  878. package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
  879. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
  880. package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
  881. package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
  882. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
  883. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
  884. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
  885. package/src/engine/graphics/ecs/water/Water.js +104 -104
  886. package/src/engine/graphics/geometry/AttributeData.js +84 -84
  887. package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
  888. package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
  889. package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
  890. package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
  891. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
  892. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
  893. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
  894. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
  895. package/src/engine/graphics/impostors/voxel/README.md +149 -149
  896. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
  897. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
  898. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
  899. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  900. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
  901. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  902. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
  903. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  904. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
  905. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
  906. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
  907. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  908. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
  909. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  910. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
  911. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  912. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
  913. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  914. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
  915. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  916. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
  917. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  918. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
  919. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  920. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
  921. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  922. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
  923. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  924. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
  925. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  926. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
  927. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  928. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
  929. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  930. package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
  931. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
  932. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
  933. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  934. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
  935. package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
  936. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
  937. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
  938. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  939. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
  940. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
  941. package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
  942. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
  943. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
  944. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
  945. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  946. package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
  947. package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
  948. package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
  949. package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
  950. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
  951. package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
  952. package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
  953. package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
  954. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
  955. package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
  956. package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
  957. package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
  958. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
  959. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
  960. package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
  961. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
  962. package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
  963. package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
  964. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
  965. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
  966. package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
  967. package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
  968. package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
  969. package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
  970. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
  971. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
  972. package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
  973. package/src/engine/graphics/texture/isImageBitmap.js +2 -2
  974. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
  975. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
  976. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  977. package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
  978. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
  979. package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
  980. package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
  981. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
  982. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
  983. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
  984. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
  985. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
  986. package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
  987. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
  988. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
  989. package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
  990. package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
  991. package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
  992. package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
  993. package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
  994. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
  995. package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
  996. package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
  997. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
  998. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
  999. package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
  1000. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
  1001. package/src/engine/graphics/texture/shade_image_preview.js +103 -103
  1002. package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
  1003. package/src/engine/graphics/trail/tube/TubeX.js +573 -573
  1004. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
  1005. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
  1006. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
  1007. package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
  1008. package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
  1009. package/src/engine/graphics3/AnimationSystem3.js +352 -352
  1010. package/src/engine/graphics3/CameraSystem3.js +170 -170
  1011. package/src/engine/graphics3/DecalSystem3.js +441 -441
  1012. package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
  1013. package/src/engine/graphics3/GraphicsEngine3.js +776 -776
  1014. package/src/engine/graphics3/HighlightSystem3.js +241 -241
  1015. package/src/engine/graphics3/LightSystem3.js +211 -211
  1016. package/src/engine/graphics3/MeshSystem3.js +646 -646
  1017. package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
  1018. package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
  1019. package/src/engine/graphics3/PickingQuery.js +48 -48
  1020. package/src/engine/graphics3/PickingResult.js +47 -47
  1021. package/src/engine/graphics3/PickingSystem3.js +273 -273
  1022. package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
  1023. package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
  1024. package/src/engine/graphics3/TerrainSystem3.js +566 -566
  1025. package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
  1026. package/src/engine/graphics3/Trail3DSystem3.js +221 -221
  1027. package/src/engine/graphics3/WaterSystem3.js +293 -293
  1028. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  1029. package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
  1030. package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
  1031. package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
  1032. package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
  1033. package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
  1034. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
  1035. package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
  1036. package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
  1037. package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
  1038. package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
  1039. package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
  1040. package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
  1041. package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
  1042. package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
  1043. package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
  1044. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
  1045. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
  1046. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
  1047. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
  1048. package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
  1049. package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
  1050. package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
  1051. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  1052. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  1053. package/src/engine/graphics3/load_cube_environment.js +68 -68
  1054. package/src/engine/graphics3/make_default_environment.js +107 -107
  1055. package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
  1056. package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
  1057. package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
  1058. package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
  1059. package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
  1060. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  1061. package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
  1062. package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
  1063. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  1064. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  1065. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
  1066. package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
  1067. package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
  1068. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
  1069. package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
  1070. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
  1071. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
  1072. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  1073. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1074. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1075. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1076. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1077. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1078. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1079. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1080. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1081. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1082. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1083. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1084. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1085. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1086. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1087. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1088. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1089. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1090. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1091. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1092. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1093. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1094. package/src/engine/input/ecs/components/Input.js +118 -118
  1095. package/src/engine/input/ecs/components/InputController.js +78 -78
  1096. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1097. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1098. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1099. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1100. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1101. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1102. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1103. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1104. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1105. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1106. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1107. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1108. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1109. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1110. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1111. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1112. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1113. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1114. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1115. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1116. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1117. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1118. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1119. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1120. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1121. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1122. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1123. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1124. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1125. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1126. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1127. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1128. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1129. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1130. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1131. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1132. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1133. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1134. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1135. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1136. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1137. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1138. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1139. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1140. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1141. package/src/engine/interpolation/Interpoland.js +49 -49
  1142. package/src/engine/interpolation/Interpolated.js +149 -149
  1143. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1144. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1145. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1146. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1147. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1148. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1149. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1150. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1151. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1152. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1153. package/src/engine/network/README.md +52 -52
  1154. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1155. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1156. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1157. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1158. package/src/engine/network/convertPathToURL.js +2 -2
  1159. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1160. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1161. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1162. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1163. package/src/engine/network/core/sequence/seq16.js +69 -69
  1164. package/src/engine/network/core/sequence/seq32.js +73 -73
  1165. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1166. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1167. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1168. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1169. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1170. package/src/engine/network/sim/ActionLog.js +152 -152
  1171. package/src/engine/network/sim/SimAction.js +273 -273
  1172. package/src/engine/network/sim/Snapshotter.js +222 -222
  1173. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1174. package/src/engine/network/state/InputRing.js +134 -134
  1175. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1176. package/src/engine/network/time/TimeDilation.js +62 -62
  1177. package/src/engine/network/transport/Channel.js +336 -336
  1178. package/src/engine/network/transport/Transport.js +203 -203
  1179. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1180. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1181. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1182. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1183. package/src/engine/network/xhr.js +23 -23
  1184. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1185. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1186. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1187. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1188. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1189. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1190. package/src/engine/physics/PLAN.md +1120 -1094
  1191. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1192. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1193. package/src/engine/physics/REVIEW_002.md +151 -151
  1194. package/src/engine/physics/REVIEW_003.md +166 -166
  1195. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1196. package/src/engine/physics/constraint/DofMode.js +35 -35
  1197. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1198. package/src/engine/physics/contact/combine_material.js +35 -35
  1199. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1200. package/src/engine/physics/ecs/Collider.js +177 -177
  1201. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1202. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1203. package/src/engine/physics/ecs/Joint.js +475 -475
  1204. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1205. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1206. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1207. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1208. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1209. package/src/engine/physics/ecs/SleepState.js +12 -12
  1210. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1211. package/src/engine/physics/fluid/FluidField.js +867 -867
  1212. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1213. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1214. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1215. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1216. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1217. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1218. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1219. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1220. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1221. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1222. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1223. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1224. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1225. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1226. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1227. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1228. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1229. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1230. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1231. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1232. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1233. package/src/engine/physics/fluid/prototype.js +126 -126
  1234. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1235. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1236. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1237. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1238. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1239. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1240. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1241. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1242. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1243. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1244. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1245. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1246. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1247. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1248. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1249. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1250. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1251. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1252. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1253. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1254. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1255. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1256. package/src/engine/physics/integration/integrate_position.js +97 -97
  1257. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1258. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1259. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1260. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1261. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1262. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1263. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1264. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1265. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1266. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1267. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1268. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1269. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1270. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1271. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1272. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1273. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1274. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1275. package/src/engine/physics/queries/raycast.js +11 -5
  1276. package/src/engine/physics/queries/shape_cast.js +417 -417
  1277. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1278. package/src/engine/physics/solver/friction_cone.js +37 -37
  1279. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1280. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1281. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1282. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1283. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1284. package/src/engine/scene/transitionToScene.js +121 -121
  1285. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1286. package/src/engine/simulation/Ticker.js +33 -0
  1287. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1288. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1289. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1290. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1291. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1292. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1293. package/src/engine/sound/simulation/README.md +277 -277
  1294. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1295. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1296. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1297. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1298. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1299. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1300. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1301. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1302. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1303. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1304. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1305. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1306. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1307. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1308. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1309. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1310. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1311. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1312. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1313. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1314. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1315. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1316. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1317. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1318. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1319. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1320. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1321. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1322. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1323. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1324. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1325. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1326. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1327. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1328. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1329. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1330. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1331. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1332. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1333. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1334. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1335. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1336. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1337. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1338. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1339. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1340. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1341. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1342. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1343. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1344. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1345. package/src/engine/sound/sopra/README.md +643 -643
  1346. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1347. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1348. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1349. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1350. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1351. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1352. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1353. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1354. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1355. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1356. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1357. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1358. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1359. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1360. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1361. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1363. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1364. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1365. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1366. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1367. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1368. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1370. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1371. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1372. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1373. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1374. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1375. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1376. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1377. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1378. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1379. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1380. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1381. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1382. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1383. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1384. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1385. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1386. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1387. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1388. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1389. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1390. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1391. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1392. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1393. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1394. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1395. package/src/engine/ui/DraggableAspect.js +115 -115
  1396. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1397. package/src/generation/COORDINATES.md +54 -54
  1398. package/src/generation/GridGenerationResource.js +24 -24
  1399. package/src/generation/GridTaskGroup.js +214 -214
  1400. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1401. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1402. package/src/generation/automata/CellularAutomata.js +22 -22
  1403. package/src/generation/compute_writes_union.js +25 -25
  1404. package/src/generation/filtering/CellFilter.js +117 -117
  1405. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1406. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1407. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1408. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1409. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1410. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1411. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1412. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1413. package/src/generation/grid/GridData.js +233 -233
  1414. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1415. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1416. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1417. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1418. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1419. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1420. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1421. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1422. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1423. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1424. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1425. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1426. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1427. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1428. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1429. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1430. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1431. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1432. package/src/generation/placement/action/GridCellAction.js +45 -45
  1433. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1434. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1435. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1436. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1437. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1438. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1439. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1440. package/src/generation/theme/ThemeEngine.js +666 -666
  1441. package/src/shade/RENDERER_CONTRACT.md +568 -0
  1442. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1443. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1444. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1445. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1446. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1447. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1448. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1449. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1450. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1451. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1452. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1453. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1454. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1455. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1456. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1457. package/src/shade/device/mock/texture_copy.js +287 -287
  1458. package/src/shade/device/timing/GPUTimer.js +285 -285
  1459. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1460. package/src/shade/playground/add_random_lights.js +49 -49
  1461. package/src/shade/playground/add_random_meshes.js +83 -83
  1462. package/src/shade/playground/animation_prototype.js +508 -508
  1463. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1464. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1465. package/src/shade/playground/enable_picker.js +59 -59
  1466. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1467. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1468. package/src/shade/playground/gltf_viewer.js +282 -282
  1469. package/src/shade/playground/load_default_environment.js +19 -19
  1470. package/src/shade/playground/main.js +2727 -2727
  1471. package/src/shade/playground/make_animated_instances.js +117 -117
  1472. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1473. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1474. package/src/shade/playground/sample.js +234 -234
  1475. package/src/shade/playground/sample_load_gltf.js +102 -102
  1476. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1477. package/src/shade/renderer/Renderer.js +2436 -2436
  1478. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1479. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1480. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1481. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1482. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1483. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1484. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1485. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1486. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1487. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1488. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1489. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1490. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1491. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1492. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1493. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1494. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1495. package/src/shade/renderer/camera/Camera.js +281 -281
  1496. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1497. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1498. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1499. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1500. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1501. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1502. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1503. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1504. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1505. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1506. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1507. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1508. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1509. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1510. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1511. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1512. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1513. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1514. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1515. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1516. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1517. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1518. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1519. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1520. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1521. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1522. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1523. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1524. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1525. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1526. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1527. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1528. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1529. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1530. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1531. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1532. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1533. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1534. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1535. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1536. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1537. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1538. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1539. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1540. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1541. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1542. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1543. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1544. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1545. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1546. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1547. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1548. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1549. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1550. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1551. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1552. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1553. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1554. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1555. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1556. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1557. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1558. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1559. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1560. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1561. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1562. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1563. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1564. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1565. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1566. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1567. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1568. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1569. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1570. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1571. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1572. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1573. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1574. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1575. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1576. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1577. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1578. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1579. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1580. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1581. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1582. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1583. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1584. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1585. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1586. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1587. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1588. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1589. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1590. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1591. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1592. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1593. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1594. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1595. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1596. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1597. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1598. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1599. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1600. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1601. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1602. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1603. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1604. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1605. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1606. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1607. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1608. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1609. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1610. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1611. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1612. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1613. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1616. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1617. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1618. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1619. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1620. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1621. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1622. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1623. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1624. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1626. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1627. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1628. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1629. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1630. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1631. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1632. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1633. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1634. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1635. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1636. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1637. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1638. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1639. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1640. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1641. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1642. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1643. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1644. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1645. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1646. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1647. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1648. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1649. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1650. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1651. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1652. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1653. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1654. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1655. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1656. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1657. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1658. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1659. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1660. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1661. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1662. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1663. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1664. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1665. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1666. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1667. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1668. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1669. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1670. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1671. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1672. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1673. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1674. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1675. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1676. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1677. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1678. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1679. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1680. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1681. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1682. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1683. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1684. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1685. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1686. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1687. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1688. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1689. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1690. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1691. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1692. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1693. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1694. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1695. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1696. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1697. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1698. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1699. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1700. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1701. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1702. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1703. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1704. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1705. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1706. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1707. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1708. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1709. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1710. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1711. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1712. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1713. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1714. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1715. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1716. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1717. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1718. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1719. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1720. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1721. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1722. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1723. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1724. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1725. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1726. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1727. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1728. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1729. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1730. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1731. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1732. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1733. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1734. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1735. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1736. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1737. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1738. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1739. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1740. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1741. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1742. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1743. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1744. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1745. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1746. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1747. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1748. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1749. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1750. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1751. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1752. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1753. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1754. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1755. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1756. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1757. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1758. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1759. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1760. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1761. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1762. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1763. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1764. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1765. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1766. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1767. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1768. package/src/shade/renderer/water/shader_water.js +246 -246
  1769. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1770. package/src/shade/vitest.setup.mjs +89 -89
  1771. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1772. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1773. package/src/view/controller/controls/Vector2Control.js +83 -83
  1774. package/src/view/controller/controls/Vector3Control.js +90 -90
  1775. package/src/view/elements/Group.js +56 -56
  1776. package/src/view/elements/drawCheckers.js +33 -33
  1777. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1778. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1779. package/src/view/layout/DockPanel.js +51 -51
  1780. package/src/view/layout/SplitView.js +365 -365
  1781. package/src/view/layout/show_popup_menu.js +81 -81
  1782. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1783. package/src/view/minimap/MinimapImageCache.js +88 -88
  1784. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1785. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1786. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1787. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1788. package/src/view/minimap/minimap_focus_area.js +97 -97
  1789. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1790. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1791. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1792. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1793. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1794. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1795. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1796. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1797. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1798. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1799. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1800. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1801. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1802. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1803. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1804. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1805. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1806. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1807. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1808. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1809. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1810. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1811. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1812. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1813. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1814. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1815. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1816. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1817. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1818. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1819. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1820. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1821. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1822. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1823. 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).