@the-inclusionist/engine 9.0.0 → 11.0.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 (532) hide show
  1. package/README.md +120 -78
  2. package/app/css/style.css +761 -377
  3. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  10. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  11. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  12. package/app/public/vendor/fonts.css +36 -84
  13. package/dist-pkg/boot/create-game.d.ts +499 -219
  14. package/dist-pkg/boot/create-game.js +3944 -562
  15. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  16. package/dist-pkg/core/a11y-sr.js +38 -19
  17. package/dist-pkg/core/accessible-label.d.ts +17 -0
  18. package/dist-pkg/core/accessible-label.js +37 -0
  19. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  20. package/dist-pkg/core/accommodation-subjects.js +32 -0
  21. package/dist-pkg/core/accommodations.d.ts +125 -0
  22. package/dist-pkg/core/accommodations.js +195 -0
  23. package/dist-pkg/core/actions.d.ts +108 -55
  24. package/dist-pkg/core/actions.js +122 -71
  25. package/dist-pkg/core/calm-mode.d.ts +31 -0
  26. package/dist-pkg/core/calm-mode.js +47 -0
  27. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  28. package/dist-pkg/core/camera-cycle.js +7 -0
  29. package/dist-pkg/core/caption-duration.d.ts +15 -0
  30. package/dist-pkg/core/caption-duration.js +19 -0
  31. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  32. package/dist-pkg/core/cartridge-problems.js +53 -0
  33. package/dist-pkg/core/constants.d.ts +0 -54
  34. package/dist-pkg/core/constants.js +9 -76
  35. package/dist-pkg/core/contract.d.ts +142 -164
  36. package/dist-pkg/core/contract.js +222 -208
  37. package/dist-pkg/core/dom-query.d.ts +4 -4
  38. package/dist-pkg/core/dom-query.js +12 -20
  39. package/dist-pkg/core/entity.d.ts +62 -77
  40. package/dist-pkg/core/entity.js +18 -32
  41. package/dist-pkg/core/escape-html.d.ts +9 -10
  42. package/dist-pkg/core/escape-html.js +16 -20
  43. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  44. package/dist-pkg/core/flash-threshold.js +113 -0
  45. package/dist-pkg/core/game-speed.d.ts +11 -0
  46. package/dist-pkg/core/game-speed.js +16 -0
  47. package/dist-pkg/core/genres.d.ts +20 -0
  48. package/dist-pkg/core/genres.js +44 -0
  49. package/dist-pkg/core/i18n.d.ts +71 -60
  50. package/dist-pkg/core/i18n.js +186 -200
  51. package/dist-pkg/core/loop.d.ts +17 -6
  52. package/dist-pkg/core/loop.js +26 -26
  53. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  54. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  55. package/dist-pkg/core/ring.d.ts +17 -0
  56. package/dist-pkg/core/ring.js +29 -0
  57. package/dist-pkg/core/rng.d.ts +7 -12
  58. package/dist-pkg/core/rng.js +23 -53
  59. package/dist-pkg/core/route.d.ts +26 -26
  60. package/dist-pkg/core/route.js +126 -121
  61. package/dist-pkg/core/scenes.d.ts +37 -40
  62. package/dist-pkg/core/scenes.js +41 -43
  63. package/dist-pkg/core/screens.d.ts +4 -4
  64. package/dist-pkg/core/screens.js +11 -16
  65. package/dist-pkg/core/session-clock.d.ts +23 -0
  66. package/dist-pkg/core/session-clock.js +37 -0
  67. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  68. package/dist-pkg/core/setting-defaults.js +66 -0
  69. package/dist-pkg/core/speech-rate.d.ts +29 -0
  70. package/dist-pkg/core/speech-rate.js +47 -0
  71. package/dist-pkg/core/state.d.ts +118 -118
  72. package/dist-pkg/core/state.js +322 -297
  73. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  74. package/dist-pkg/core/visual-cycles.js +52 -0
  75. package/dist-pkg/core/visual-state.d.ts +13 -0
  76. package/dist-pkg/core/visual-state.js +6 -0
  77. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  78. package/dist-pkg/educational/activities-registry.js +9 -8
  79. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  80. package/dist-pkg/educational/adaptive-engine.js +17 -17
  81. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  82. package/dist-pkg/educational/segment-bar.js +9 -9
  83. package/dist-pkg/i18n/en.js +324 -223
  84. package/dist-pkg/i18n/es.js +323 -223
  85. package/dist-pkg/i18n/pt.js +348 -226
  86. package/dist-pkg/input/default-bindings.d.ts +37 -38
  87. package/dist-pkg/input/default-bindings.js +127 -144
  88. package/dist-pkg/input/devices.d.ts +7 -7
  89. package/dist-pkg/input/devices.js +17 -16
  90. package/dist-pkg/input/edges.d.ts +23 -32
  91. package/dist-pkg/input/edges.js +23 -28
  92. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  93. package/dist-pkg/input/empathy-filter.js +34 -0
  94. package/dist-pkg/input/face-map.d.ts +96 -0
  95. package/dist-pkg/input/face-map.js +255 -0
  96. package/dist-pkg/input/face-signals.d.ts +40 -0
  97. package/dist-pkg/input/face-signals.js +48 -0
  98. package/dist-pkg/input/gamepad.d.ts +195 -145
  99. package/dist-pkg/input/gamepad.js +383 -556
  100. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  101. package/dist-pkg/input/gaze-cycle.js +133 -0
  102. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  103. package/dist-pkg/input/gaze-relative.js +142 -0
  104. package/dist-pkg/input/hand-map.d.ts +28 -0
  105. package/dist-pkg/input/hand-map.js +142 -0
  106. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  107. package/dist-pkg/input/input-cooldown.js +39 -0
  108. package/dist-pkg/input/key-default.d.ts +30 -0
  109. package/dist-pkg/input/key-default.js +60 -0
  110. package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
  111. package/dist-pkg/input/keyboard-runtime.js +23 -27
  112. package/dist-pkg/input/keyboard.d.ts +68 -63
  113. package/dist-pkg/input/keyboard.js +83 -105
  114. package/dist-pkg/input/keydown.d.ts +123 -137
  115. package/dist-pkg/input/keydown.js +284 -289
  116. package/dist-pkg/input/latch-edge.d.ts +33 -21
  117. package/dist-pkg/input/latch-edge.js +34 -35
  118. package/dist-pkg/input/latch-scope.d.ts +51 -50
  119. package/dist-pkg/input/latch-scope.js +61 -82
  120. package/dist-pkg/input/latch-store.d.ts +26 -30
  121. package/dist-pkg/input/latch-store.js +42 -50
  122. package/dist-pkg/input/latch-sync.d.ts +29 -29
  123. package/dist-pkg/input/latch-sync.js +25 -25
  124. package/dist-pkg/input/latch.d.ts +14 -17
  125. package/dist-pkg/input/latch.js +29 -37
  126. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  127. package/dist-pkg/input/pad-defaults.js +21 -28
  128. package/dist-pkg/input/pad-reading.d.ts +76 -0
  129. package/dist-pkg/input/pad-reading.js +169 -0
  130. package/dist-pkg/input/pad-wizard.d.ts +99 -0
  131. package/dist-pkg/input/pad-wizard.js +256 -0
  132. package/dist-pkg/input/pointer-space.d.ts +19 -20
  133. package/dist-pkg/input/pointer-space.js +27 -32
  134. package/dist-pkg/input/pointer.d.ts +39 -40
  135. package/dist-pkg/input/pointer.js +42 -45
  136. package/dist-pkg/input/state.d.ts +101 -89
  137. package/dist-pkg/input/state.js +54 -134
  138. package/dist-pkg/input/switch-scan.d.ts +59 -0
  139. package/dist-pkg/input/switch-scan.js +67 -0
  140. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  141. package/dist-pkg/input/synthetic-source.js +57 -0
  142. package/dist-pkg/input/touch-bindings.d.ts +127 -116
  143. package/dist-pkg/input/touch-bindings.js +158 -173
  144. package/dist-pkg/input/touch.d.ts +147 -72
  145. package/dist-pkg/input/touch.js +362 -126
  146. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  147. package/dist-pkg/input/transport-in-use.js +130 -0
  148. package/dist-pkg/input/transports.d.ts +90 -117
  149. package/dist-pkg/input/transports.js +104 -106
  150. package/dist-pkg/input/virtual-controller.d.ts +82 -0
  151. package/dist-pkg/input/virtual-controller.js +69 -0
  152. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  153. package/dist-pkg/input/vocabulary-migration.js +80 -79
  154. package/dist-pkg/input/voice-map.d.ts +59 -0
  155. package/dist-pkg/input/voice-map.js +123 -0
  156. package/dist-pkg/platform/audio-ambient.d.ts +13 -2
  157. package/dist-pkg/platform/audio-ambient.js +14 -14
  158. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  159. package/dist-pkg/platform/audio-earcons.js +51 -42
  160. package/dist-pkg/platform/audio-jingles.js +9 -10
  161. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  162. package/dist-pkg/platform/audio-mixer.js +31 -39
  163. package/dist-pkg/platform/audio-sonar.d.ts +49 -152
  164. package/dist-pkg/platform/audio-sonar.js +124 -302
  165. package/dist-pkg/platform/audio.d.ts +62 -19
  166. package/dist-pkg/platform/audio.js +193 -168
  167. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  168. package/dist-pkg/platform/choose-by-voice.js +157 -0
  169. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  170. package/dist-pkg/platform/flash-sampler.js +79 -0
  171. package/dist-pkg/platform/font-library.d.ts +77 -0
  172. package/dist-pkg/platform/font-library.js +98 -0
  173. package/dist-pkg/platform/font-library.json +721 -0
  174. package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
  175. package/dist-pkg/platform/heavy-catalogue.js +256 -0
  176. package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
  177. package/dist-pkg/platform/heavy-mirror.js +73 -0
  178. package/dist-pkg/platform/heavy.d.ts +136 -0
  179. package/dist-pkg/platform/heavy.js +324 -0
  180. package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
  181. package/dist-pkg/platform/interruptible-speech.js +77 -54
  182. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  183. package/dist-pkg/platform/kokoro-port.js +50 -0
  184. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  185. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  186. package/dist-pkg/platform/kokoro.d.ts +70 -0
  187. package/dist-pkg/platform/kokoro.js +133 -0
  188. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  189. package/dist-pkg/platform/listener-scope.js +90 -0
  190. package/dist-pkg/platform/locale-host.d.ts +14 -0
  191. package/dist-pkg/platform/locale-host.js +45 -0
  192. package/dist-pkg/platform/microphone.d.ts +51 -0
  193. package/dist-pkg/platform/microphone.js +118 -0
  194. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  195. package/dist-pkg/platform/onnx-runtime.js +42 -0
  196. package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
  197. package/dist-pkg/platform/reading-in-worker.js +125 -0
  198. package/dist-pkg/platform/reading-model.d.ts +94 -0
  199. package/dist-pkg/platform/reading-model.js +272 -0
  200. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  201. package/dist-pkg/platform/reading-runtime.js +164 -0
  202. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  203. package/dist-pkg/platform/reading-worker.js +58 -0
  204. package/dist-pkg/platform/reading.d.ts +80 -0
  205. package/dist-pkg/platform/reading.js +136 -0
  206. package/dist-pkg/platform/speech-recognition.d.ts +60 -0
  207. package/dist-pkg/platform/speech-recognition.js +104 -0
  208. package/dist-pkg/platform/speech.d.ts +23 -1
  209. package/dist-pkg/platform/speech.js +29 -18
  210. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  211. package/dist-pkg/platform/storage-keys.js +106 -0
  212. package/dist-pkg/platform/storage.d.ts +46 -111
  213. package/dist-pkg/platform/storage.js +104 -169
  214. package/dist-pkg/platform/tts.d.ts +85 -39
  215. package/dist-pkg/platform/tts.js +447 -127
  216. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  217. package/dist-pkg/platform/vision-loop.js +56 -0
  218. package/dist-pkg/platform/vision.d.ts +132 -0
  219. package/dist-pkg/platform/vision.js +117 -0
  220. package/dist-pkg/platform/voice-listener.d.ts +40 -0
  221. package/dist-pkg/platform/voice-listener.js +88 -0
  222. package/dist-pkg/platform/voice-plan.d.ts +15 -98
  223. package/dist-pkg/platform/voice-plan.js +56 -143
  224. package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
  225. package/dist-pkg/platform/vosk-runtime.js +151 -0
  226. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  227. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  228. package/dist-pkg/render/canvas.d.ts +13 -7
  229. package/dist-pkg/render/canvas.js +13 -13
  230. package/dist-pkg/render/crt.d.ts +48 -12
  231. package/dist-pkg/render/crt.js +78 -80
  232. package/dist-pkg/render/cvd-matrices.d.ts +22 -18
  233. package/dist-pkg/render/cvd-matrices.js +37 -38
  234. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  235. package/dist-pkg/render/hc-role-data.js +14 -14
  236. package/dist-pkg/render/high-contrast.d.ts +67 -56
  237. package/dist-pkg/render/high-contrast.js +226 -217
  238. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  239. package/dist-pkg/render/low-vision-drawing.js +46 -0
  240. package/dist-pkg/render/lq-filter.d.ts +32 -20
  241. package/dist-pkg/render/lq-filter.js +59 -65
  242. package/dist-pkg/render/port.d.ts +86 -88
  243. package/dist-pkg/render/port.js +17 -23
  244. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  245. package/dist-pkg/render/screen-pipeline.js +80 -87
  246. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  247. package/dist-pkg/render/sprite-fx.js +16 -14
  248. package/dist-pkg/render/viewports.d.ts +20 -15
  249. package/dist-pkg/render/viewports.js +83 -114
  250. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  251. package/dist-pkg/render/viz-axes-labels.js +88 -0
  252. package/dist-pkg/render/viz-axes.d.ts +89 -86
  253. package/dist-pkg/render/viz-axes.js +128 -116
  254. package/dist-pkg/render/viz-modes.d.ts +28 -29
  255. package/dist-pkg/render/viz-modes.js +40 -45
  256. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  257. package/dist-pkg/render/viz-refusal.js +57 -0
  258. package/dist-pkg/render/viz-setters.d.ts +104 -66
  259. package/dist-pkg/render/viz-setters.js +182 -191
  260. package/dist-pkg/ui/aac-sets.d.ts +45 -0
  261. package/dist-pkg/ui/aac-sets.js +41 -0
  262. package/dist-pkg/ui/audio-choices.d.ts +68 -0
  263. package/dist-pkg/ui/audio-choices.js +98 -0
  264. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  265. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  266. package/dist-pkg/ui/camera-control.d.ts +53 -0
  267. package/dist-pkg/ui/camera-control.js +48 -0
  268. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  269. package/dist-pkg/ui/changed-mark.js +22 -53
  270. package/dist-pkg/ui/control-choices.d.ts +37 -0
  271. package/dist-pkg/ui/control-choices.js +84 -0
  272. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  273. package/dist-pkg/ui/debug-panel.js +68 -69
  274. package/dist-pkg/ui/declared-words.d.ts +21 -0
  275. package/dist-pkg/ui/declared-words.js +66 -0
  276. package/dist-pkg/ui/dom.d.ts +10 -14
  277. package/dist-pkg/ui/dom.js +10 -47
  278. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  279. package/dist-pkg/ui/drawing-problems.js +94 -0
  280. package/dist-pkg/ui/eye-control.d.ts +36 -0
  281. package/dist-pkg/ui/eye-control.js +178 -0
  282. package/dist-pkg/ui/face-control.d.ts +25 -0
  283. package/dist-pkg/ui/face-control.js +183 -0
  284. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  285. package/dist-pkg/ui/focus-trap.js +56 -65
  286. package/dist-pkg/ui/fonts.d.ts +157 -68
  287. package/dist-pkg/ui/fonts.js +305 -115
  288. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  289. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  290. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  291. package/dist-pkg/ui/footer-scroll.js +55 -0
  292. package/dist-pkg/ui/game-options.d.ts +47 -0
  293. package/dist-pkg/ui/game-options.js +158 -0
  294. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  295. package/dist-pkg/ui/gaze-overlay.js +165 -0
  296. package/dist-pkg/ui/hand-control.d.ts +25 -0
  297. package/dist-pkg/ui/hand-control.js +143 -0
  298. package/dist-pkg/ui/help-panel.d.ts +87 -0
  299. package/dist-pkg/ui/help-panel.js +242 -0
  300. package/dist-pkg/ui/hud-bands.d.ts +62 -0
  301. package/dist-pkg/ui/hud-bands.js +146 -0
  302. package/dist-pkg/ui/hud-row.d.ts +34 -0
  303. package/dist-pkg/ui/hud-row.js +80 -0
  304. package/dist-pkg/ui/hud.d.ts +73 -73
  305. package/dist-pkg/ui/hud.js +94 -98
  306. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  307. package/dist-pkg/ui/item-announcement.js +25 -26
  308. package/dist-pkg/ui/layout.d.ts +103 -28
  309. package/dist-pkg/ui/layout.js +117 -99
  310. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  311. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  312. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  313. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  314. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  315. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  316. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  317. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  318. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  319. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  320. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  321. package/dist-pkg/ui/libras-glosses.js +161 -0
  322. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  323. package/dist-pkg/ui/locale-flags.js +30 -0
  324. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  325. package/dist-pkg/ui/loop-crash.js +33 -62
  326. package/dist-pkg/ui/menu-intent.d.ts +61 -0
  327. package/dist-pkg/ui/menu-intent.js +88 -0
  328. package/dist-pkg/ui/menu-items.d.ts +20 -0
  329. package/dist-pkg/ui/menu-items.js +51 -0
  330. package/dist-pkg/ui/menu-nav.d.ts +98 -92
  331. package/dist-pkg/ui/menu-nav.js +398 -286
  332. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  333. package/dist-pkg/ui/mobility-choices.js +41 -0
  334. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  335. package/dist-pkg/ui/motion-choices.js +68 -0
  336. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  337. package/dist-pkg/ui/motion-scene.js +29 -61
  338. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  339. package/dist-pkg/ui/mount-panel.js +61 -0
  340. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  341. package/dist-pkg/ui/panel-shell.js +92 -66
  342. package/dist-pkg/ui/panel-widgets.d.ts +150 -0
  343. package/dist-pkg/ui/panel-widgets.js +294 -0
  344. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  345. package/dist-pkg/ui/pause-buttons.js +66 -0
  346. package/dist-pkg/ui/pause-icons.d.ts +261 -347
  347. package/dist-pkg/ui/pause-icons.js +671 -601
  348. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  349. package/dist-pkg/ui/pause-markup.js +125 -0
  350. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  351. package/dist-pkg/ui/reach-notice.js +55 -58
  352. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  353. package/dist-pkg/ui/scan-overlay.js +50 -0
  354. package/dist-pkg/ui/screen-text.d.ts +25 -0
  355. package/dist-pkg/ui/screen-text.js +152 -0
  356. package/dist-pkg/ui/session-clock.d.ts +32 -0
  357. package/dist-pkg/ui/session-clock.js +67 -0
  358. package/dist-pkg/ui/settings-aac.d.ts +70 -0
  359. package/dist-pkg/ui/settings-aac.js +208 -0
  360. package/dist-pkg/ui/settings-audio.d.ts +97 -114
  361. package/dist-pkg/ui/settings-audio.js +493 -425
  362. package/dist-pkg/ui/settings-controls.d.ts +53 -96
  363. package/dist-pkg/ui/settings-controls.js +143 -215
  364. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  365. package/dist-pkg/ui/settings-empathy.js +53 -60
  366. package/dist-pkg/ui/settings-mobility.d.ts +192 -0
  367. package/dist-pkg/ui/settings-mobility.js +332 -0
  368. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  369. package/dist-pkg/ui/settings-motion.js +287 -198
  370. package/dist-pkg/ui/settings-panel.d.ts +58 -45
  371. package/dist-pkg/ui/settings-panel.js +189 -94
  372. package/dist-pkg/ui/settings-typo.d.ts +29 -78
  373. package/dist-pkg/ui/settings-typo.js +158 -129
  374. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  375. package/dist-pkg/ui/settings-visual.js +240 -193
  376. package/dist-pkg/ui/shell.d.ts +112 -135
  377. package/dist-pkg/ui/shell.js +148 -217
  378. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  379. package/dist-pkg/ui/simulation-list.js +76 -0
  380. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  381. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  382. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  383. package/dist-pkg/ui/simulation-refusal.js +8 -56
  384. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  385. package/dist-pkg/ui/switchable-control.js +19 -0
  386. package/dist-pkg/ui/title.d.ts +3 -12
  387. package/dist-pkg/ui/title.js +49 -18
  388. package/dist-pkg/ui/top-band.d.ts +26 -0
  389. package/dist-pkg/ui/top-band.js +96 -0
  390. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  391. package/dist-pkg/ui/typo-choices.js +85 -0
  392. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  393. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  394. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  395. package/dist-pkg/ui/visual-choices.js +107 -0
  396. package/dist-pkg/ui/vlibras.d.ts +77 -10
  397. package/dist-pkg/ui/vlibras.js +130 -81
  398. package/dist-pkg/ui/voice-control.d.ts +68 -0
  399. package/dist-pkg/ui/voice-control.js +276 -0
  400. package/dist-pkg/ui/voice-settings.d.ts +95 -0
  401. package/dist-pkg/ui/voice-settings.js +352 -0
  402. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  403. package/dist-pkg/ui/where-the-child-is.js +70 -0
  404. package/docs/CREDITS.md +300 -44
  405. package/docs/LICENSES.md +189 -145
  406. package/package.json +51 -27
  407. package/scripts/check-cartridge.mjs +78 -0
  408. package/scripts/game-build.d.mts +25 -0
  409. package/scripts/game-build.mjs +144 -0
  410. package/scripts/heavy-into-the-delivery.mjs +388 -0
  411. package/scripts/libras-avatar.json +680 -0
  412. package/scripts/libras-avatar.mjs +246 -0
  413. package/scripts/libras-glosses/gloss.py +95 -0
  414. package/scripts/libras-glosses/pyproject.toml +32 -0
  415. package/scripts/libras-glosses/uv.lock +781 -0
  416. package/scripts/libras-glosses.mjs +351 -0
  417. package/scripts/licences/Apache-2.0.txt +176 -0
  418. package/scripts/licences/GPL-3.0.txt +674 -0
  419. package/scripts/licences/MIT.txt +17 -0
  420. package/scripts/licences/OFL-1.1.txt +86 -0
  421. package/scripts/licences/UFL-1.0.txt +96 -0
  422. package/scripts/licences/fonts.mjs +389 -0
  423. package/scripts/licences/third-party.mjs +301 -0
  424. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  425. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  427. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  428. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  429. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  431. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  433. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  435. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  437. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  439. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  440. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  441. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  443. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  444. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  445. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  446. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  447. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  448. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  449. package/dist-pkg/core/anel.d.ts +0 -17
  450. package/dist-pkg/core/anel.js +0 -31
  451. package/dist-pkg/core/collision.d.ts +0 -21
  452. package/dist-pkg/core/collision.js +0 -62
  453. package/dist-pkg/core/layers.d.ts +0 -63
  454. package/dist-pkg/core/layers.js +0 -95
  455. package/dist-pkg/core/letter-grid.d.ts +0 -55
  456. package/dist-pkg/core/letter-grid.js +0 -116
  457. package/dist-pkg/core/password.d.ts +0 -34
  458. package/dist-pkg/core/password.js +0 -205
  459. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  460. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  461. package/dist-pkg/core/run-state.d.ts +0 -115
  462. package/dist-pkg/core/run-state.js +0 -40
  463. package/dist-pkg/core/tiles.d.ts +0 -12
  464. package/dist-pkg/core/tiles.js +0 -61
  465. package/dist-pkg/core/world.d.ts +0 -4
  466. package/dist-pkg/core/world.js +0 -58
  467. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  468. package/dist-pkg/input/origem-sintetica.js +0 -60
  469. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  470. package/dist-pkg/input/transporte-em-uso.js +0 -130
  471. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  472. package/dist-pkg/platform/audio-nav.js +0 -99
  473. package/dist-pkg/platform/guide-intensity.d.ts +0 -40
  474. package/dist-pkg/platform/guide-intensity.js +0 -73
  475. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  476. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  477. package/dist-pkg/platform/pesados.d.ts +0 -31
  478. package/dist-pkg/platform/pesados.js +0 -75
  479. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  480. package/dist-pkg/platform/vozes-prontas.js +0 -61
  481. package/dist-pkg/render/camera.d.ts +0 -58
  482. package/dist-pkg/render/camera.js +0 -105
  483. package/dist-pkg/render/cenario-data.d.ts +0 -127
  484. package/dist-pkg/render/cenario-data.js +0 -145
  485. package/dist-pkg/render/city-tex.d.ts +0 -63
  486. package/dist-pkg/render/city-tex.js +0 -228
  487. package/dist-pkg/render/city-tiles.d.ts +0 -11
  488. package/dist-pkg/render/city-tiles.js +0 -140
  489. package/dist-pkg/render/draw.d.ts +0 -149
  490. package/dist-pkg/render/draw.js +0 -231
  491. package/dist-pkg/render/fx.d.ts +0 -63
  492. package/dist-pkg/render/fx.js +0 -123
  493. package/dist-pkg/render/minimap.d.ts +0 -11
  494. package/dist-pkg/render/minimap.js +0 -94
  495. package/dist-pkg/render/parallax.d.ts +0 -85
  496. package/dist-pkg/render/parallax.js +0 -158
  497. package/dist-pkg/render/player-anim.d.ts +0 -71
  498. package/dist-pkg/render/player-anim.js +0 -152
  499. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  500. package/dist-pkg/render/recycling-tex.js +0 -164
  501. package/dist-pkg/render/scene-city.d.ts +0 -77
  502. package/dist-pkg/render/scene-city.js +0 -181
  503. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  504. package/dist-pkg/render/scene-parallax.js +0 -426
  505. package/dist-pkg/render/scene-sky.d.ts +0 -126
  506. package/dist-pkg/render/scene-sky.js +0 -295
  507. package/dist-pkg/render/set-cenario.d.ts +0 -54
  508. package/dist-pkg/render/set-cenario.js +0 -103
  509. package/dist-pkg/render/textures.d.ts +0 -75
  510. package/dist-pkg/render/textures.js +0 -240
  511. package/dist-pkg/render/title-scene.d.ts +0 -46
  512. package/dist-pkg/render/title-scene.js +0 -75
  513. package/dist-pkg/render/weather.d.ts +0 -96
  514. package/dist-pkg/render/weather.js +0 -171
  515. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  516. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  517. package/dist-pkg/render/world-tex.d.ts +0 -18
  518. package/dist-pkg/render/world-tex.js +0 -87
  519. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  520. package/dist-pkg/ui/activities-menu.js +0 -648
  521. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  522. package/dist-pkg/ui/caa-sets.js +0 -65
  523. package/dist-pkg/ui/latch-refusal.d.ts +0 -32
  524. package/dist-pkg/ui/latch-refusal.js +0 -60
  525. package/dist-pkg/ui/map-hub.d.ts +0 -56
  526. package/dist-pkg/ui/map-hub.js +0 -138
  527. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  528. package/dist-pkg/ui/settings-caa.js +0 -137
  529. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  530. package/dist-pkg/ui/settings-motor.js +0 -342
  531. package/dist-pkg/ui/webcam.d.ts +0 -7
  532. package/dist-pkg/ui/webcam.js +0 -93
@@ -0,0 +1,45 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Translate } from '../core/i18n.js';
3
+ export type AacTier =
4
+ /** A licence is RECORDED in `license`: the set may be chosen. */
5
+ 'licensed'
6
+ /** No licence: the set appears in the menu, locked, with the reason. */
7
+ | 'unlicensed';
8
+ interface AacSetBase {
9
+ readonly key: string;
10
+ readonly name: string;
11
+ /** Pictograms only: one line about the origin, when it changes what the choice means. */
12
+ readonly note?: string;
13
+ }
14
+ /**
15
+ * A pictogram set. The tier and the licence travel together BY TYPE: a licensed set names its licence (short text, to
16
+ * fit on screen), and an unlicensed one has none to name.
17
+ */
18
+ export type AacSet = (AacSetBase & {
19
+ readonly tier: 'licensed';
20
+ readonly license: string;
21
+ }) | (AacSetBase & {
22
+ readonly tier: 'unlicensed';
23
+ readonly license: null;
24
+ });
25
+ export declare const AAC_SETS: readonly AacSet[];
26
+ export declare const AAC_BY_KEY: Readonly<Record<string, AacSet>>;
27
+ /** Can the child choose this set? Only when it is licensed — the tier is the one fact the menu reads. */
28
+ export declare function aacSelectable(s: AacSet): boolean;
29
+ /** Which SETS the child can choose today: none, because no licence has been obtained (ADR-0233). */
30
+ export declare function aacAvailable(): AacSet[];
31
+ /**
32
+ * Is the AAC menu's DOOR disabled? Yes while no set is licensed (ADR-0233 erratum): the menu exists for pictogram
33
+ * exercises and a pictogram keyboard, and with no set it has nothing to offer. The door then says only that the menu is
34
+ * disabled (`pause.motivo.caa`) — never the licence reason. Letter case does not wait for it: it lives in the quick bar's
35
+ * communication cycle (ADR-0151).
36
+ */
37
+ export declare function aacMenuLocked(): boolean;
38
+ /**
39
+ * Why this set cannot be chosen — an i18n key, never finished text. One answer, because there is one reason: no
40
+ * licence. A selectable set has nothing to explain.
41
+ */
42
+ export declare function aacReason(s: AacSet): string | null;
43
+ /** A label ready for the menu row: the name + the reason, when there is one. */
44
+ export declare function aacLabel(t: Translate, s: AacSet): string;
45
+ export {};
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // LETTERS ARE NOT IN THIS LIST, and the absence is the Dev's decision: letter case is ONE switch (upper case, on or
3
+ // off, with off meaning both cases), not two entries. A binary choice presented as two options makes the child compare
4
+ // two rows to discover they are the same question; a switch asks once.
5
+ //
6
+ // What remains here is genuinely a LIST: the pictogram sets, of which one is chosen. «PCS» is the Picture Communication
7
+ // Symbols set (ADR-0233 reads the Dev's «PECS» as PCS: PECS is a teaching method, not a symbol set).
8
+ export const AAC_SETS = [
9
+ { key: 'arasaac', name: 'ARASAAC', tier: 'unlicensed', license: null },
10
+ { key: 'pcs', name: 'PCS', tier: 'unlicensed', license: null },
11
+ ];
12
+ export const AAC_BY_KEY = Object.fromEntries(AAC_SETS.map((s) => [s.key, s]));
13
+ /** Can the child choose this set? Only when it is licensed — the tier is the one fact the menu reads. */
14
+ export function aacSelectable(s) {
15
+ return s.tier === 'licensed';
16
+ }
17
+ /** Which SETS the child can choose today: none, because no licence has been obtained (ADR-0233). */
18
+ export function aacAvailable() {
19
+ return AAC_SETS.filter(aacSelectable);
20
+ }
21
+ /**
22
+ * Is the AAC menu's DOOR disabled? Yes while no set is licensed (ADR-0233 erratum): the menu exists for pictogram
23
+ * exercises and a pictogram keyboard, and with no set it has nothing to offer. The door then says only that the menu is
24
+ * disabled (`pause.motivo.caa`) — never the licence reason. Letter case does not wait for it: it lives in the quick bar's
25
+ * communication cycle (ADR-0151).
26
+ */
27
+ export function aacMenuLocked() {
28
+ return aacAvailable().length === 0;
29
+ }
30
+ /**
31
+ * Why this set cannot be chosen — an i18n key, never finished text. One answer, because there is one reason: no
32
+ * licence. A selectable set has nothing to explain.
33
+ */
34
+ export function aacReason(s) {
35
+ return aacSelectable(s) ? null : 'aac.noLicence';
36
+ }
37
+ /** A label ready for the menu row: the name + the reason, when there is one. */
38
+ export function aacLabel(t, s) {
39
+ const reason = aacReason(s);
40
+ return reason ? `${s.name} — ${t(reason)}` : s.name;
41
+ }
@@ -0,0 +1,68 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Translate } from '../core/i18n.js';
3
+ /** `lbl` is an i18n key (a key missing from the dictionaries is shown as written). */
4
+ export interface AudioCatDef {
5
+ k: string;
6
+ lbl: string;
7
+ }
8
+ export interface AudioCatState {
9
+ on: boolean;
10
+ vol: number;
11
+ }
12
+ /**
13
+ * The SOUND-NAVIGATION categories — their master volume is separate from the game's sound. Only the sonar since `guard` and
14
+ * `guide` left (ADR-0258): the engine plays nothing in them since the guide moved to the platformer.
15
+ */
16
+ export declare const NAV_CATS: readonly ["sonar"];
17
+ /** The game's GENERAL categories (TTS lives in the Voice section, outside this list). */
18
+ export declare const GEN_CATS: readonly ["music", "ambient", "interact", "earcons"];
19
+ /** 0..1 -> 0..100 rounded (slider display value). */
20
+ export declare function volPercent(v: number): number;
21
+ /** #navsound-master's value: the loudest of the nav categories, as a 0..100 slider value. */
22
+ export declare function navMasterVolume(state: Readonly<Record<string, AudioCatState>>, navCats?: readonly string[]): number;
23
+ /** Cane-hit spacing select -> validated int (garbage/empty -> 1, "one tap per block"). */
24
+ export declare function parseCaneDiv(raw: string): number;
25
+ /** srSay text for a cane-hit spacing choice. The two halves are ONE sentence per case, not a shared prefix
26
+ * plus a tail: a language that renders this as "One tap per block (cane)" needs to move the word "cane". */
27
+ export declare function caneDivMessage(t: Translate, div: number): string;
28
+ /** #tts-engine's option catalog: (value, i18n KEY of the label). The engine NAMES are proper nouns and stay
29
+ * put; what translates is the parenthetical that explains each one. Keys, not text — see input/devices. */
30
+ export declare const TTS_ENGINE_OPTIONS: readonly (readonly [string, string])[];
31
+ /**
32
+ * The engines THIS assembly can offer (ADR-0094): the neural one arrives through the game's Kokoro port (ADR-0198, ADR-0207), so
33
+ * without it Kokoro is not offered — a choice that cannot work leaves whoever picks it waiting for a download that never starts.
34
+ * `kitten`/`espeak` are not built and stay as they were.
35
+ */
36
+ export declare function voiceEngineOptions(neuralAvailable: boolean): readonly (readonly [string, string])[];
37
+ export interface VoiceLike {
38
+ name: string;
39
+ lang: string;
40
+ }
41
+ /**
42
+ * The system voices in the language asked for, and ONLY those (ADR-0185): with none of them, none — the list then says there is
43
+ * no voice, and the speech rows lock.
44
+ *
45
+ * It was called `pickPtVoices` and filtered a hard-coded `/^pt/i`, so a game in English offered a child a list of PORTUGUESE
46
+ * voices to read English text. The name told the truth about what it did and lied about what it should do.
47
+ *
48
+ * The comparison is by language PREFIX and not by the whole tag: a child playing in pt-BR should also be able to choose a
49
+ * pt-PT voice if that is the only one installed, and a school's browser rarely has the exact variant. 🔴 There is no fallback
50
+ * to the whole list: a voice of another language reads the text with the wrong phonetics, and ADR-0243 §3 decided that a wrong
51
+ * voice is worse than none — silence is noticed, a wrong accent is learned.
52
+ */
53
+ export declare function pickVoicesFor<T extends VoiceLike>(voices: readonly T[], lang: string): readonly T[];
54
+ /** "<name> (<lang>)" option label. */
55
+ export declare function voiceLabel(v: VoiceLike): string;
56
+ /** Whether the browser can list/switch audio outputs at all (gates the #audio-sinks hint copy). */
57
+ export declare function sinksSupported(hasEnumerateDevices: boolean, hasAudioContextCtor: boolean): boolean;
58
+ export interface SinkDeviceLike {
59
+ deviceId: string;
60
+ label?: string;
61
+ }
62
+ /** A device's option label, falling back to a 1-based "Output N" when the browser withholds the real label
63
+ * (no getUserMedia permission granted yet). */
64
+ export declare function sinkOptionLabel(t: Translate, d: SinkDeviceLike, index: number): string;
65
+ /** A player's current sink select value ('' = default/shared). */
66
+ export declare function sinkSelectValue(p: {
67
+ audioSink?: string | null;
68
+ } | undefined): string;
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/audio-choices — WHAT THE AUDIO PANELS OFFER, computed without a document (ADR-0221; issue #203).
3
+ //
4
+ // The sound categories and their volumes, the cane's tap spacing, the speech engines, the system voices and the audio
5
+ // outputs: each one answered as a value or a string, with no `document`, no ctx and no state. `ui/settings-audio` keeps the
6
+ // other half — finding the thirteen controls, wiring them and reflecting what the child chose.
7
+ //
8
+ // 🔴 AND THE SUITE HAD ALREADY MADE THIS CUT, which is what made it obvious. `tests/settings-audio.node.test.js` imported
9
+ // exactly these fifteen names and nothing else, while `settings-audio.browser.test.js` drove the DOM half: the file was two
10
+ // modules wearing one name, and the only place that said so was the test folder. 📏 The module it left was 568 lines and 112
11
+ // decision nodes, the third largest in the engine.
12
+ //
13
+ // 📌 WHY IT IS `ui/` AND NOT `core/`: half of these resolve i18n KEYS (`t`) into the words a child reads, and a label is
14
+ // interface. What is genuinely free of language — the category lists and the volume arithmetic — stays here beside the rest
15
+ // rather than being split again for the sake of a layer.
16
+ /**
17
+ * The SOUND-NAVIGATION categories — their master volume is separate from the game's sound. Only the sonar since `guard` and
18
+ * `guide` left (ADR-0258): the engine plays nothing in them since the guide moved to the platformer.
19
+ */
20
+ export const NAV_CATS = ['sonar'];
21
+ /** The game's GENERAL categories (TTS lives in the Voice section, outside this list). */
22
+ // ⚠️ THEY BELONG TO THE «AUDIO» PANEL since ADR-0151, not to the hearing one: what is taste (music, ambience) does not live
23
+ // beside what is accessibility (the sonar). And `other` left — it controlled no sound at all.
24
+ export const GEN_CATS = ['music', 'ambient', 'interact', 'earcons'];
25
+ /** 0..1 -> 0..100 rounded (slider display value). */
26
+ export function volPercent(v) {
27
+ return Math.round(v * 100);
28
+ }
29
+ /*
30
+ * 🔴 NO CATEGORY-ROW MARKUP HERE (note BV): the audio panel's category list is built as NODES (ADR-0129), in
31
+ * `ui/settings-audio.buildCatRow`, where the row is also RECONCILED instead of reborn. A markup builder with no consumer is
32
+ * published debt — it is deleted, not moved.
33
+ *
34
+ * 📌 And no alias is left: a re-export would keep alive a published path nothing imports — surface with no consumer
35
+ * (issue #204).
36
+ */
37
+ /** #navsound-master's value: the loudest of the nav categories, as a 0..100 slider value. */
38
+ export function navMasterVolume(state, navCats = NAV_CATS) {
39
+ return volPercent(Math.max(...navCats.map((k) => state[k].vol)));
40
+ }
41
+ /** Cane-hit spacing select -> validated int (garbage/empty -> 1, "one tap per block"). */
42
+ export function parseCaneDiv(raw) {
43
+ return (+raw) || 1;
44
+ }
45
+ /** srSay text for a cane-hit spacing choice. The two halves are ONE sentence per case, not a shared prefix
46
+ * plus a tail: a language that renders this as "One tap per block (cane)" needs to move the word "cane". */
47
+ export function caneDivMessage(t, div) {
48
+ return t(div === 2 ? 'sr.audio.caneHalfBlock' : 'sr.audio.canePerBlock');
49
+ }
50
+ /** #tts-engine's option catalog: (value, i18n KEY of the label). The engine NAMES are proper nouns and stay
51
+ * put; what translates is the parenthetical that explains each one. Keys, not text — see input/devices. */
52
+ export const TTS_ENGINE_OPTIONS = [
53
+ ['webspeech', 'tts.engine.webspeech'],
54
+ ['kokoro', 'tts.engine.kokoro'],
55
+ ['kitten', 'tts.engine.kitten'],
56
+ ['espeak', 'tts.engine.espeak'],
57
+ ];
58
+ /**
59
+ * The engines THIS assembly can offer (ADR-0094): the neural one arrives through the game's Kokoro port (ADR-0198, ADR-0207), so
60
+ * without it Kokoro is not offered — a choice that cannot work leaves whoever picks it waiting for a download that never starts.
61
+ * `kitten`/`espeak` are not built and stay as they were.
62
+ */
63
+ export function voiceEngineOptions(neuralAvailable) {
64
+ return neuralAvailable ? TTS_ENGINE_OPTIONS : TTS_ENGINE_OPTIONS.filter(([v]) => v !== 'kokoro');
65
+ }
66
+ /**
67
+ * The system voices in the language asked for, and ONLY those (ADR-0185): with none of them, none — the list then says there is
68
+ * no voice, and the speech rows lock.
69
+ *
70
+ * It was called `pickPtVoices` and filtered a hard-coded `/^pt/i`, so a game in English offered a child a list of PORTUGUESE
71
+ * voices to read English text. The name told the truth about what it did and lied about what it should do.
72
+ *
73
+ * The comparison is by language PREFIX and not by the whole tag: a child playing in pt-BR should also be able to choose a
74
+ * pt-PT voice if that is the only one installed, and a school's browser rarely has the exact variant. 🔴 There is no fallback
75
+ * to the whole list: a voice of another language reads the text with the wrong phonetics, and ADR-0243 §3 decided that a wrong
76
+ * voice is worse than none — silence is noticed, a wrong accent is learned.
77
+ */
78
+ export function pickVoicesFor(voices, lang) {
79
+ const pref = lang.slice(0, 2).toLowerCase();
80
+ return voices.filter((v) => v.lang.slice(0, 2).toLowerCase() === pref);
81
+ }
82
+ /** "<name> (<lang>)" option label. */
83
+ export function voiceLabel(v) {
84
+ return v.name + ' (' + v.lang + ')';
85
+ }
86
+ /** Whether the browser can list/switch audio outputs at all (gates the #audio-sinks hint copy). */
87
+ export function sinksSupported(hasEnumerateDevices, hasAudioContextCtor) {
88
+ return hasEnumerateDevices && hasAudioContextCtor;
89
+ }
90
+ /** A device's option label, falling back to a 1-based "Output N" when the browser withholds the real label
91
+ * (no getUserMedia permission granted yet). */
92
+ export function sinkOptionLabel(t, d, index) {
93
+ return d.label || t('audio.sinkFallback', { n: index + 1 });
94
+ }
95
+ /** A player's current sink select value ('' = default/shared). */
96
+ export function sinkSelectValue(p) {
97
+ return (p && p.audioSink) || '';
98
+ }
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { AccommodationWord } from '../core/accommodations.js';
3
+ export interface AudioRowsThatApplyCtx {
4
+ /** The host's DOM query — this module never reaches a global (ADR-0221 step 7d). */
5
+ readonly find: <T extends Element>(selector: string) => T | null;
6
+ /** The word this cartridge gives the cane row, or `null` when nobody walks in this game. */
7
+ readonly caneWord: () => AccommodationWord | null;
8
+ /** Does sound navigation have a subject here? A world AND a direction — read NOW, because a game changes between phases. */
9
+ readonly hasNavigationSound: () => boolean;
10
+ }
11
+ /** Offers the rows this cartridge has a subject for, and hides the rest. Safe to call at every open. */
12
+ export declare function showOnlyRowsThatApply(ctx: AudioRowsThatApplyCtx): void;
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/audio-rows-that-apply — A ROW WITH NO SUBJECT IS NOT OFFERED, AND THE ONE THAT APPLIES CARRIES THE GAME'S WORD
3
+ // (ADR-0153; ADR-0106 §5; ADR-0113 clause 3; ADR-0145).
4
+ //
5
+ // Two rows of the hearing panel, and two different ways of knowing whether this cartridge has anything to say with them:
6
+ //
7
+ // · THE CANE. The cartridge ANSWERS for it (`acomodacoes.caneSpacing`), because whether anybody walks in this game is
8
+ // not derivable from a contract — a board game and a platformer both have a world. When the answer is a word, the row
9
+ // carries the GAME's word and the GAME's explanation; when it is `false`, nobody walks and the row is not offered.
10
+ // · THE SOUND NAVIGATION. DERIVED and never asked (ADR-0153): the sonar needs a world AND a bearing, and `bearing`
11
+ // answers `none` on `hotspots`. In a list-of-points game the sonar, the edge guard and the guide were three sliders
12
+ // with no subject, and asking the cartridge again would let the two answers disagree.
13
+ //
14
+ // ⚠️ `hidden` ON THE ROW, and never the row out of the document, and the reason is measured: `initSettingsAudio` wires
15
+ // `#cane-div` ONCE, at boot. A row that did not exist then and that a later `mount()` brought in would arrive with no
16
+ // listener — a dead control with the look of a live one. `hidden` takes it out of the accessibility tree entirely, which
17
+ // is the «not offered» of ADR-0113 clause 3, and leaves the listener alive for the cartridge that does have it.
18
+ //
19
+ // 📌 THE ANSWERS ARRIVE AS QUESTIONS AND NOT AS A CARTRIDGE, so this module has no opinion about where they come from and
20
+ // a case can drive it without a root. It is also what keeps the derivation in the composition root, where the contract
21
+ // lives: 📏 measured on 2026-09-23, this rule asks the subject set for ONE membership, and two of the three fields that
22
+ // set is built from cannot change that answer — passing them here would be carrying weight that never does anything.
23
+ /** Offers the rows this cartridge has a subject for, and hides the rest. Safe to call at every open. */
24
+ export function showOnlyRowsThatApply(ctx) {
25
+ const row = ctx.find('#cane-div')?.closest('.ctrl-row');
26
+ const cane = ctx.caneWord();
27
+ if (row) {
28
+ row.hidden = cane === null;
29
+ // 🔴 WHAT APPLIES CARRIES THE GAME'S WORD (ADR-0153's confirmation), and BOTH halves of it. `mountAudioInside`
30
+ // re-labels in the engine's own words at every open and this runs after it, so the game's label wins the `<strong>`
31
+ // and the game's explanation wins the `.opt-hint` the footer reads. ⚠️ An absent hint keeps the engine's sentence:
32
+ // «this game has nothing to add» is not «this row has nothing to say».
33
+ const label = row.querySelector('strong');
34
+ if (cane && label)
35
+ label.textContent = cane.label;
36
+ const hint = row.querySelector('.opt-hint');
37
+ if (cane?.hint && hint)
38
+ hint.textContent = cane.hint;
39
+ }
40
+ const navigation = ctx.find('#navsound-list');
41
+ if (navigation)
42
+ navigation.hidden = !ctx.hasNavigationSound();
43
+ }
@@ -0,0 +1,53 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { CameraControl } from '../core/camera-cycle.js';
3
+ import type { SwitchableControl } from './switchable-control.js';
4
+ export declare function followCameraMode(mode: CameraControl, controls: {
5
+ readonly [M in Exclude<CameraControl, 'off'>]: SwitchableControl;
6
+ }): void;
7
+ /** Why a camera mode did not start: its files are not on the device, its runtime did not load, or the camera did not open. */
8
+ export type CameraStartFailure = 'files' | 'runtime' | 'camera';
9
+ /** How one camera mode names itself when it cannot start. */
10
+ export interface CameraWords {
11
+ /** Its name in `problems`: «gesture control». */
12
+ readonly subject: string;
13
+ /** What the child loses, after «the child cannot»: «play with gestures». */
14
+ readonly loses: string;
15
+ /**
16
+ * What the child hears for each failure, in the page's language at the moment it happens. Each is the mode's own `t('…')`
17
+ * with a literal key, so `tests/no-key-reaches-the-child-unresolved` still reads it — a key built here would not be checked.
18
+ */
19
+ readonly spoken: {
20
+ readonly [K in CameraStartFailure]: () => string;
21
+ };
22
+ }
23
+ export interface CameraStartDeps<T extends {
24
+ close(): void;
25
+ }, F> {
26
+ /** The reader's loader (`platform/vision`), already given the page and the checked cache. */
27
+ readonly load: () => Promise<{
28
+ readonly ok: true;
29
+ readonly tracker: T;
30
+ } | {
31
+ readonly ok: false;
32
+ readonly missing: readonly string[];
33
+ }>;
34
+ readonly openFeed: () => Promise<F>;
35
+ /** Nothing started: the control says it, writes it once, and puts the 📷 back to off — if it is still wanted. */
36
+ readonly failed: (kind: CameraStartFailure, line: string, spoken: string) => void;
37
+ }
38
+ /**
39
+ * THE READER, THEN THE CAMERA — or the reason neither is running, said through `failed`; `null` when nothing started.
40
+ *
41
+ * 🔴 A LOADER THAT REJECTS IS A FAILURE LIKE ANY OTHER (ADR-0169). `platform/vision` answers the files it cannot find, but a
42
+ * runtime that cannot be IMPORTED (a server that sends `.mjs` as `text/plain`, an address that answers 404) or a vision task
43
+ * that cannot be built on the GPU nor on the CPU rejects instead, and the three controls awaited it with nothing around it: the
44
+ * rejection went up through `apply` into `followCameraMode`'s `void` and died unhandled, with the 📷 lit, nothing said and
45
+ * nothing in `problems` — measured in a real page on 2026-09-25. The 👄 had met the same defect on 2026-09-21
46
+ * (`ui/voice-control`); this is the one place the three camera modes now start through, so a fourth cannot miss it.
47
+ */
48
+ export declare function startCameraReader<T extends {
49
+ close(): void;
50
+ }, F>(words: CameraWords, d: CameraStartDeps<T, F>): Promise<{
51
+ tracker: T;
52
+ feed: F;
53
+ } | null>;
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/camera-control — THE 📷 POSITION TO THE THREE CAMERA CONTROLS (ADR-0215; issue #199), AND HOW EACH ONE STARTS.
3
+ //
4
+ // One stored position (`core/state.cameraControl`): off · hands · face · eyes. Each control starts only at its own position, so one camera
5
+ // mode at a time holds by construction (ADR-0197). The ones turning off are told first, so a camera is let go before the next mode asks.
6
+ export function followCameraMode(mode, controls) {
7
+ const all = Object.entries(controls);
8
+ for (const [m, c] of all)
9
+ if (m !== mode)
10
+ void c.apply(false);
11
+ for (const [m, c] of all)
12
+ if (m === mode)
13
+ void c.apply(true);
14
+ }
15
+ /**
16
+ * THE READER, THEN THE CAMERA — or the reason neither is running, said through `failed`; `null` when nothing started.
17
+ *
18
+ * 🔴 A LOADER THAT REJECTS IS A FAILURE LIKE ANY OTHER (ADR-0169). `platform/vision` answers the files it cannot find, but a
19
+ * runtime that cannot be IMPORTED (a server that sends `.mjs` as `text/plain`, an address that answers 404) or a vision task
20
+ * that cannot be built on the GPU nor on the CPU rejects instead, and the three controls awaited it with nothing around it: the
21
+ * rejection went up through `apply` into `followCameraMode`'s `void` and died unhandled, with the 📷 lit, nothing said and
22
+ * nothing in `problems` — measured in a real page on 2026-09-25. The 👄 had met the same defect on 2026-09-21
23
+ * (`ui/voice-control`); this is the one place the three camera modes now start through, so a fourth cannot miss it.
24
+ */
25
+ export async function startCameraReader(words, d) {
26
+ const { subject, loses, spoken } = words;
27
+ let load;
28
+ try {
29
+ load = await d.load();
30
+ }
31
+ catch (e) {
32
+ const why = e instanceof Error ? e.message : String(e);
33
+ d.failed('runtime', `${subject}: the vision runtime did not load (${why}) — the child cannot ${loses}; check that the delivery carries the vision files and that the server serves .mjs files with the MIME type text/javascript`, spoken.runtime());
34
+ return null;
35
+ }
36
+ if (!load.ok) {
37
+ d.failed('files', `${subject}: ${load.missing.join(', ')} not on this device — the child cannot ${loses}; open the game once online so the install fetches them`, spoken.files());
38
+ return null;
39
+ }
40
+ try {
41
+ return { tracker: load.tracker, feed: await d.openFeed() };
42
+ }
43
+ catch {
44
+ load.tracker.close();
45
+ d.failed('camera', `${subject}: the camera did not open — the child cannot ${loses}; allow the camera for this page, or plug one in`, spoken.camera());
46
+ return null;
47
+ }
48
+ }
@@ -1,15 +1,16 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Translate } from '../core/i18n.js';
2
3
  export declare const CHANGED_CLASS = "is-changed";
3
4
  /**
4
- * Marca (ou desmarca) UM controle. Idempotente por construção: limpa e só então aplica. Os painéis chamam
5
- * isto de dentro do mesmo `reflect*` que já redesenha o controle, então ele roda muitas vezes seguidas.
5
+ * Marks (or unmarks) ONE control. Idempotent by construction: clears and only then applies. The panels call this from
6
+ * inside the same `reflect*` that already redraws the control, so it runs many times in a row.
6
7
  */
7
- export declare function markChanged(el: HTMLElement | null, changed: boolean): void;
8
+ export declare function markChanged(t: Translate, el: HTMLElement | null, changed: boolean): void;
8
9
  /**
9
- * Sobe a marca um nível: o botão que ABRE o menu fica marcado enquanto qualquer opção dentro dele estiver.
10
- * Sem isso a trilha começaria dentro do menu, e achar o menu certo continuaria custando abrir os sete.
10
+ * Raises the mark one level: the button that OPENS the menu stays marked while any option inside it is. Without this
11
+ * the trail would start inside the menu, and finding the right menu would still cost opening every one.
11
12
  *
12
- * `changes` é uma lista de booleanos e não de elementos porque quem sabe comparar com o padrão é o painel;
13
- * este módulo não deve ter opinião sobre o que é padrão — há uma fonte só, e é o DEFAULTS do ADR-0028.
13
+ * `changes` is a list of booleans and not of elements because the panel is the one that knows how to compare with the
14
+ * default; this module must have no opinion on what the default is — there is one source, ADR-0028's DEFAULTS.
14
15
  */
15
- export declare function markMenuChanged(opener: HTMLElement | null, changes: readonly boolean[]): void;
16
+ export declare function markMenuChanged(t: Translate, opener: HTMLElement | null, changes: readonly boolean[]): void;
@@ -1,56 +1,25 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/changed-mark.ts — MARCA O QUE SAIU DO PADRÃO (ADR-0029). Módulo-folha: só DOM e i18n, nenhuma dep de
3
- // estado. Quem sabe o que é padrão é cada painel, que já leu DEFAULTS (ADR-0028); aqui mora só o COMO marcar.
4
- //
5
- // O problema que ele resolve: o reset por menu do ADR-0028 só serve a quem sabe que há algo para desfazer.
6
- // Sete menus, ~40 controles, e o que a criança mexeu tem exatamente a mesma aparência do que ela não mexeu.
7
- // Os menus não estavam mal rotulados — estavam IMPESQUISÁVEIS.
8
- //
9
- // TRÊS CANAIS, e nenhum é enfeite. O ADR-0029 explica por quê; em resumo: a moldura branca sozinha não serve
10
- // a quem não distingue cores, some no alto contraste (onde `--ink-soft` JÁ é branco) e não existe para quem
11
- // não enxerga. Então:
12
- //
13
- // 1. COR — `--changed` na moldura (CSS; troca sozinha no alto contraste).
14
- // 2. FORMA — anel interno, uma moldura dupla. Sobrevive a escala de cinza e a qualquer paleta, porque é uma
15
- // CONTAGEM DE ANÉIS e não uma matiz.
16
- // 3. NOME — o nome acessível do controle ganha um sufixo. É o canal que faz mais trabalho pelo menor preço:
17
- // a criança cega PERCORRE o menu e OUVE, em ordem, o que saiu do padrão.
18
- //
19
- // O canal 3 tem dois caminhos porque os painéis constroem o nome de duas formas, e um `aria-label` vence o
20
- // conteúdo do botão. Marcar só um deles deixaria metade dos controles mudos — em silêncio, que é o pior jeito
21
- // de uma marca de acessibilidade falhar, porque nada na tela denuncia a falta.
22
- //
23
- // O sufixo NUNCA toca o `textContent`. A primeira versão pendurava um `<span class="sr-only">` dentro do
24
- // botão, e um teste que aferia o rótulo visível caiu — mostrando o problema real: os painéis reescrevem
25
- // `textContent` inteiro a cada reflect, então o sufixo vivia à mercê da ordem das chamadas. Agora o caminho
26
- // sem `aria-label` CRIA um, a partir do conteúdo, e o desmarcar simplesmente o remove.
27
- //
28
- // E a idempotência é ESTRUTURAL, não caso a caso: toda chamada primeiro devolve o controle ao estado
29
- // original — tirando qualquer rastro nosso, dos dois caminhos — e só então aplica. Uma marca que só soubesse
30
- // acrescentar acabaria em tudo, e uma marca em tudo não é marca.
31
- import { t } from '../core/i18n.js';
32
2
  export const CHANGED_CLASS = 'is-changed';
33
- /** Guarda o `aria-label` ORIGINAL quando ele é do painel, para devolvê-lo sem adivinhar por texto. */
3
+ /** Keeps the ORIGINAL `aria-label` when it is the panel's, to give it back without guessing from text. */
34
4
  const BASE_ATTR = 'markBase';
35
- /** Marca que o `aria-label` foi criado por NÓS: desmarcar tira o atributo inteiro em vez de restaurar algo. */
5
+ /** Marks that the `aria-label` was created by US: unmarking removes the whole attribute instead of restoring one. */
36
6
  const OWNED_ATTR = 'markOwnsLabel';
37
7
  /**
38
- * O nó cujo NOME acessível recebe o sufixo: o PRIMEIRO controle da linha em ordem de documento, ou a própria
39
- * linha quando ela não tem controle dentro.
8
+ * The node whose accessible NAME gets the suffix: the row's FIRST control in document order, or the row itself when it
9
+ * has no control inside.
40
10
  *
41
- * "O primeiro" é decisão, não acaso — e ela apareceu ao verificar no jogo. Uma linha do mixer tem DOIS
42
- * controles: o volume e o liga/desliga. O sufixo foi para o volume, que vem antes no markup, e isso está
43
- * certo por dois motivos: é o controle que a criança alcança primeiro ao tabular, então ela ouve "alterado"
44
- * ANTES de decidir se para nesta linha; e repetir o sufixo nos dois faria o leitor de tela dizer a mesma
45
- * coisa duas vezes ao atravessar uma linha só, que é ruído com cara de informação.
11
+ * "The first" is a decision, not chance. A mixer row has TWO controls: the volume and the on/off. The suffix goes on the
12
+ * volume, which comes first in the markup, for two reasons: it is the control the child reaches first when tabbing, so
13
+ * they hear the mark BEFORE deciding whether to stop on this row; and repeating the suffix on both would make the
14
+ * screen reader say the same thing twice across one row, which is noise dressed as information.
46
15
  *
47
- * O que isto deixa frágil, e por isso está preso por teste: reordenar o markup move o sufixo de controle sem
48
- * quebrar nada visível.
16
+ * What this leaves fragile, and why a test holds it: reordering the markup moves the suffix to another control without
17
+ * breaking anything visible.
49
18
  */
50
19
  function namedNode(el) {
51
20
  return el.querySelector('button, select, input, [role="button"]') ?? el;
52
21
  }
53
- /** Devolve o controle ao estado original, venha o rótulo do painel ou de nós. Base de toda chamada. */
22
+ /** Returns the control to its original state, whether the label came from the panel or from us. The base of every call. */
54
23
  function clearMark(node) {
55
24
  if (node.dataset[OWNED_ATTR] !== undefined) {
56
25
  node.removeAttribute('aria-label');
@@ -62,10 +31,10 @@ function clearMark(node) {
62
31
  }
63
32
  }
64
33
  /**
65
- * Marca (ou desmarca) UM controle. Idempotente por construção: limpa e só então aplica. Os painéis chamam
66
- * isto de dentro do mesmo `reflect*` que já redesenha o controle, então ele roda muitas vezes seguidas.
34
+ * Marks (or unmarks) ONE control. Idempotent by construction: clears and only then applies. The panels call this from
35
+ * inside the same `reflect*` that already redraws the control, so it runs many times in a row.
67
36
  */
68
- export function markChanged(el, changed) {
37
+ export function markChanged(t, el, changed) {
69
38
  if (!el)
70
39
  return;
71
40
  el.classList.toggle(CHANGED_CLASS, changed);
@@ -76,21 +45,21 @@ export function markChanged(el, changed) {
76
45
  const suffix = t('a11y.changed');
77
46
  const label = node.getAttribute('aria-label');
78
47
  if (label !== null) {
79
- node.dataset[BASE_ATTR] = label; // rótulo do painel: guardamos e devolvemos depois
48
+ node.dataset[BASE_ATTR] = label; // the panel's label: keep it and give it back later
80
49
  node.setAttribute('aria-label', label + ', ' + suffix);
81
50
  }
82
51
  else {
83
- node.dataset[OWNED_ATTR] = '1'; // rótulo vinha do conteúdo: criamos um e o assumimos
52
+ node.dataset[OWNED_ATTR] = '1'; // the label came from the content: create one and own it
84
53
  node.setAttribute('aria-label', (node.textContent ?? '').trim() + ', ' + suffix);
85
54
  }
86
55
  }
87
56
  /**
88
- * Sobe a marca um nível: o botão que ABRE o menu fica marcado enquanto qualquer opção dentro dele estiver.
89
- * Sem isso a trilha começaria dentro do menu, e achar o menu certo continuaria custando abrir os sete.
57
+ * Raises the mark one level: the button that OPENS the menu stays marked while any option inside it is. Without this
58
+ * the trail would start inside the menu, and finding the right menu would still cost opening every one.
90
59
  *
91
- * `changes` é uma lista de booleanos e não de elementos porque quem sabe comparar com o padrão é o painel;
92
- * este módulo não deve ter opinião sobre o que é padrão — há uma fonte só, e é o DEFAULTS do ADR-0028.
60
+ * `changes` is a list of booleans and not of elements because the panel is the one that knows how to compare with the
61
+ * default; this module must have no opinion on what the default is — there is one source, ADR-0028's DEFAULTS.
93
62
  */
94
- export function markMenuChanged(opener, changes) {
95
- markChanged(opener, changes.some(Boolean));
63
+ export function markMenuChanged(t, opener, changes) {
64
+ markChanged(t, opener, changes.some(Boolean));
96
65
  }
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Translate } from '../core/i18n.js';
3
+ import { type Action } from '../core/actions.js';
4
+ import type { KeyScheme } from '../core/entity.js';
5
+ /**
6
+ * Physical key code → short readable label.
7
+ *
8
+ * `Space` is the only one with a word to translate; the arrows are glyphs and the rest are the bare letter or
9
+ * digit, identical in every language.
10
+ *
11
+ * ⚠️ A TABLE AND A LADDER, and not a chain of replacements, which leaks the MACHINE's name whenever a prefix is not
12
+ * foreseen (`Digit1`, `Numpad5`). What is not recognised still passes through untouched on purpose: `Comma` is ugly
13
+ * and honest, and inventing a name for it would be guessing.
14
+ */
15
+ export declare function keyName(t: Translate, code: string): string;
16
+ /**
17
+ * Which OTHER player already owns `code`, among `schemes` (one entry per player, same order as player index) —
18
+ * or -1 if free. `mapRef` (the scheme currently being edited) is excluded BY REFERENCE. Built over a Map (code →
19
+ * owner index) so a scheme with many bound keys does not cost a full re-scan per lookup.
20
+ */
21
+ export declare function keyUsedByOther(code: string, mapRef: KeyScheme, schemes: readonly KeyScheme[]): number;
22
+ /**
23
+ * Which OTHER action OF THE SAME scheme already has `code` — or `null` if none.
24
+ *
25
+ * ⚠️ THE SIBLING `keyUsedByOther` NEEDS, AND ITS ABSENCE IS INVISIBLE IN A ONE-PLAYER GAME (#126). That function
26
+ * excludes the scheme being edited **by reference**; with a single player, `schemesFor()` returns exactly that
27
+ * scheme, so its guard scans an empty list and **can never fire**. A child who puts `W` on a new action would keep
28
+ * `W` on the old one too, and play the whole game with both firing together.
29
+ *
30
+ * ⚠️ AND THAT IS THE WORST SHAPE A DEFECT CAN TAKE, as the `input/default-bindings` header says: the two actions fire
31
+ * together, and the child sees an intermittent double action nobody can reproduce on purpose — on a screen they
32
+ * opened **because** they could not use the default controls.
33
+ *
34
+ * Returns the ACTION and not a boolean, because the announcement has to say which one — saying only that the key is
35
+ * in use sends the child looking for what the function already knows.
36
+ */
37
+ export declare function actionAlreadyBound(code: string, mapRef: KeyScheme, except: Action): Action | null;