@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
@@ -1,46 +1,49 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  // input/touch.ts — on-screen touch controls (Estágio 4): the physical geometry of the touch pad (mm→px) and
3
- // its config UI (#touchcfg, #touch-controls visibility). Extracted from game.js's E13 block. Geometry/layout/
3
+ // its config UI (#touchcfg, #touch-controls visibility). Extracted from the monolith's E13 block. Geometry/layout/
4
4
  // touch-map-merge are pure (padPxPerMm/padHandTag/computePadPhysicalPx/padLayoutFromId/normalizeTouchMap —
5
5
  // project node); DOM wiring + persisted state (touchMap/padDesign/padBtnMm/padGapMm/padStickMm/padTravelMm/
6
6
  // padDpadMm/padDir) live behind initTouch(ctx). PAD_DESIGNS/TOUCH_ACT_LABELS/TOUCH_DEFAULT come from
7
7
  // ./devices.js (not reimplemented). Reading the real Gamepad API (polling, mapping wizard) is input/gamepad's
8
- // territory, not this module's — see the header note on padKind() for the one deliberate exception.
8
+ // territory, not this module's.
9
9
  import { PAD_DESIGNS, TOUCH_DEFAULT } from './devices.js';
10
- import { migrarMapaDeToque } from './vocabulary-migration.js';
11
- import { t } from '../core/i18n.js';
12
- import { KEYS } from '../platform/storage.js'; // só as CHAVES (constantes) — leitura/escrita passam por ctx.store (DI)
13
- import { setMinimapCorner } from '../render/minimap.js'; // já módulo próprio (Estágio 4, Tier 1) — importado direto
14
- // ===================== PURO (sem DOM/store — project node) =====================
15
- // Alvo físico ancorado no iPhone 16 a tela cheia (aresta longa 141,1mm do display 1179×2556 @460ppi →
16
- // ~6,04 px CSS/mm). Ver o comentário original em game.js para a justificativa completa (WCAG 2.5.5 / GAG).
10
+ import { migrateTouchMap } from './vocabulary-migration.js';
11
+ import { KEYS } from '../platform/storage-keys.js'; // the names only — reading and writing go through ctx.store (ADR-0232)
12
+ // ===================== PURE (no DOM/store — node project) =====================
13
+ // Physical target anchored on a full-screen iPhone 16 (the long edge of the 1179×2556 @460ppi display is 141.1 mm →
14
+ // ~6.04 CSS px/mm) — see `padPxPerMm` for what this estimate is and is not (WCAG 2.5.5 / GAG).
17
15
  export const IPHONE16_LONG_MM = 141.1;
18
16
  export const IPHONE16_LONG_PX = 852;
19
- export const IPHONE16_PXMM = IPHONE16_LONG_PX / IPHONE16_LONG_MM; // ~6,04 px CSS/mm
20
- /** mm→px CSS. No celular, ancora na aresta longa da JANELA (físico exato, retrato ou paisagem). No
21
- * desktop/notebook, fixa no ratio do iPhone 16 (não cresce com a largura do monitor). Determinístico e puro:
22
- * `mobile`/`viewW`/`viewH` chegam já resolvidos (isMobile()/innerWidth/innerHeight ficam no lado impuro). */
17
+ export const IPHONE16_PXMM = IPHONE16_LONG_PX / IPHONE16_LONG_MM; // ~6.04 CSS px/mm
18
+ /**
19
+ * CSS px per ASSUMED millimetre — an estimate anchored on one phone, NOT a measurement of the device (plan phase 5b).
20
+ * On a mobile, the window's long edge is taken to be an iPhone 16's display (141.1 mm), whatever the device: a 10-inch
21
+ * tablet window of 1280×800 is read as 141.1 mm long, about 9.07 px/mm, where its glass is about 216 mm. On a desktop it is
22
+ * the iPhone 16 ratio, fixed (it does not grow with the monitor). The browser exposes no physical size, so a «real
23
+ * millimetre» is not reachable from here; targets that must meet a floor use the engine's `--alvo-min` (ADR-0163).
24
+ * Pure: `mobile`/`viewW`/`viewH` arrive resolved (isMobile()/innerWidth/innerHeight stay on the impure side).
25
+ */
23
26
  export function padPxPerMm(mobile, viewW, viewH) {
24
27
  return mobile ? Math.max(viewW, viewH) / IPHONE16_LONG_MM : IPHONE16_PXMM;
25
28
  }
26
- /** Classifica um valor em mm na faixa mão-de-criança / intermediário / mão-de-adulto (usado nas etiquetas
27
- * do painel de configuração de toque). */
29
+ /** Sorts a value in mm into the child's-hand / in-between / adult's-hand band (used by the touch settings panel's
30
+ * tags). */
28
31
  export function padHandTag(v, lo, hi) {
29
32
  return v <= lo ? 'crianca' : v >= hi ? 'adulto' : 'inter';
30
33
  }
31
- /** Toda a geometria em px a partir dos mm configurados + do fator px/mm — extraída de applyPadPhysical() para
32
- * ficar pura/testável (dpr alto/baixo, tela pequena, valores extremos). applyPadPhysical() só chama isto e
33
- * escreve o resultado em custom properties CSS + nos rótulos do painel. */
34
+ /** All the geometry in px from the configured mm + the px/mm factor — kept pure so it is testable (high/low dpr, small
35
+ * screen, extreme values). applyPadPhysical() only calls this and writes the result into CSS custom properties + the
36
+ * panel's labels. */
34
37
  export function computePadPhysicalPx(mm, pxPerMm) {
35
38
  const btnPx = mm.btn * pxPerMm, gapPx = mm.gap * pxPerMm;
36
- const diamPx = btnPx + Math.SQRT2 * (btnPx + gapPx); // losango: folga de aresta = gap
39
+ const diamPx = btnPx + Math.SQRT2 * (btnPx + gapPx); // diamond: edge clearance = gap
37
40
  const knobPx = mm.stick * pxPerMm, travelPx = mm.travel * pxPerMm;
38
- const basePx = knobPx + 2 * travelPx + 16; // base do analógico = contato + curso
39
- const armPx = mm.dpad * pxPerMm, armWPx = armPx * 0.8, spanPx = 2 * armPx + armWPx; // cruz: braço + largura(0,8×)
40
- const stickTravelPx = travelPx, stickDeadPx = Math.max(6, travelPx * 0.4); // zona-morta ~40% do curso
41
+ const basePx = knobPx + 2 * travelPx + 16; // stick base = contact + travel
42
+ const armPx = mm.dpad * pxPerMm, armWPx = armPx * 0.8, spanPx = 2 * armPx + armWPx; // cross: arm + width (0.8×)
43
+ const stickTravelPx = travelPx, stickDeadPx = Math.max(6, travelPx * 0.4); // dead zone ~40% of the travel
41
44
  return { btnPx, diamPx, knobPx, basePx, armPx, armWPx, spanPx, stickTravelPx, stickDeadPx };
42
45
  }
43
- /** Layout de rotulagem dos botões pelo id reportado do controle (heurística por substring/VID). */
46
+ /** The button-labelling layout from the controller's reported id (a substring/VID heuristic). */
44
47
  export function padLayoutFromId(id) {
45
48
  const s = (id || '').toLowerCase();
46
49
  if (/dualshock|dualsense|playstation|054c/.test(s))
@@ -51,54 +54,50 @@ export function padLayoutFromId(id) {
51
54
  return 'microsoft';
52
55
  return 'generic';
53
56
  }
54
- /** As 9 posições de toque remapeáveis (direcional×4, START, botões 0–3) — `lbl` é a chave i18n do rótulo. */
55
- // `lbl` guarda a CHAVE i18n, não o texto: tabela de módulo resolvida no import congelaria o idioma no boot
56
- // (ver a nota em input/devices). A seta e o nome do botão viajam DENTRO da tradução, porque em inglês o
57
- // "(cima)" vira "(up)" e a seta fica onde está — é moldura inteira, não conteúdo interpolado.
57
+ /** The 13 remappable touch slots (d-pad×4, START, buttons 0–3, the four shoulders) — `lbl` is the label's i18n key. */
58
+ // `lbl` keeps the i18n KEY, not the text: a module table resolved on import would freeze the language at boot (see the
59
+ // note in input/devices). The arrow and the button's name travel INSIDE the translation, because in English the
60
+ // direction word changes and the arrow stays where it is — it is the whole frame, not interpolated content.
58
61
  export const TOUCH_SLOTS = [
59
62
  { k: 'up', lbl: 'touch.slot.up' }, { k: 'down', lbl: 'touch.slot.down' },
60
63
  { k: 'left', lbl: 'touch.slot.left' }, { k: 'right', lbl: 'touch.slot.right' },
61
64
  { k: 'start', lbl: 'touch.slot.start' },
62
65
  { k: 'b0', lbl: 'touch.slot.b0' }, { k: 'b1', lbl: 'touch.slot.b1' },
63
66
  { k: 'b2', lbl: 'touch.slot.b2' }, { k: 'b3', lbl: 'touch.slot.b3' },
67
+ { k: 'bl2', lbl: 'touch.slot.bl2' }, { k: 'bl1', lbl: 'touch.slot.bl1' },
68
+ { k: 'br2', lbl: 'touch.slot.br2' }, { k: 'br1', lbl: 'touch.slot.br1' },
64
69
  ];
65
70
  /**
66
- * As ações que o TRANSPORTE de toque consegue carregar.
71
+ * The actions the touch TRANSPORT can carry.
67
72
  *
68
- * ⚠️ DEIXOU DE SER A LISTA DO `<select>`: as opções vêm agora de `ctx.acoesDoJogo()`, porque quem decide
69
- * quais ações existem é o jogo. Esta lista continua a valer como o que ESTE transporte alcança — e é contra
70
- * ela que o gate de `input/touch-bindings` confere se o despacho reconhece tudo o que se pode oferecer.
71
- * As duas coisas eram a mesma por acidente enquanto só havia um jogo.
73
+ * ⚠️ IT IS NOT THE `<select>`'S LIST: the options come from `ctx.gameActions()`, because the game decides which actions
74
+ * exist. This list is what THIS transport reaches — and it is against it that `input/touch-bindings`' gate checks the
75
+ * dispatch recognises everything that can be offered. The two were the same by accident while there was one game.
72
76
  */
73
- export const TOUCH_ACTS = ['left', 'right', 'up', 'down', 'action2', 'action1', 'action3', 'action4', 'start'];
74
- /** Funde o mapa persistido (JSON solto do localStorage) sobre TOUCH_DEFAULT. Mantido IDÊNTICO ao original:
75
- * NÃO valida chaves/valores contra TOUCH_SLOTS/TOUCH_ACTS — um JSON malformado com chaves/valores estranhos
76
- * passa como está (só falha se não for um objeto). Ver a nota "bug surfaced" no retorno da extração. */
77
+ export const TOUCH_ACTS = ['left', 'right', 'up', 'down', 'action2', 'action1', 'action3', 'action4', 'start',
78
+ 'leftShoulder', 'leftTrigger', 'rightShoulder', 'rightTrigger'];
79
+ /** Merges the persisted map (loose JSON from localStorage) over TOUCH_DEFAULT. It does NOT validate keys/values
80
+ * against TOUCH_SLOTS/TOUCH_ACTS — malformed JSON with strange keys/values passes as it is (it only fails if it is not
81
+ * an object). */
77
82
  export function normalizeTouchMap(stored) {
78
- // ⚠️ O GUARDADO PASSA PELO TRADUTOR ANTES DA FUSÃO, e a ordem é o que importa: `Object.assign` deixa o
79
- // guardado VENCER o padrão, então um `b0: 'jump'` de antes do ADR-0086 sobrescreveria o `b0: 'action2'`
80
- // correto e o botão da tela deixaria de fazer nada — sem erro nenhum. Aqui o nome da ação está no VALOR,
81
- // não na chave, e por isso precisa de um tradutor próprio. Ver `input/vocabulary-migration.ts`.
82
- const migrado = migrarMapaDeToque(stored && typeof stored === 'object' ? stored : null);
83
- return Object.assign({}, TOUCH_DEFAULT, migrado || {});
84
- }
85
- /** UNUSED hoje — grep em game.js (`\bpadKind\b`) só acha a própria definição, nenhum chamador. Extraído porque
86
- * a fronteira explicitamente listou; NÃO apagado (relatado, não conserto). Lê navigator.getGamepads() direto,
87
- * no mesmo padrão de ui/settings-audio.ts / core/i18n.ts (acesso direto a navigator, sem DI) — mesmo assim,
88
- * é semanticamente "ler o controle", território do agente de input/gamepad; ver a nota de fronteira no retorno. */
89
- export function padKind() {
90
- let kind = 'kb';
91
- const pads = navigator.getGamepads ? navigator.getGamepads() : [];
92
- for (const gp of pads) {
93
- if (!gp)
94
- continue;
95
- kind = gp.mapping === 'standard' ? 'x' : 'd';
96
- if (kind === 'x')
97
- break;
98
- }
99
- return kind;
83
+ // ⚠️ WHAT IS STORED GOES THROUGH THE TRANSLATOR BEFORE THE MERGE, and the order is what matters: `Object.assign`
84
+ // lets the stored map WIN over the default, so a `b0: 'jump'` from before ADR-0086 would overwrite the correct
85
+ // `b0: 'action2'` and the on-screen button would stop doing anything — with no error at all. Here the action name
86
+ // is in the VALUE, not the key, which is why it needs its own translator. See `input/vocabulary-migration.ts`.
87
+ const migrated = migrateTouchMap(stored && typeof stored === 'object' ? stored : null);
88
+ return Object.assign({}, TOUCH_DEFAULT, migrated || {});
100
89
  }
90
+ /** The five size sliders a page's touch panel may carry, each writing its own measure (mm). */
91
+ const PAD_SLIDERS = [
92
+ ['#pad-size', 'btn'], ['#pad-gap', 'gap'], ['#pad-stick', 'stick'], ['#pad-travel', 'travel'], ['#pad-dpad', 'dpad'],
93
+ ];
94
+ /** The two presets: every size at once, and the sentence that says so — a child who cannot see five sliders move hears it. */
95
+ const PAD_PRESETS = [
96
+ ['#pad-preset-child', { btn: 12, gap: 2.5, stick: 16.5, travel: 4, dpad: 11.5 }, 'sr.touch.presetChild'],
97
+ ['#pad-preset-adult', { btn: 14, gap: 4.5, stick: 20, travel: 5.5, dpad: 14 }, 'sr.touch.presetAdult'],
98
+ ];
101
99
  export function initTouch(ctx) {
100
+ const { t } = ctx;
102
101
  let padDesign = ctx.store.get(KEYS.padDesign, 'generic') || 'generic';
103
102
  let padBtnMm = ctx.store.getNum(KEYS.padBtnMm, 12.5);
104
103
  let padGapMm = ctx.store.getNum(KEYS.padGapMm, 3);
@@ -106,47 +105,84 @@ export function initTouch(ctx) {
106
105
  let padTravelMm = ctx.store.getNum(KEYS.padTravelMm, 4.5);
107
106
  let padDpadMm = ctx.store.getNum(KEYS.padDpadMm, 12);
108
107
  let padDir = ctx.store.get(KEYS.padDir, 'stick') || 'stick';
109
- let _stickTravelPx = 42, _stickDeadPx = 12; // valores de arranque; applyPadPhysical() atualiza de verdade
108
+ let _stickTravelPx = 42, _stickDeadPx = 12; // boot values; applyPadPhysical() sets the real ones
110
109
  const touchMap = normalizeTouchMap(ctx.store.getJSON(KEYS.touchmap, null));
110
+ /** A slot's choice landed: store it, and say it in the game's word — never the abstract name. */
111
+ function slotChosen(slot, slotLabel, action) {
112
+ touchMap[slot] = action;
113
+ ctx.store.setJSON(KEYS.touchmap, touchMap);
114
+ // ⚠️ The spoken word is the SAME as the one read: it comes from the list that just built the control, so nothing has
115
+ // to make two tables agree.
116
+ const chosen = ctx.gameActions().find((x) => x.action === action);
117
+ const slotName = slotLabel || t('touch.slot.fallback');
118
+ // ⚠️ WITHOUT THE GAME'S WORD, THE ANNOUNCEMENT DROPS THE POSITION — IT DOES NOT FALL BACK TO THE ID. `action` is the
119
+ // ABSTRACT name (`action3`), and ADR-0074 says it never reaches a person; `7742ac0` already paid for this defect on the
120
+ // remapping screen and the way out is the same: a key of its own that says what matters.
121
+ // 📌 The fallback is reachable because `gameActions()` is a FUNCTION of the cartridge, reread on every choice: in an
122
+ // activity hub the list changes underneath and the control drawn before is orphaned.
123
+ ctx.srSay(chosen
124
+ ? t('sr.touch.slotSet', { slot: slotName, acao: chosen.label })
125
+ : t('sr.touch.slotSetUnnamed', { slot: slotName }));
126
+ }
111
127
  function renderTouchMap() {
112
128
  const el = ctx.$('#touchmap-list');
113
129
  if (!el)
114
130
  return;
131
+ if (ctx.drawChoice) {
132
+ drawSlotChoices(el, ctx.drawChoice);
133
+ return;
134
+ }
115
135
  el.innerHTML = TOUCH_SLOTS.map((s) => `<div class="ctrl-row"><label for="tm-${s.k}">${t(s.lbl)}</label><select id="tm-${s.k}" class="vol" data-slot="${s.k}">` +
116
- // ⚠️ A OPÇÃO NASCE VAZIA e o `rotulo` entra logo abaixo por `textContent` (issue #106). Ele é a PALAVRA
117
- // do jogo — `acoesDoJogo()` sai do preset —, e um jogo vive noutro repositório (ADR-0083), então este
118
- // texto não é revisto por esta árvore. `value="${acao}"` fica: `acao` é o nome ABSTRATO, e a engine
119
- // enumera-o em `core/actions`; é dela e não do jogo.
120
- ctx.acoesDoJogo().map(({ acao }) => `<option value="${acao}"${touchMap[s.k] === acao ? ' selected' : ''}></option>`).join('') +
136
+ // ⚠️ THE OPTION IS BORN EMPTY and the `label` goes in just below through `textContent` (issue #106). It is the
137
+ // game's WORD — `gameActions()` comes from the preset —, and a game lives in another repository (ADR-0083), so
138
+ // this text is not reviewed by this tree. `value="${slotAction}"` stays: `slotAction` is the ABSTRACT name, and
139
+ // the engine enumerates it in `core/actions`; it is the engine's and not the game's.
140
+ ctx.gameActions().map(({ action: slotAction }) => `<option value="${slotAction}"${touchMap[s.k] === slotAction ? ' selected' : ''}></option>`).join('') +
121
141
  `</select></div>`).join('');
122
- // As palavras do jogo, por `textContent` — que escapa por construção. A ordem casa porque é a mesma lista.
142
+ // The game's words, through `textContent` — which escapes by construction. The order matches because it is the same list.
123
143
  for (const sel of el.querySelectorAll('select[data-slot]')) {
124
- const palavras = ctx.acoesDoJogo();
125
- for (let i = 0; i < sel.options.length && i < palavras.length; i++) {
126
- sel.options[i].textContent = palavras[i].rotulo;
144
+ const words = ctx.gameActions();
145
+ for (let i = 0; i < sel.options.length && i < words.length; i++) {
146
+ sel.options[i].textContent = words[i].label;
127
147
  }
128
148
  }
129
149
  el.querySelectorAll('select[data-slot]').forEach((sel) => {
130
150
  sel.addEventListener('change', () => {
131
- const slot = sel.dataset.slot || '';
132
- touchMap[slot] = sel.value;
133
- ctx.store.setJSON(KEYS.touchmap, touchMap);
134
151
  const label = sel.previousElementSibling ? sel.previousElementSibling.textContent : null;
135
- // ⚠️ A palavra falada é a MESMA que a lida: sai da mesma lista que acabou de montar o `<option>`.
136
- // Antes vinham de tabelas diferentes e nada obrigava as duas a concordar.
137
- const escolhida = ctx.acoesDoJogo().find((x) => x.acao === sel.value);
138
- const nomeDoSlot = label || t('touch.slot.fallback');
139
- // ⚠️ SEM PALAVRA DO JOGO, O ANÚNCIO PERDE A POSIÇÃO — NÃO RECUA PARA O ID. `sel.value` é o nome
140
- // ABSTRATO (`action3`), e o ADR-0074 diz que ele nunca chega a uma pessoa; o `7742ac0` já pagou este
141
- // defeito no ecrã de remapeamento e a saída é a mesma: uma chave própria que diz o que importa.
142
- // 📌 O recuo é alcançável porque `acoesDoJogo()` é FUNÇÃO do cartucho, relida a cada `change`: num hub
143
- // de atividades a lista muda por baixo e a `<option>` desenhada antes fica órfã.
144
- ctx.srSay(escolhida
145
- ? t('sr.touch.slotSet', { slot: nomeDoSlot, acao: escolhida.rotulo })
146
- : t('sr.touch.slotSetUnnamed', { slot: nomeDoSlot }));
152
+ slotChosen(sel.dataset.slot || '', label, sel.value);
147
153
  });
148
154
  });
149
155
  }
156
+ /**
157
+ * THE SLOTS DRAWN BY THEIR SIZE (ADR-0130 rule 3), node by node: each slot is a choice among the game's named positions,
158
+ * so a game naming five or fewer gets cycle rows «◀ Botão 0: Confirmar ▶», and one naming more gets dropdowns. The id
159
+ * (`#tm-<slot>`) and `data-slot` stay on the control in both shapes — they are how the root finds the slots it hides.
160
+ */
161
+ function drawSlotChoices(el, draw) {
162
+ while (el.firstChild)
163
+ el.removeChild(el.firstChild);
164
+ const doc = el.ownerDocument;
165
+ const positions = ctx.gameActions().map(({ action, label }) => ({ value: action, label }));
166
+ for (const s of TOUCH_SLOTS) {
167
+ const slotName = t(s.lbl);
168
+ const control = draw({ label: slotName, values: positions, current: touchMap[s.k] ?? '' }, (action) => slotChosen(s.k, slotName, action));
169
+ control.id = `tm-${s.k}`;
170
+ control.dataset.slot = s.k;
171
+ const row = doc.createElement('div');
172
+ row.className = 'ctrl-row';
173
+ if (control.tagName === 'SELECT') {
174
+ // a dropdown keeps its name beside it; a cycle row already carries it in its own text
175
+ const name = doc.createElement('label');
176
+ name.htmlFor = control.id;
177
+ name.textContent = slotName;
178
+ row.appendChild(name);
179
+ }
180
+ else
181
+ row.classList.add('ctrl-row--passos');
182
+ row.appendChild(control);
183
+ el.appendChild(row);
184
+ }
185
+ }
150
186
  function openTouchCfg() {
151
187
  const ov = ctx.$('#touchcfg');
152
188
  if (!ov)
@@ -172,17 +208,16 @@ export function initTouch(ctx) {
172
208
  if (tc && !tc.hidden)
173
209
  tc.hidden = true;
174
210
  ctx.$('body')?.classList.remove('touch-mode');
175
- setMinimapCorner(false);
211
+ ctx.onTouchControlsHidden?.();
176
212
  }
177
213
  function showTouchControls() {
178
- // MENU ativo = sem controle virtual: dá pra tocar direto nos botões da tela. QUEM decide é o jogo.
214
+ // A MENU up = no virtual pad: the child touches the on-screen buttons directly. The GAME decides.
179
215
  if (!ctx.padAllowed())
180
216
  return;
181
217
  const tc = ctx.$('#touch-controls');
182
218
  if (tc)
183
219
  tc.hidden = false;
184
220
  ctx.$('body')?.classList.add('touch-mode');
185
- setMinimapCorner(true);
186
221
  ctx.onTouchControlsShown?.();
187
222
  }
188
223
  function applyDirStyle() {
@@ -267,47 +302,37 @@ export function initTouch(ctx) {
267
302
  ctx.onPadDesignApplied?.();
268
303
  return padDesign;
269
304
  }
270
- // --- wiring: #touchcfg (painel "Botões de tela touch") + o botão que o abre/fecha (#opt-touchcfg no painel
271
- // de Movimento) — todo elemento abaixo mora dentro (ou controla) o overlay #touchcfg. ---
272
- const touchCfgBtn = ctx.$('#opt-touchcfg');
273
- if (touchCfgBtn)
274
- touchCfgBtn.addEventListener('click', openTouchCfg);
275
- const touchCfgClose = ctx.$('#touchcfg-close');
276
- if (touchCfgClose)
277
- touchCfgClose.addEventListener('click', closeTouchCfg);
278
- const padDirSel = ctx.$('#pad-dir');
279
- if (padDirSel) {
280
- padDirSel.value = padDir;
281
- padDirSel.addEventListener('change', () => {
282
- padDir = padDirSel.value;
283
- ctx.store.set(KEYS.padDir, padDir);
284
- applyDirStyle();
285
- ctx.srSay(t('sr.touch.dirSet', { tipo: t(padDir === 'cross' ? 'touch.dir.cross' : 'touch.dir.stick') }));
286
- });
305
+ /**
306
+ * The touch panel's controls, where a PAGE carries them (the engine's own panels do not): open and close, the direction, the
307
+ * five sizes and the two presets. The selector is not set here — the boot's `applyDirStyle` sets it to the stored direction.
308
+ */
309
+ function wirePanel() {
310
+ ctx.$('#opt-touchcfg')?.addEventListener('click', openTouchCfg);
311
+ ctx.$('#touchcfg-close')?.addEventListener('click', closeTouchCfg);
312
+ const padDirSel = ctx.$('#pad-dir');
313
+ if (padDirSel) {
314
+ padDirSel.addEventListener('change', () => {
315
+ padDir = padDirSel.value;
316
+ ctx.store.set(KEYS.padDir, padDir);
317
+ applyDirStyle();
318
+ ctx.srSay(t('sr.touch.dirSet', { tipo: t(padDir === 'cross' ? 'touch.dir.cross' : 'touch.dir.stick') }));
319
+ });
320
+ }
321
+ for (const [id, field] of PAD_SLIDERS) {
322
+ const slider = ctx.$(id);
323
+ if (!slider)
324
+ continue;
325
+ slider.addEventListener('input', () => { const patch = {}; patch[field] = parseFloat(slider.value); setPadMm(patch); });
326
+ }
327
+ for (const [id, mm, said] of PAD_PRESETS) {
328
+ ctx.$(id)?.addEventListener('click', () => { setPadMm(mm); ctx.srSay(t(said)); });
329
+ }
287
330
  }
288
- const padSizeEl = ctx.$('#pad-size');
289
- if (padSizeEl)
290
- padSizeEl.addEventListener('input', () => setPadMm({ btn: parseFloat(padSizeEl.value) }));
291
- const padGapEl = ctx.$('#pad-gap');
292
- if (padGapEl)
293
- padGapEl.addEventListener('input', () => setPadMm({ gap: parseFloat(padGapEl.value) }));
294
- const padStickEl = ctx.$('#pad-stick');
295
- if (padStickEl)
296
- padStickEl.addEventListener('input', () => setPadMm({ stick: parseFloat(padStickEl.value) }));
297
- const padTravelEl = ctx.$('#pad-travel');
298
- if (padTravelEl)
299
- padTravelEl.addEventListener('input', () => setPadMm({ travel: parseFloat(padTravelEl.value) }));
300
- const padDpadEl = ctx.$('#pad-dpad');
301
- if (padDpadEl)
302
- padDpadEl.addEventListener('input', () => setPadMm({ dpad: parseFloat(padDpadEl.value) }));
303
- const padPresetChild = ctx.$('#pad-preset-child');
304
- if (padPresetChild)
305
- padPresetChild.addEventListener('click', () => { setPadMm({ btn: 12, gap: 2.5, stick: 16.5, travel: 4, dpad: 11.5 }); ctx.srSay(t('sr.touch.presetChild')); });
306
- const padPresetAdult = ctx.$('#pad-preset-adult');
307
- if (padPresetAdult)
308
- padPresetAdult.addEventListener('click', () => { setPadMm({ btn: 14, gap: 4.5, stick: 20, travel: 5.5, dpad: 14 }); ctx.srSay(t('sr.touch.presetAdult')); });
309
- addEventListener('resize', applyPadPhysical); // recalcula os px ao girar/redimensionar; os mm são fixos
310
- // estado inicial (equivalente aos `applyPadDesign(padDesign); applyPadPhysical(); applyDirStyle();` de boot no game.js)
331
+ wirePanel();
332
+ // Recomputes the px on rotate/resize; the mm are fixed.
333
+ // ⚠️ THE WINDOW COMES THROUGH THE `ctx` (see `TouchCtx.win`). Without a window there is nothing to listen to.
334
+ ctx.win?.addEventListener('resize', applyPadPhysical);
335
+ // initial state: the stored design, geometry and direction applied once at boot
311
336
  applyPadDesign();
312
337
  applyPadPhysical();
313
338
  applyDirStyle();
@@ -320,3 +345,214 @@ export function initTouch(ctx) {
320
345
  getStickDeadPx: () => _stickDeadPx,
321
346
  };
322
347
  }
348
+ /**
349
+ * A BUTTON'S NAME, from the position it fires (ADR-0165): the same in every game — 1 to 4, L1/L2/R1/R2, SELECT, START,
350
+ * and the four directions by their spoken name. The cartridge's word is the FUNCTION, and it never reaches the face. The pad
351
+ * wizard asks by these names where the game names no position (`input/pad-wizard`, ADR-0144 erratum of 2026-09-26).
352
+ */
353
+ const SLOT_NAME = {
354
+ action1: '1', action2: '2', action3: '3', action4: '4',
355
+ leftShoulder: 'L1', leftTrigger: 'L2', rightShoulder: 'R1', rightTrigger: 'R2',
356
+ select: 'SELECT', start: 'START',
357
+ };
358
+ export function buttonName(t, slotAction) {
359
+ if (SLOT_NAME[slotAction])
360
+ return SLOT_NAME[slotAction];
361
+ return slotAction === 'up' || slotAction === 'down' || slotAction === 'left' || slotAction === 'right' ? t(`touch.nome.${slotAction}`) : null;
362
+ }
363
+ /** The four direction slots, in the order the cross draws them. */
364
+ const DIRECTIONS = ['up', 'left', 'right', 'down'];
365
+ /** The four action-button slots, in the diamond's order. */
366
+ const BUTTONS = ['b0', 'b1', 'b2', 'b3'];
367
+ /** The SHOULDERS per corner, top to bottom (ADR-0160): the trigger (2) over the shoulder (1). */
368
+ const SHOULDERS = [['esq', ['bl2', 'bl1']], ['dir', ['br2', 'br1']]];
369
+ /**
370
+ * Builds (or reuses) `#touch-controls` and returns it.
371
+ *
372
+ * ⚠️ IT IS BORN HIDDEN, and that is not a detail: switching by modality belongs to `touch-bindings` — a touch or click
373
+ * SHOWS it, the keyboard or a pad HIDES it. A pad born on screen covers the game of whoever will never touch it.
374
+ *
375
+ * 🔴 ONLY WHAT THE GAME NAMES, since ADR-0162 — which undid ADR-0157's minimum (directions and four buttons in every
376
+ * game, with the physical legend on the unnamed ones): «Vale para todos os botões: somente aparecem se o jogo os
377
+ * nomeia.» SELECT and START always stay, because they are the pause's doors. ⚠️ A game that names no direction does not
378
+ * move through menus by touch; that is why `consumer-quiz` declares the positions it uses. The four buttons sit in a 2×2
379
+ * block by the action's NUMBER (ADR-0160): 1 and 4 on top, 2 and 3 below.
380
+ *
381
+ * Idempotent: mounting twice returns the same node, its content rebuilt for the map of now.
382
+ */
383
+ export function mountTouchControls(ctx, spec) {
384
+ const { t } = ctx;
385
+ const touchControls = ctx.find('#touch-controls') ?? ctx.create('div');
386
+ touchControls.id = 'touch-controls';
387
+ touchControls.className = 'touch';
388
+ touchControls.hidden = true;
389
+ while (touchControls.firstChild)
390
+ touchControls.removeChild(touchControls.firstChild);
391
+ const f = padFacesOf(t, spec);
392
+ // in the order they sit on the screen; a part the game names nothing for is an empty list, never an empty box
393
+ const parts = [...directionPad(ctx, spec, f), ...actionButtons(ctx, spec, f), ...shoulderCorners(ctx, f), systemPills(ctx, f)];
394
+ for (const part of parts)
395
+ touchControls.appendChild(part);
396
+ return touchControls;
397
+ }
398
+ function padFacesOf(t, spec) {
399
+ // 🔴 ONLY WHAT THE GAME NAMES (ADR-0162, superseding ADR-0157's minimum): «Vale para todos os botões: somente
400
+ // aparecem se o jogo os nomeia.» A button on screen is a promise that it does something, and the game is who knows.
401
+ const named = (slot) => spec.gameActions.has(spec.map[slot] ?? '');
402
+ // ADR-0165: the face is the NAME of the position the slot fires; the accessible name is «name, function».
403
+ const nameOf = (slot) => buttonName(t, spec.map[slot] ?? slot) ?? spec.slotLabel(slot);
404
+ const accessible = (slot) => {
405
+ const name = nameOf(slot);
406
+ const purpose = spec.slotLabel(slot);
407
+ return purpose && purpose !== name ? `${name}, ${purpose}` : name;
408
+ };
409
+ return { named, nameOf, accessible };
410
+ }
411
+ /** The directional, where the game names a direction: the analog stick with its knob, or the cross with its arms. */
412
+ function directionPad(ctx, spec, f) {
413
+ const live = DIRECTIONS.filter(f.named);
414
+ if (!live.length)
415
+ return [];
416
+ const kind = spec.dpad === 'analogico' ? 'touch-stick' : 'touch-cross';
417
+ const dir = ctx.create('div');
418
+ dir.id = kind;
419
+ dir.className = kind; // the id is what `touch-bindings` finds; the class is what the stylesheet DRAWS
420
+ if (kind === 'touch-stick') {
421
+ // `touch-bindings` requires the `.touch-knob` inside the base (`if (stick && knob)`), and without it gives up on the
422
+ // whole stick — silently.
423
+ const knob = ctx.create('div');
424
+ knob.className = 'touch-knob';
425
+ dir.appendChild(knob);
426
+ return [dir];
427
+ }
428
+ for (const d of live) {
429
+ const arm = ctx.create('button');
430
+ // ⚠️ THE THREE CLASSES, and each has a reader. `touch-arm` is this module's; `dpad-arm` is the one the stylesheet
431
+ // DRAWS; `dpad-<dir>` is the one `touch-bindings` LIGHTS on touch (`.dpad-up` & co.). With only the first, the arm
432
+ // is an unstyled button that never lights, and no case sees it unless both are mounted together.
433
+ arm.className = `touch-arm dpad-arm dpad-${d}`;
434
+ arm.setAttribute('type', 'button');
435
+ arm.setAttribute('aria-label', f.accessible(d));
436
+ dir.appendChild(arm);
437
+ }
438
+ return [dir];
439
+ }
440
+ /**
441
+ * One button the dispatch can read back: `data-btn` is the slot without its `b` (`b2` -> `2`, `bl1` -> `l1`), which is
442
+ * what `'b' + dataset.btn` in `touch-bindings` recomposes. Writing the ACTION here would be a second source for the same
443
+ * answer, and the child's remapping would stop counting.
444
+ */
445
+ function padButton(ctx, f, slot, className) {
446
+ const button = ctx.create('button');
447
+ button.className = className;
448
+ button.dataset.btn = slot.slice(1);
449
+ button.setAttribute('type', 'button');
450
+ button.setAttribute('aria-label', f.accessible(slot));
451
+ button.textContent = f.nameOf(slot);
452
+ return button;
453
+ }
454
+ /** The four action buttons, where the game names them, in a 2×2 block by the action's NUMBER (ADR-0160). */
455
+ function actionButtons(ctx, spec, f) {
456
+ const live = BUTTONS.filter(f.named);
457
+ if (!live.length)
458
+ return [];
459
+ const rhombus = ctx.create('div');
460
+ rhombus.className = 'touch-pad';
461
+ for (const b of live) {
462
+ const button = padButton(ctx, f, b, 'touch-btn');
463
+ // THE PLACE FOLLOWS THE ACTION, not the slot (ADR-0160: 1 4 on top, 2 3 below): the stylesheet puts each button in
464
+ // its number's cell, and a remapped slot takes the button to the place of the action it now fires.
465
+ button.dataset.acao = spec.map[b] ?? '';
466
+ rhombus.appendChild(button);
467
+ }
468
+ return [rhombus];
469
+ }
470
+ /** THE SHOULDERS, each pair in its top corner (ADR-0160), and only those the game names (ADR-0162) — no empty corner. */
471
+ function shoulderCorners(ctx, f) {
472
+ return SHOULDERS.flatMap(([side, slots]) => {
473
+ const named = slots.filter(f.named);
474
+ if (!named.length)
475
+ return [];
476
+ const corner = ctx.create('div');
477
+ corner.className = `touch-ombros touch-ombros--${side}`;
478
+ for (const s of named)
479
+ corner.appendChild(padButton(ctx, f, s, 'touch-btn touch-ombro'));
480
+ return [corner];
481
+ });
482
+ }
483
+ /*
484
+ * THE TWO SYSTEM PILLS, side by side in the centre: SELECT (the menus) and START (the quick pause), in a console pad's
485
+ * order. ⚠️ BOTH UNCONDITIONAL, for the same reason: since ADR-0155 they are the pause's two doors, and the pause cannot
486
+ * be declined (ADR-0122) — a tablet without a keyboard has no other way to reach "Quit".
487
+ */
488
+ function systemPills(ctx, f) {
489
+ const system = ctx.create('div');
490
+ system.className = 'touch-sistema';
491
+ for (const slot of ['select', 'start']) {
492
+ const b = ctx.create('button');
493
+ b.id = `touch-${slot}`;
494
+ b.className = `touch-btn touch-${slot}`;
495
+ b.setAttribute('type', 'button');
496
+ b.setAttribute('aria-label', f.accessible(slot));
497
+ // ⚠️ AND WRITTEN, not only spoken: a pill with no text is a button a sighted child cannot read.
498
+ b.textContent = f.nameOf(slot);
499
+ system.appendChild(b);
500
+ }
501
+ return system;
502
+ }
503
+ /**
504
+ * What this game does NOT reach by touch, said instead of kept quiet — the half of ADR-0143 §4 that is not markup.
505
+ *
506
+ * 📌 It returns LINES and does not throw: a host gap never takes the boot down, by the same rule the rest of
507
+ * `problems` follows. The sentences go to the consumer that INTEGRATES the engine, not to a child.
508
+ */
509
+ export function touchGaps(spec) {
510
+ // ⚠️ WITHOUT `preset` THE PAD HAS ONLY SELECT AND START (ADR-0162): no direction, no button — not even to move
511
+ // through menus. It is the integrator's gap, and it is said.
512
+ if (!spec.gameActions.size) {
513
+ return ['the virtual pad shows only SELECT and START — no direction and no button: a child on a tablet without a '
514
+ + 'keyboard cannot play — declare `preset` with the positions this game uses and a word for each'];
515
+ }
516
+ // ⚠️ AND THE PARTIAL GAP IS SAID TOO. A game may declare an action no slot fires: it exists on the keyboard and not on
517
+ // touch, and without this line that would show up nowhere.
518
+ const reached = new Set(TOUCH_SLOTS.map((s) => spec.map[s.k]).filter(Boolean));
519
+ const outsideTouch = [...spec.gameActions].filter((a) => !reached.has(a));
520
+ if (!outsideTouch.length)
521
+ return [];
522
+ return [`the virtual pad does not reach ${outsideTouch.join(', ')}: no slot fires them, so a child playing by touch `
523
+ + 'does not have them — remap a slot in the pad panel, or declare fewer actions'];
524
+ }
525
+ /** The factory pad of `input/touch`, the base the non-button sizes are proportioned from. */
526
+ const FACTORY = { btn: 12.5, gap: 3, stick: 18, travel: 4.5, dpad: 12 };
527
+ /** A persona's pad: the button from the sources, the rest in proportion to the factory pad. */
528
+ function pad(btn) {
529
+ const k = btn / FACTORY.btn;
530
+ const r = (n) => Math.round(n * k * 10) / 10;
531
+ return { btn, gap: r(FACTORY.gap), stick: r(FACTORY.stick), travel: r(FACTORY.travel), dpad: r(FACTORY.dpad) };
532
+ }
533
+ /** The four, in the Dev's order. */
534
+ export const PERSONAS_DO_PAD = Object.freeze([
535
+ // 16 mm: an adult's 9.6 mm plus twice the extra spread of a 3-year-old (≈ 2.4 mm each side), rounded up.
536
+ { key: 'crianca-pequena', label: 'motora.pad.crianca-pequena', mm: pad(16) },
537
+ // 14 mm: 9 mm is still missed once in six until 17 (Anthony), and 12.7 mm was the size that did not trouble them.
538
+ { key: 'crianca-grande', label: 'motora.pad.crianca-grande', mm: pad(14) },
539
+ // 11.5 mm: where adult performance levels off (Parhi) — above it there is no gain, only lost room.
540
+ { key: 'adulto-pequeno', label: 'motora.pad.adulto-pequeno', mm: pad(11.5) },
541
+ // 15 mm: a 16–20 mm finger covers anything smaller and hides the label under the thumb (MIT Touch Lab).
542
+ { key: 'adulto-maos-grandes', label: 'motora.pad.adulto-maos-grandes', mm: pad(15) },
543
+ ]);
544
+ /**
545
+ * The persona whose button is closest to `btnMm` — what the step control shows for a pad that was sized before
546
+ * the personas existed (the factory 12.5 mm reads as «small adult»). Ties go to the LARGER button: when in doubt,
547
+ * the easier target.
548
+ */
549
+ export function closestPersona(btnMm) {
550
+ let best = 0;
551
+ PERSONAS_DO_PAD.forEach((p, i) => {
552
+ const d = Math.abs(p.mm.btn - btnMm);
553
+ const dm = Math.abs(PERSONAS_DO_PAD[best].mm.btn - btnMm);
554
+ if (d < dm || (d === dm && p.mm.btn > PERSONAS_DO_PAD[best].mm.btn))
555
+ best = i;
556
+ });
557
+ return best;
558
+ }