@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
@@ -2,617 +2,444 @@
2
2
  // input/gamepad.ts — gamepad READING (stdDirs/padActions/bindActive/padMapFor) + the poll dispatcher
3
3
  // (pollPads) + the accessibility MAPPING WIZARD (openPadWiz/openPadWizFor/closePadWiz/padWizTick). Pure
4
4
  // button->action logic is separated from the DI runtime (initGamepad(ctx)): the Gamepad API is injected
5
- // (ctx.getGamepads, so tests feed a fake pad with no browser) and every cross-subsystem call (menu nav, quiz,
6
- // join/respawn, phase, overlays) is injected too — never reaches `document`/`navigator` or imports game.js.
5
+ // (ctx.getGamepads, so tests feed a fake pad with no browser) and every cross-subsystem call (menu nav, modal,
6
+ // join/respawn, pause, overlays) is injected too — it never reaches `document`/`navigator`.
7
7
  // Boundary: this module only READS buttons/axes and maps them to actions; the physical button ARTWORK/labels
8
- // (PAD_DESIGNS) and the on-screen touch pad are input/devices.ts + input/touch (a parallel extraction) — not here.
9
- import { t } from '../core/i18n.js';
8
+ // (PAD_DESIGNS) and the on-screen touch pad are input/devices.ts + input/touch — not here.
10
9
  import { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
11
- import { migrarMapaDeControle } from './vocabulary-migration.js';
12
- import { GAMEPAD_STANDARD } from './default-bindings.js';
13
- import { tabelaDoPad } from './pad-defaults.js';
14
- import { padCur, padPrevAct, padPrevStart, PAD_DEAD } from './state.js';
15
- // ⚠️ O `oneButton` ENTRA POR IMPORT, e não pelo `ctx` — ao contrário de `input/keydown`, que o recebe por
16
- // getter. A diferença não é de gosto: o `keydown` foi extraído quando o `oneButton` era um `let` do
17
- // `game.js`, e a regra da casa manda o que o JOGO reatribui entrar por getter. Hoje ele é `core/state`,
18
- // ou seja da ENGINE — e um getter obrigaria cada consumidor a lembrar-se de o passar.
19
- //
20
- // ⚠️ E ESQUECER É EXATAMENTE O DEFEITO QUE ISTO CONSERTA (issue #120). Um campo opcional no ctx faria a
21
- // acomodação existir só nos jogos onde alguém se lembrou dela, que é o argumento M3 do ADR-0077 com outro
22
- // substantivo. Binding vivo de `core/state`: não há por onde falhar.
23
- import * as estadoDoJogo from '../core/state.js';
24
- import * as store from '../platform/storage.js';
25
- function bindingAt(map, key) {
26
- const v = map[key];
27
- return typeof v === 'object' && v !== null ? v : undefined;
28
- }
29
- // [axisValue, up, down, left, right] — os 8 passos de um D-pad "POV hat" (eixo alto do DirectInput), repouso ~1.286.
30
- const HAT_STEPS = [
31
- [-1, 1, 0, 0, 0], [-0.7143, 1, 0, 0, 1], [-0.4286, 0, 0, 0, 1], [-0.1429, 0, 1, 0, 1],
32
- [0.1429, 0, 1, 0, 0], [0.4286, 0, 1, 1, 0], [0.7143, 0, 0, 1, 0], [1, 1, 0, 1, 0],
33
- ];
34
- /** Direções pelas FONTES PADRÃO: stick 0/1 (zona morta PAD_DEAD), D-pad 12-15, e o "hat" nos eixos >=6 (POV do
35
- * DirectInput). Controles com os dois direcionais mapeados ficam com ambos vivos (dedo no stick não mata o D-pad). */
36
- export function stdDirs(gp) {
37
- const b = (i) => !!(gp.buttons[i] && gp.buttons[i].pressed);
38
- const ax = (i) => gp.axes[i] || 0;
39
- const d = { left: ax(0) < -PAD_DEAD || b(14), right: ax(0) > PAD_DEAD || b(15), up: ax(1) < -PAD_DEAD || b(12), down: ax(1) > PAD_DEAD || b(13) };
40
- for (let i = 6; i < gp.axes.length; i++) {
41
- const v = gp.axes[i];
42
- if (typeof v !== 'number' || Math.abs(v) > 1.001)
43
- continue; // fora do repouso do hat
44
- for (const [hv, u, dn, l, r] of HAT_STEPS) {
45
- if (Math.abs(v - hv) <= 0.09) {
46
- if (u)
47
- d.up = true;
48
- if (dn)
49
- d.down = true;
50
- if (l)
51
- d.left = true;
52
- if (r)
53
- d.right = true;
54
- break;
55
- }
56
- }
57
- }
58
- return d;
59
- }
60
- /** Está o binding `bd` ativo agora neste gamepad? Digital = pressed; analógico ({ax,s}) = limiar por sinal
61
- * (metade do curso); hat ({av,v}) = valor exato do passo (±0.13 — os 8 passos distam ~0.286). */
62
- export function bindActive(gp, bd) {
63
- if (!bd)
64
- return false;
65
- if (bd.b != null)
66
- return !!(gp.buttons[bd.b] && gp.buttons[bd.b].pressed);
67
- if (bd.ax != null)
68
- return ((gp.axes[bd.ax] || 0) * (bd.s ?? 0)) > 0.5;
69
- if (bd.av != null)
70
- return Math.abs((gp.axes[bd.av] || 0) - (bd.v ?? 0)) <= 0.13;
71
- return false;
72
- }
73
- /** Ações do frame para este gamepad. `custom` = mapa salvo pelo wizard para este `gp.id` (null/`_skip` = usa o
74
- * mapa PADRÃO da Gamepad API "standard": 0=pulo · 1=especial · 2/5/7=correr · 3=troca · 9=START). Direções
75
- * custom caem de volta em stdDirs quando o binding do usuário não está ativo (D-pad/stick continuam vivos). */
76
- export function padActions(gp, custom, tabela = GAMEPAD_STANDARD) {
77
- if (custom && !custom._skip) {
78
- const A = (k) => bindActive(gp, bindingAt(custom, k));
79
- const sd = stdDirs(gp);
80
- return {
81
- left: A('left') || sd.left, right: A('right') || sd.right, up: A('up') || sd.up, down: A('down') || sd.down,
82
- action2: A('action2'), action1: A('action1'), action4: A('action4'), action3: A('action3'), _start: A('action2') || A('start'), _pause: A('start'),
83
- };
84
- }
85
- const b = (i) => !!(gp.buttons[i] && gp.buttons[i].pressed);
86
- const sd = stdDirs(gp);
87
- // ⚠️ OS ÍNDICES SAEM DA TABELA DECLARADA, e não de literais aqui. Enquanto eram literais, esta linha e
88
- // `input/default-bindings` DISCORDAVAM e nada notava — a mesma forma de defeito que o gate do toque
89
- // apanhou: duas tabelas que concordam entre si não provam nada sobre um terceiro que as lê.
90
- //
91
- // ⚠️ E A DISCORDÂNCIA ERA REAL: aqui estava `action1: b(2) || b(5) || b(7)`, ou seja X, R1 e R2 todos a
92
- // correr, enquanto a tabela declara R1 como `rightShoulder` e R2 como `rightTrigger`. O ADR-0086 registrou
93
- // esta mudança como o asterisco do seu «zero movimento»: nenhum VERBO muda de botão, mas `run` perde dois
94
- // dos seus três. Quem usava R1 para correr sente — e é o preço de os quatro ombros existirem.
95
- // 📌 A TABELA CHEGA POR PARÂMETRO desde o ADR-0115: é a fábrica da engine COM o padrão deste jogo por cima,
96
- // resolvida em `input/pad-defaults`. O padrão da assinatura é o da engine, então quem chamava com dois
97
- // argumentos continua a ler exactamente o que lia.
98
- // ⚠️ E ela só decide neste ramo, que é o certo: o ramo de cima é o mapa que a CRIANÇA gravou no assistente,
99
- // e o padrão de um jogo não se sobrepõe a uma escolha dela.
100
- const B = tabela;
101
- const at = (a) => { const i = B[a]; return typeof i === 'number' ? b(i) : false; };
102
- return {
103
- left: sd.left, right: sd.right, up: sd.up, down: sd.down,
104
- action1: at('action1'), action2: at('action2'), action3: at('action3'), action4: at('action4'),
105
- leftShoulder: at('leftShoulder'), leftTrigger: at('leftTrigger'),
106
- rightShoulder: at('rightShoulder'), rightTrigger: at('rightTrigger'),
107
- // ⚠️ `start` COMO POSIÇÃO, e não só como os derivados abaixo. Faltava, e o gate da tabela foi quem
108
- // o encontrou: quem quisesse saber «o START está apertado?» tinha de ler `_start`, que começa por
109
- // underscore e quer dizer outra coisa (fecha diálogo, e aceita a ação 2 também).
110
- start: at('start'), select: at('select'),
111
- // `_start` e `_pause` são DERIVADOS e não posições: «fecha diálogo» aceita a ação 2 ou o START, «pausa»
112
- // só o START. Ficam escritos aqui porque descrevem o que a raiz faz com duas posições, não uma terceira.
113
- _start: at('action2') || at('start'), _pause: at('start'),
114
- };
115
- }
10
+ import { createPadWizard, PADWIZ_ORDER as ORDEM_DO_ASSISTENTE } from './pad-wizard.js';
11
+ // 📌 The pad's frame memory, the stored maps and the game's button table arrive through the ctx, built once by the root
12
+ // (ADR-0232 D4): as module state, two roots on one page shared a pad's previous frame and each other's maps.
13
+ // 📌 `oneButton` (the motor empathy's one-button mode, issue #120) arrives through the ctx, as `input/keydown`'s does:
14
+ // the settings store is built by the root and passed in (ADR-0232, issue #207). The field is REQUIRED, which is what the
15
+ // old live import defended — an optional field would make the accommodation exist only where someone remembered it
16
+ // (ADR-0077's M3 argument); a required one does not compile without it.
17
+ // ---------------------------------------------------------------------------------------------
18
+ // Gamepad API surface (minimal, adapter-friendly — mirrors the real Gamepad/GamepadButton shape)
116
19
  // ---------------------------------------------------------------------------------------------
117
- // Wizard data (a ORDEM dos passos + a animação de demonstração; as PALAVRAS vêm do jogo)
20
+ // 🔴 WHAT A PAD IS DOING lives in `input/pad-reading`: it is the PURE half — buttons and axes in, positions out. Here
21
+ // is the wiring, which can only be read with a ctx in hand: the conductor that polls the pads every frame and decides
22
+ // where the reading goes.
23
+ // ⚠️ NO ALIAS: a re-export would keep alive a published path nothing in the engine imports (issue #204), and the
24
+ // migration note is what says where each name went.
25
+ import { padActions, oneButtonAtOnce, } from './pad-reading.js';
26
+ // ---------------------------------------------------------------------------------------------
27
+ // Wizard data (the ORDER of the steps; the WORDS and the demonstration come from the game)
118
28
  // ---------------------------------------------------------------------------------------------
119
29
  /**
120
- * A ORDEM em que o assistente pergunta. SÓ a ordem.
121
- *
122
- * ⚠️ ELA CARREGAVA AS PALAVRAS, EM PORTUGUÊS CRU, DENTRO DA ENGINE: `['action2', 'PULAR']`,
123
- * `['action1', 'CORRER / INTERAGIR']`. Era o defeito do ADR-0074 na sua forma mais visível — não só
124
- * vocabulário de plataforma dentro do motor, mas vocabulário de plataforma NUM IDIOMA SÓ, à frente de uma
125
- * criança, num ficheiro que o pilar 3 obriga a ser localizável.
30
+ * The ORDER in which the wizard asks. ONLY the order — the same list `input/pad-wizard` walks, where it lives apart
31
+ * from any game (issue #182).
126
32
  *
127
- * A palavra vem agora de `ctx.rotuloDaAcao`, que o jogo fornece pelo seu preset. Aqui fica o que é mesmo da
128
- * engine: a sequência em que se pergunta — direções primeiro, ação depois, sistema no fim —, que é uma
129
- * decisão de ergonomia do assistente e não do jogo.
33
+ * ⚠️ NO WORDS HERE: the word comes from `ctx.actionLabel`, which the game supplies through its preset (ADR-0074 —
34
+ * platformer vocabulary, in one language, inside the engine was the defect this removed). What stays is what really
35
+ * is the engine's: the sequence of the questions — directions first, actions next, system last —, an ergonomic
36
+ * decision of the wizard and not of the game.
130
37
  *
131
- * ⚠️ E UMA AÇÃO QUE O JOGO NÃO NOMEIA NÃO É PERGUNTADA. `wizPrompt` salta-a, porque um jogo que não usa a
132
- * posição não tem o que mapear nela — e perguntar produziria um passo mudo, ou pior, um passo a dizer
133
- * `action7` em voz alta.
38
+ * ⚠️ AND AN ACTION THE GAME DOES NOT NAME IS NOT ASKED. `input/pad-wizard` skips it, because a game that does not use
39
+ * the position has nothing to map on it — and asking would produce a mute step or, worse, a step saying `action7` out
40
+ * loud. The exception is the system's two: START and SELECT are always asked, in the engine's words, because no game may
41
+ * name them and no pad may lack them (ADR-0144, erratum of 2026-09-26).
134
42
  */
43
+ export const PADWIZ_ORDER = ORDEM_DO_ASSISTENTE;
135
44
  /**
136
- * AS POSIÇÕES QUE O MODO DE UM BOTÃO NÃO CORTA. Pausar é a SAÍDA, não uma jogada.
45
+ * WHAT EACH ABSENCE MEANS, AS DATA (ADR-0224). A table and not ten `??` scattered through whoever mounts: the meaning
46
+ * of an absence is a DECISION, and a composition root should hold only wiring — ten decisions in it are ten branches in
47
+ * a function the ratchet already watches (ADR-0221, erratum).
137
48
  *
138
- * ⚠️ Cortar o START prenderia a criança dentro da partida — é o mesmo raciocínio que põe «a saída
139
- * primeiro» no ADR-0044 e que fez a armadilha de foco existir no ADR-0090. Uma acomodação que tranca não
140
- * é acomodação. Os derivados (`_start`, `_pause`) também passam: eles descrevem o que a raiz faz com
141
- * estas duas posições, não uma terceira.
49
+ * ⚠️ `worldRunning` is not here because its absence has no universal answer: it depends on whoever mounts knowing
50
+ * which menus they have open. It comes in as a parameter, which also makes it the only absence a host can answer.
142
51
  */
143
- const FORA_DO_CORTE = new Set(['start', 'select', '_start', '_pause']);
52
+ const SILENT_PAD_ANSWERS = Object.freeze({
53
+ navTitle: () => { }, // no title screen to navigate
54
+ attractActive: () => false, // no demonstration running
55
+ stopAttract: () => { }, // so nothing to stop
56
+ hasModal: () => false, // no open challenge: the d-pad belongs to the game
57
+ modalInput: () => { }, // and so nothing to feed
58
+ joinPlayer: () => false, // nobody joins a running game midway
59
+ respawnPlayer: () => { }, // an abandoned screen stays abandoned
60
+ clearWaitingBadge: () => { }, // the waiting badge is the game's HUD; with no game declaring it, it does not exist
61
+ wizardStep: () => { }, // the mapping wizard speaks, with no drawing
62
+ wizardTick: () => { }, // and so nothing animates
63
+ });
144
64
  /**
145
- * O MODO DE UM BOTÃO, APLICADO AO CONTROLE — a metade que faltava da empatia motora (issue #120).
146
- *
147
- * ⚠️ ELE VALIA SÓ NO TECLADO. `input/keydown.ts:407` solta todas as outras teclas de jogo quando uma nova
148
- * chega com o modo ligado; `pollPads` não tinha equivalente nenhum, e `grep oneButton` neste ficheiro
149
- * devolvia zero. Uma criança que ligasse o modo e tivesse um controle na mão **não estava no modo** — sem
150
- * erro, sem aviso, sem sintoma, porque as definições continuavam a dizer que estava ligado.
151
- *
152
- * ⚠️ AS DIREÇÕES CONTAM, e é isso que torna a regra fiel ao teclado: lá, `isGameKeyCode` inclui as teclas
153
- * de `p.ctrl`, que são as quatro direções — andar e pular não coexistem. Um filtro que poupasse as
154
- * direções seria mais confortável e estaria a simular outra deficiência.
155
- *
156
- * ⚠️ E A ESCOLHA DE QUEM SOBREVIVE É DIFERENTE DA DO TECLADO, POR NECESSIDADE. No teclado a chegada nova
157
- * ganha, porque HÁ uma chegada: o evento diz qual é. Um controle é lido por SONDAGEM — o que chega é um
158
- * retrato, sem ordem. Então mantém-se a que já valia, e só quando ela solta é que a próxima assume. É o
159
- * que impede o botão de correr de ser cortado porque o polegar encostou noutro, e é a mesma leitura de
160
- * «segurar» que o ADR-0077 dá.
65
+ * The cartridge's answers with every absence already resolved. 📌 A field DECLARED as `undefined` is an absence like any
66
+ * other — spreading the raw object over the table would erase the answer with an `undefined`, which is the silent
67
+ * defect this function exists not to have.
68
+ */
69
+ export function padGameAnswers(hooks, worldRunningWhenSilent) {
70
+ const declared = {};
71
+ for (const [name, answer] of Object.entries(hooks ?? {}))
72
+ if (answer !== undefined)
73
+ declared[name] = answer;
74
+ return { worldRunning: worldRunningWhenSilent, ...SILENT_PAD_ANSWERS, ...declared };
75
+ }
76
+ /**
77
+ * THE SEAT FIELDS, SEEDED BY THE ENGINE (ADR-0224). 📏 Measured: the players a cartridge declares are
78
+ * `{ ctrl, audioSink? }`, and this transport needs to know whose each pad is (`pad`), who is still waiting (`waiting`)
79
+ * and who left the screen (`quit`).
161
80
  *
162
- * A POLÍTICA é a mesma do `keydown`; a IMPLEMENTAÇÃO não pode ser partilhada hoje porque as formas do
163
- * estado diferem — lá é um `Set` de códigos de tecla, aqui é um retrato de booleanos por posição. Unificar
164
- * as duas é trabalho à parte, e escrevê-lo aqui é a alternativa a fingir que não há duas.
81
+ * 🔴 IT SEEDS THE OBJECT ITSELF and returns the SAME list — copying would lose the seat on the next frame, because
82
+ * `takeSeat` writes into this object. That is also why there is no `.map`: the identity is what carries the state.
83
+ * 📌 It only touches whoever does not have the fields yet, so a cartridge that declares them keeps its own.
165
84
  */
166
- export function umBotaoPorVez(
167
- // ⚠️ O ANTERIOR É TIPADO PELO QUE ESTA FUNÇÃO LÊ, e não por `PadActions`: o `padPrevAct[gi]` do laço é
168
- // `PadState`, mais frouxo, e exigir a forma completa obrigaria o chamador a um molde que não descreve o
169
- // que se passa aqui — só se pergunta «esta chave estava em baixo?».
170
- anterior, atual, ligado) {
171
- if (!ligado)
172
- return atual;
173
- const cortaveis = Object.keys(atual).filter((k) => !FORA_DO_CORTE.has(k));
174
- const ativas = cortaveis.filter((k) => atual[k] === true);
175
- if (ativas.length <= 1)
176
- return atual;
177
- // A que já valia tem prioridade; sem nenhuma, a primeira do retrato assume.
178
- const mantida = ativas.find((k) => anterior[k] === true) ?? ativas[0];
179
- const saida = { ...atual };
180
- for (const k of ativas)
181
- if (k !== mantida)
182
- saida[k] = false;
183
- return saida;
85
+ export function seatEveryPlayer(list) {
86
+ for (const p of list) {
87
+ const seat = p;
88
+ seat.pad ??= -1;
89
+ seat.waiting ??= false;
90
+ seat.quit ??= false;
91
+ }
92
+ return list;
184
93
  }
185
- export const PADWIZ_ORDER = [
186
- // Direções primeiro: são o que a criança encontra sem pensar, e acertar as quatro dá confiança para as
187
- // outras dez.
188
- 'up', 'down', 'left', 'right',
189
- // O losango, na ordem em que o dedo o percorre neste projeto (ADR-0086 §2).
190
- 'action2', 'action1', 'action4', 'action3',
191
- // ⚠️ OS QUATRO OMBROS FALTAVAM AQUI ATÉ 2026-09-06, e a falta era um buraco de acessibilidade e não uma
192
- // omissão cosmética: o assistente existe PARA controles que não são «standard» — genéricos, adaptados,
193
- // de uma mão —, e um jogo que declarasse `leftShoulder` não tinha por onde a criança o mapear. Cinco das
194
- // quatorze posições eram inalcançáveis exatamente para quem mais precisa do assistente.
195
- 'leftShoulder', 'leftTrigger', 'rightShoulder', 'rightTrigger',
196
- // Sistema por último: `start` e `select` costumam ser os botões mais pequenos e mais escondidos, e pedi-los
197
- // no fim deixa a criança já habituada ao ritmo do assistente quando chega neles.
198
- 'start', 'select',
199
- ];
200
- /** Demonstração animada de cada ação (frames reais do jogo) mostrada durante o passo correspondente do wizard. */
201
- export const PADWIZ_ANIM = {
202
- up: { seq: ['escada/0', 'escada/1'], hold: 9, cls: 'pw-up' },
203
- down: { seq: ['escada/1', 'escada/0'], hold: 9, cls: 'pw-down' },
204
- left: { seq: ['andar/0', 'andar/1', 'andar/2', 'andar/3', 'andar/4', 'andar/5', 'andar/6', 'andar/7'], hold: 4, cls: 'pw-left', flip: 1 },
205
- right: { seq: ['andar/0', 'andar/1', 'andar/2', 'andar/3', 'andar/4', 'andar/5', 'andar/6', 'andar/7'], hold: 4, cls: 'pw-right' },
206
- action2: { seq: ['pulo/0', 'pulo/0', 'pulo/1', 'pulo/1'], hold: 7, cls: 'pw-jump' },
207
- action1: { seq: ['correr/0', 'correr/1', 'correr/2', 'correr/3'], hold: 3, cls: 'pw-run' },
208
- action4: { fx: '👟 🕷️ 🎈 🐇 🦘', cls: 'pw-swap', noimg: 1 },
209
- action3: { seq: ['idle/0', 'idle/1', 'idle/2', 'idle/3'], hold: 8, fx: '✨', cls: 'pw-especial' },
210
- start: { fx: 'PAUSA', cls: 'pw-start', noimg: 1 },
211
- };
94
+ /** The eight positions THIS transport reads: the d-pad and the four actions. The shoulders and triggers a cartridge may
95
+ * declare have no reading on the pad — the list says what is true today. */
96
+ const PAD_POSITIONS = Object.freeze(['left', 'right', 'up', 'down', 'action1', 'action2', 'action3', 'action4']);
97
+ /** Inside a modal the ORDER is the rule — `input/keydown`'s: a pad delivers a snapshot with no order of arrival, and
98
+ * with left and confirm in the same frame it is this list that decides the child moves instead of confirming. */
99
+ const MODAL_BY_POSITION = Object.freeze([
100
+ ['left', 'left'], ['right', 'right'], ['up', 'up'], ['down', 'down'], ['action2', 'confirm'], ['action3', 'erase'],
101
+ ]);
212
102
  export function initGamepad(ctx) {
213
- const _padMaps = new Map(); // cache id -> mapa custom (evita reparsear JSON a cada frame)
214
- let padWiz = null;
215
- let padWizAutoResume = false; // wizard aberto automaticamente no meio do jogo -> retoma a fase ao fechar
216
- let padWizAnim = null;
217
- function padMapFor(id) {
218
- // ⚠️ O MAPA SALVO PASSA PELO TRADUTOR NA LEITURA. Ele é indexado por AÇÃO — `{ jump: { b: 0 } }` — e um
219
- // mapa gravado antes do ADR-0086 tem as chaves antigas, que `bindingAt` já não procura: o controle
220
- // custom simplesmente pararia de responder, e o jogo cairia no mapa padrão sem dizer nada.
221
- // É o mais caro dos três formatos a perder: um mapa destes existe porque alguém passou por um assistente
222
- // de nove passos, botão a botão, quase sempre porque o controle NÃO é «standard» — e controles genéricos
223
- // ou adaptados raramente são. Ver `input/vocabulary-migration.ts`.
224
- if (!_padMaps.has(id)) {
225
- _padMaps.set(id, migrarMapaDeControle(store.getJSON('incl_padmap_' + id, null)));
226
- }
227
- return _padMaps.get(id) ?? null;
228
- }
229
- function actionsFor(gp, tabela) { return padActions(gp, padMapFor(gp.id), tabela); }
230
- // ----- wizard: anúncio + demo animada (DOM-facing, thin) -----
231
- // ⚠️ O PARÂMETRO CHAMAVA-SE `t`, E ERA ELE QUE FECHAVA A PORTA. Dentro desta função o `t` do
232
- // `core/i18n` estava sombreado, então traduzir uma frase aqui era impossível sem primeiro reparar no
233
- // sombreamento — e não há erro nenhum a apontá-lo. Foi assim que cinco frases em português cru ficaram a
234
- // falar dentro do motor (#123): não por decisão, por um nome.
235
- function wizSay(frase) {
236
- const el = ctx.$('#padwiz-prompt');
237
- if (el)
238
- el.textContent = frase;
239
- ctx.srSay(frase);
240
- }
241
- function wizDemo(k) {
242
- const d = ctx.$('#padwiz-demo');
243
- const img = ctx.$('#padwiz-demo-img');
244
- const fx = ctx.$('#padwiz-demo-fx');
245
- if (!d)
246
- return;
247
- const a = k ? PADWIZ_ANIM[k] : null;
248
- d.className = a ? a.cls : '';
249
- padWizAnim = null;
250
- if (fx)
251
- fx.textContent = (a && a.fx) || '';
252
- if (img) {
253
- img.style.display = a && a.noimg ? 'none' : '';
254
- img.style.transform = a && a.flip ? 'scaleX(-1)' : '';
255
- if (a && a.seq) {
256
- img.src = ctx.spriteBase + a.seq[0] + '.png';
257
- padWizAnim = { seq: a.seq, hold: a.hold || 6, t: 0 };
103
+ const { t } = ctx;
104
+ const { padCur, padPrevAct, padPrevStart } = ctx.input;
105
+ let padWizAutoResume = false; // the wizard opened by itself mid-game -> resume when it closes
106
+ // the root's one cache of stored maps (input/pad-wizard): a map saved by the engine's own wizard is read here next frame
107
+ const padMapFor = (id) => ctx.padMaps.padMap(id);
108
+ function actionsFor(gp, table) { return padActions(gp, padMapFor(gp.id), table); }
109
+ // the wizard asks; what a position LOOKS like is drawn by the game (note CD), through the two hooks it may answer
110
+ const wizard = createPadWizard({
111
+ maps: ctx.padMaps, t,
112
+ getGamepads: () => ctx.getGamepads(),
113
+ actionLabel: (action) => ctx.actionLabel(action),
114
+ say: (phrase) => { const el = ctx.$('#padwiz-prompt'); if (el)
115
+ el.textContent = phrase; ctx.srSay(phrase); },
116
+ progress: (text) => { const pr = ctx.$('#padwiz-progress'); if (pr)
117
+ pr.textContent = text; },
118
+ srAlert: (phrase) => ctx.srAlert(phrase),
119
+ onStep: (position) => ctx.wizardStep(position),
120
+ onTick: () => ctx.wizardTick(),
121
+ onClose: (gi) => {
122
+ const ov = ctx.$('#padwiz');
123
+ if (ov)
124
+ ov.hidden = true;
125
+ // no phantom edges: the button still HELD from the last step (SELECT) must not open the menus or act on resume
126
+ try {
127
+ const pads = ctx.getGamepads() ?? [];
128
+ const gp = pads[gi];
129
+ if (gp) {
130
+ const c = actionsFor(gp);
131
+ padCur[gi] = c;
132
+ padPrevAct[gi] = c;
133
+ padPrevStart[gi] = c._start;
134
+ }
258
135
  }
259
- else if (!a)
260
- img.src = ctx.spriteBase + 'idle/0.png';
261
- }
262
- }
263
- function wizDemoTick() {
264
- if (!padWizAnim)
265
- return;
266
- const a = padWizAnim;
267
- a.t++;
268
- const img = ctx.$('#padwiz-demo-img');
269
- if (img)
270
- img.src = ctx.spriteBase + a.seq[Math.floor(a.t / a.hold) % a.seq.length] + '.png';
271
- }
272
- /**
273
- * Anda até o próximo passo que ESTE jogo usa, ou fecha se não houver mais.
274
- *
275
- * ⚠️ UMA FUNÇÃO SÓ, e antes eram duas com regras diferentes: `wizBind` incrementava e fechava no fim,
276
- * `wizPrompt` saltava as não nomeadas. Depois do último passo nomeado o assistente ficava aberto a
277
- * apontar para uma posição que o jogo não usa, e só fechava no tique seguinte — uma criança veria o
278
- * assistente pendurado, sem pergunta nenhuma na tela.
279
- */
280
- function wizAvancar() {
281
- if (!padWiz)
282
- return;
283
- while (padWiz.step < PADWIZ_ORDER.length && !ctx.rotuloDaAcao(PADWIZ_ORDER[padWiz.step]))
284
- padWiz.step++;
285
- if (padWiz.step >= PADWIZ_ORDER.length)
286
- closePadWiz(true);
287
- }
288
- function wizPrompt() {
289
- if (!padWiz)
290
- return;
291
- wizAvancar();
292
- if (!padWiz)
293
- return; // fechou ao avançar
294
- const acao = PADWIZ_ORDER[padWiz.step];
295
- const rotulo = ctx.rotuloDaAcao(acao);
296
- wizSay(t('pad.wiz.step', { n: padWiz.step + 1, total: PADWIZ_ORDER.length, acao: rotulo }));
297
- wizDemo(acao); // demonstração animada do que a ação FAZ
298
- const pr = ctx.$('#padwiz-progress');
299
- // O travessão da lista vazia fica cru de propósito: é pontuação, não idioma.
300
- if (pr)
301
- pr.textContent = t('pad.wiz.mapped', { lista: Object.keys(padWiz.map).join(' · ') || '—' });
302
- }
303
- function wizBind(bd) {
304
- if (!padWiz)
305
- return;
306
- padWiz.map[PADWIZ_ORDER[padWiz.step]] = bd;
307
- padWiz.step++;
308
- padWiz.release = true; // exige soltar antes do próximo passo (mesmo botão segurado não dobra pro passo seguinte)
309
- wizAvancar(); // salta o que este jogo não usa, e fecha se o resto da lista for tudo isso
310
- }
311
- // ----- wizard: abrir/fechar -----
136
+ catch { /* a pad API that throws here only costs the edge baseline, never the closing */ }
137
+ if (padWizAutoResume) {
138
+ padWizAutoResume = false;
139
+ if (ctx.pauseMenu())
140
+ ctx.resume(0);
141
+ }
142
+ // last: the overlay is hidden and the game resumed, so the focus goes back to what is really in front (ADR-0248)
143
+ ctx.wizardClosed();
144
+ },
145
+ });
312
146
  function openPadWiz() {
313
147
  const ov = ctx.$('#padwiz');
314
148
  if (!ov)
315
149
  return;
316
150
  ov.hidden = false;
317
151
  ctx.frontOverlay(ov);
318
- padWiz = { gi: -1, id: '', step: -1, base: null, map: {}, release: false, baseWait: false, axTrack: null, timer: null };
319
- wizSay(t('pad.wiz.pressAny'));
320
- wizDemo(null);
321
- const pr = ctx.$('#padwiz-progress');
322
- if (pr)
323
- pr.textContent = '';
324
- padWiz.timer = setInterval(padWizTick, 30);
152
+ wizard.open();
325
153
  }
326
- // Wizard aberto AUTOMATICAMENTE (controle DirectInput sem mapa apertou algo): já sabemos qual controle é.
154
+ // The wizard opened BY ITSELF (a DirectInput pad with no map pressed something): we already know which pad it is.
327
155
  function openPadWizFor(gp) {
328
156
  const ov = ctx.$('#padwiz');
329
157
  if (!ov)
330
158
  return;
331
159
  ov.hidden = false;
332
160
  ctx.frontOverlay(ov);
333
- padWiz = { gi: gp.index, id: gp.id, step: -1, base: null, map: {}, release: false, baseWait: true, axTrack: null, timer: null };
334
- wizSay(t('pad.wiz.detected', { id: gp.id }));
335
- wizDemo(null);
336
- const pr = ctx.$('#padwiz-progress');
337
- if (pr)
338
- pr.textContent = '';
339
- padWiz.timer = setInterval(padWizTick, 30);
161
+ wizard.openFor(gp);
340
162
  }
341
- function closePadWiz(save) {
342
- if (!padWiz)
343
- return;
344
- if (padWiz.timer != null)
345
- clearInterval(padWiz.timer);
346
- if (save && padWiz.id) {
347
- store.setJSON('incl_padmap_' + padWiz.id, padWiz.map);
348
- _padMaps.set(padWiz.id, padWiz.map);
349
- ctx.srAlert(t('sr.pad.mapSaved', { id: padWiz.id }));
163
+ const closePadWiz = (save) => wizard.close(save);
164
+ const padWizTick = () => wizard.tick();
165
+ const cancelBtn = ctx.$('#padwiz-cancel');
166
+ if (cancelBtn)
167
+ cancelBtn.addEventListener('click', () => closePadWiz(false));
168
+ // ----- poll (called every frame of the loop) -----
169
+ /** This frame's reading for ONE pad: the actions now, the edges against the previous frame, and the seat. */
170
+ function readPad(gp, players) {
171
+ const gi = gp.index;
172
+ const prev = padPrevAct[gi] || {};
173
+ // ⚠️ THE SEAT IS READ BEFORE THE ACTIONS, and the reason is ADR-0115: this game's button table is declared PER SEAT,
174
+ // and it is read inside `actionsFor`. Read after, the actions would be taken before knowing whose pad it is — and a
175
+ // per-seat default would arrive late.
176
+ const owner = players.findIndex((p) => p.pad === gi);
177
+ // ⚠️ AND A PAD NOT YET ASSIGNED (`owner < 0`) READS SEAT 0, not "none": it is producing edges on the title screen,
178
+ // and an empty map there would leave the child with no way to choose their own game.
179
+ // THE MOTOR EMPATHY SIMULATION APPLIED TO THE PAD (issue #120). Without this line, a child with the one-button mode
180
+ // on and a pad in hand WAS NOT in the mode — and nothing anywhere said so.
181
+ const cur = oneButtonAtOnce(prev, actionsFor(gp, ctx.padTable(ctx.getNumPlayers(), owner < 0 ? 0 : owner)), ctx.oneButton());
182
+ const startEdge = cur._start && !padPrevStart[gi];
183
+ padPrevStart[gi] = cur._start;
184
+ const pauseEdge = cur._pause && !prev._pause;
185
+ const selectEdge = !!cur.select && !prev.select;
186
+ const edge = (k) => cur[k] && !prev[k];
187
+ const released = (k) => !!prev[k] && !cur[k];
188
+ padCur[gi] = cur;
189
+ padPrevAct[gi] = cur;
190
+ // 📌 START counts as "yes" ONLY on the title: there it is the button that begins the game, and on the pause card or
191
+ // the bar it is the EXIT (ADR-0044 item 7). One parameter instead of three lists equal but for one term.
192
+ const navKeys = (withStart = false) => ({
193
+ yes: edge('action2') || (withStart && startEdge), no: edge('action3'),
194
+ up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right'),
195
+ });
196
+ return { gp, gi, owner, players, cur, startEdge, pauseEdge, selectEdge, edge, released, navKeys };
197
+ }
198
+ /** Did any of the six menu intents happen this frame? */
199
+ // 📌 The `!!` is what `NavKeys` asks for: its six fields are OPTIONAL, because not every transport produces them all.
200
+ const anyIntent = (k) => !!(k.yes || k.no || k.up || k.down || k.left || k.right);
201
+ /** A non-standard (DirectInput) pad with NO stored map pressed something: pause everything and open the wizard on it. */
202
+ function wizardTookOver(gp) {
203
+ if (gp.mapping === 'standard' || padMapFor(gp.id) || !gp.buttons.some((b) => b && b.pressed))
204
+ return false;
205
+ padWizAutoResume = ctx.worldRunning();
206
+ if (ctx.worldRunning())
207
+ ctx.pause();
208
+ openPadWizFor(gp);
209
+ return true;
210
+ }
211
+ /** A win: START/jump close the modal (Play again), and nothing from this pad reaches the game underneath it. */
212
+ function winOverlayTook(f) {
213
+ const winOv = ctx.$('#win-overlay');
214
+ if (!winOv || winOv.hidden)
215
+ return false;
216
+ if (f.startEdge) {
217
+ const b = ctx.$('#btn-again');
218
+ if (b)
219
+ b.click();
350
220
  }
351
- else if (padWiz.id && !_padMaps.get(padWiz.id)) {
352
- _padMaps.set(padWiz.id, { _skip: true }); // cancelou: usa o mapa PADRÃO nesta sessão (não persiste, evita reabrir em loop)
221
+ return true;
222
+ }
223
+ /** The title menu: the pad navigates, and in a multiplayer game only P1 chooses. */
224
+ function steerTitle(f) {
225
+ const k = f.navKeys(true);
226
+ if (!anyIntent(k))
227
+ return;
228
+ if (ctx.getNumPlayers() > 1 && f.owner > 0) {
229
+ ctx.srSay(t('sr.title.waitP1'));
230
+ return;
231
+ } // only P1 chooses
232
+ ctx.navTitle(k); // the title menu, navigable by pad
233
+ }
234
+ /** A menu is open: START resumes, action 4 is pressed, and the d-pad navigates the quick bar, the shared dialog or the seat's own card. */
235
+ function steerPause(f) {
236
+ const pi = f.owner < 0 ? 0 : f.owner;
237
+ if (f.pauseEdge) {
238
+ ctx.resume(pi);
239
+ return;
240
+ } // START resumes — if this seat's START reaches this screen's pause (the root's)
241
+ if (ctx.leadsTheScreen && !ctx.leadsTheScreen(pi))
242
+ return; // another seat of a shared screen steers none of its menus
243
+ // ACTION 4 IS PRESSED, as the keyboard's key rises: no menu intent reads it, and the controller hands it to the menus as the
244
+ // seat's key, which the root answers where it answers the keyboard's — in the quick pause, the menus' second door (ADR-0155
245
+ // erratum «Ambos»). The pad decides nothing about it; one frame, one door, as SELECT.
246
+ if (f.edge('action4')) {
247
+ ctx.press('action4', 'gamepad', pi);
248
+ return;
353
249
  }
354
- const gi = padWiz.gi;
355
- padWiz = null;
356
- const ov = ctx.$('#padwiz');
357
- if (ov)
358
- ov.hidden = true;
359
- // sem edges fantasmas: o botão ainda SEGURADO do último passo (START) não pode pausar/agir ao retomar
360
- try {
361
- const pads = ctx.getGamepads() ?? [];
362
- const gp = pads[gi];
363
- if (gp) {
364
- const c = actionsFor(gp);
365
- padCur[gi] = c;
366
- padPrevAct[gi] = c;
367
- padPrevStart[gi] = c._start;
368
- }
250
+ const k = f.navKeys();
251
+ if (!anyIntent(k))
252
+ return;
253
+ // THE QUICK BAR FIRST, in the order `ui/menu-nav` steers a key: the quick pause (ADR-0155 §1) is a menu, so its frames come
254
+ // HERE and never reach `steerGame`'s bar. START was answered above, so the bar's exits here are back, and START by `resume`.
255
+ if (ctx.onBar(pi)) {
256
+ ctx.navBar(pi, k, false);
257
+ return;
369
258
  }
370
- catch { /* espelha o try/catch silencioso do original */ }
371
- if (padWizAutoResume) {
372
- padWizAutoResume = false;
373
- if (ctx.menuDePausa())
374
- ctx.retomar();
259
+ const dlg = ctx.sharedDialogOpen();
260
+ if (dlg) {
261
+ ctx.navDialog(dlg, k);
262
+ return;
375
263
  }
264
+ const menu = ctx.getPauseMenu(pi);
265
+ if (menu && !menu.hidden)
266
+ ctx.navPause(menu, pi, k);
376
267
  }
377
- // ----- wizard: tick (roda a cada 30ms enquanto aberto) -----
378
- function padWizTick() {
379
- if (!padWiz)
268
+ /** Assignment BY ORDER OF ACTION: any button binds -> the 1st pad to act -> the 1st player without a pad. */
269
+ function takeSeat(f) {
270
+ if (!f.startEdge && !PAD_POSITIONS.some((k) => f.edge(k)))
380
271
  return;
381
- wizDemoTick();
382
- const pads = ctx.getGamepads() ?? [];
383
- if (padWiz.gi < 0) {
384
- for (const gp of pads) {
385
- if (gp && gp.buttons.some((b) => b && b.pressed)) {
386
- padWiz.gi = gp.index;
387
- padWiz.id = gp.id;
388
- padWiz.baseWait = true;
389
- wizSay(t('pad.wiz.releaseAll', { id: gp.id }));
390
- break;
391
- }
392
- }
272
+ const waitI = f.players.findIndex((p) => p && p.waiting);
273
+ const free = waitI >= 0 ? waitI : f.players.findIndex((p) => p && p.pad < 0 && !p.quit);
274
+ if (free < 0) {
275
+ ctx.joinPlayer(f.gi);
393
276
  return;
394
277
  }
395
- const gp = pads[padWiz.gi];
396
- if (!gp)
397
- return; // controle desconectado (ou índice ainda não populado): congela até voltar
398
- if (padWiz.baseWait) {
399
- if (!gp.buttons.some((b) => b && b.pressed)) {
400
- padWiz.baseWait = false;
401
- padWiz.base = { b: gp.buttons.map((x) => !!(x && x.pressed)), a: gp.axes.slice() };
402
- padWiz.step = 0;
403
- wizPrompt();
404
- }
278
+ const seated = f.players[free];
279
+ seated.pad = f.gi;
280
+ if (seated.waiting) {
281
+ seated.waiting = false;
282
+ ctx.clearWaitingBadge(free);
283
+ }
284
+ ctx.srSay(t('sr.pad.assigned', { n: free + 1 }));
285
+ }
286
+ /**
287
+ * THE HALF OF THE SINGLE DOOR THAT PRESSES (ADR-0223): every position that WENT DOWN this frame goes to the virtual
288
+ * controller, stamped `gamepad` with this pad's seat. Returns the ones that reached the GAME — the controller's
289
+ * answer, which is what tells a game press from one a menu took.
290
+ *
291
+ * ⚠️ The EIGHT positions and not the six with an edge: those with an edge are a subset (`EDGE_BY_ACTION`), and up and
292
+ * down always travelled as held keys. All eight reach the cartridge.
293
+ */
294
+ function pressWhatWentDown(f) {
295
+ const reached = new Set();
296
+ for (const act of PAD_POSITIONS)
297
+ if (f.edge(act) && ctx.press(act, 'gamepad', f.owner))
298
+ reached.add(act);
299
+ return reached;
300
+ }
301
+ /**
302
+ * THE HALF THAT RELEASES, and it is UNCONDITIONAL on purpose (ADR-0223). A press is only born in the PLAY branch, but
303
+ * the finger leaves the button wherever it likes — a child who opens the pause with jump held would leave the key held
304
+ * forever if the release depended on the branch. The controller ignores the release of a press the game never heard,
305
+ * which is exactly the memory it exists to have.
306
+ *
307
+ * 📌 The pad is the only transport that reads STATE per frame instead of receiving events, so the release never
308
+ * arrives to it: it is computed against the previous frame.
309
+ */
310
+ function releaseWhatCameUp(f) {
311
+ if (f.owner < 0)
312
+ return; // a pad with no seat never pressed anything
313
+ for (const act of PAD_POSITIONS)
314
+ if (f.released(act))
315
+ ctx.release(act, 'gamepad', f.owner);
316
+ }
317
+ /** The real game: START pauses, the modal eats the d-pad, and the rest becomes an action flag. */
318
+ function playRound(f, p) {
319
+ // START IS PRESSED FOR THIS PAD'S SEAT, as SELECT is: the virtual controller hands it to the engine (`systemPress`), which
320
+ // answers it as that seat's START key — its own quick pause, and START again leaves it (ADR-0155, ADR-0144 §1 and its
321
+ // erratum of 2026-09-26). A seatless `pause()` here froze seat 0's quick pause whoever pressed.
322
+ if (f.pauseEdge) {
323
+ ctx.press('start', 'gamepad', f.owner);
324
+ ctx.setPauseActor(f.owner);
405
325
  return;
406
326
  }
407
- const base = padWiz.base;
408
- if (padWiz.release) {
409
- const idle = !gp.buttons.some((b, i) => b && b.pressed && !base.b[i]) && gp.axes.every((v, i) => Math.abs((v || 0) - base.a[i]) < 0.35);
410
- if (idle) {
411
- padWiz.release = false;
412
- wizPrompt();
413
- }
327
+ if (ctx.hasModal(f.owner)) { // the pad navigates the player's OWN modal (the others' game goes on)
328
+ // What left this module is the MEANING — the grid's ±1/±3 and the Braille detour, which are the game's decision.
329
+ const hit = MODAL_BY_POSITION.find(([position]) => f.edge(position));
330
+ if (hit)
331
+ ctx.modalInput(f.owner, hit[1]);
414
332
  return;
415
333
  }
416
- // eixo em rastreio (~240ms): classifica pelo COMPORTAMENTO — varia continuamente = analógico (limiar por
417
- // sinal); salta e FICA CONSTANTE = D-pad/POV hat (valor exato, ±0.13).
418
- if (padWiz.axTrack) {
419
- const t = padWiz.axTrack;
420
- const v = gp.axes[t.i] || 0;
421
- if (Math.abs(v - t.last) > 0.03)
422
- t.changes++;
423
- t.last = v;
424
- if (Math.abs(v - base.a[t.i]) > Math.abs(t.v - base.a[t.i]))
425
- t.v = v;
426
- if (++t.ticks >= 8) {
427
- const pv = t.v;
428
- padWiz.axTrack = null;
429
- wizBind(t.changes >= 2 ? { ax: t.i, s: pv > 0 ? 1 : -1 } : { av: t.i, v: Math.round(pv * 10000) / 10000 });
430
- }
334
+ // The table and the Easy guard come from input/edges.ts, the SAME ones keydown and touch use — three hand-written
335
+ // copies had already drifted once (the touch one forgot `!p.easy`).
336
+ // 🔴 THE SINGLE DOOR FIRST (ADR-0223): the position goes to the virtual controller, which holds the child's key
337
+ // stamped `gamepad` and delivers the command to the cartridge. Only then is an edge raised — and only for a press
338
+ // that REACHED the game, which is the controller's answer.
339
+ const reachedPlay = pressWhatWentDown(f);
340
+ let hasAnyEdge = false;
341
+ for (const [act, flag] of EDGE_BY_ACTION) {
342
+ if (!reachedPlay.has(act))
343
+ continue;
344
+ hasAnyEdge = true;
345
+ if (edgeAllowed(act, p.easy))
346
+ p[flag] = true;
347
+ }
348
+ // 📌 THE PAD'S EDGE, and it counts EVEN WHEN EASY FILTERS IT (ADR-0113 clause 4): the child pressed the button —
349
+ // that the Easy Mode rule does not raise the game's flag does not change the fact that this is the device in use.
350
+ // Reading the same condition as `p[flag]` would leave a child in Easy Mode with the keyboard's latch while playing
351
+ // on the pad.
352
+ // ⚠️ AND IT IS HERE, in the PLAY branch, not the menu ones: `onBar`, the title and the pause are navigation, and the
353
+ // question this feeds — which latch holds NOW — is about playing.
354
+ if (hasAnyEdge)
355
+ ctx.playerEdge(f.owner, 'gamepad');
356
+ }
357
+ /** With the world moving: the quick bar first, then the seat, the abandoned screen, and finally the game. */
358
+ function steerGame(f) {
359
+ // ===================== THE `accessibility` MODE (ADR-0044, item 7) =====================
360
+ // With the game RUNNING, this player's d-pad steers the QUICK BAR and not the character. It comes before everything
361
+ // that is the game's, because while the mode is on nothing else from this pad is the game's.
362
+ //
363
+ // Both exits arrive together: `especial` (action 3) is the project's BACK — the east button: B on Xbox, ◯ on
364
+ // PlayStation, A on Nintendo — and `pauseEdge` is the button that opens the pause, where this was entered. A child
365
+ // who is lost goes back the way they came, one who knows the game tries the usual back; both work.
366
+ // ⚠️ START ALONE, not `startEdge`: that one fires on action 2 too («close a dialog»), and passed here it made the confirm
367
+ // LEAVE the bar instead of pressing the icon under the cursor.
368
+ if (f.owner >= 0 && ctx.onBar(f.owner)) {
369
+ const k = f.navKeys();
370
+ if (f.pauseEdge || anyIntent(k))
371
+ ctx.navBar(f.owner, k, f.pauseEdge);
431
372
  return;
432
373
  }
433
- for (let i = 0; i < gp.buttons.length; i++) {
434
- if (gp.buttons[i] && gp.buttons[i].pressed && !base.b[i]) {
435
- wizBind({ b: i });
436
- return;
437
- }
374
+ if (f.owner < 0) {
375
+ takeSeat(f);
376
+ return;
438
377
  }
439
- for (let i = 0; i < gp.axes.length; i++) {
440
- const v = gp.axes[i] || 0;
441
- if (Math.abs(v - base.a[i]) > 0.45) {
442
- padWiz.axTrack = { i, v, last: v, changes: 0, ticks: 0 };
443
- return;
444
- }
378
+ const p = f.players[f.owner];
379
+ if (p.quit) {
380
+ if (f.startEdge)
381
+ ctx.respawnPlayer(f.owner);
382
+ return;
383
+ } // abandoned screen -> restart ONLY it
384
+ playRound(f, p);
385
+ }
386
+ /** In the demonstration, a pad button ends the demo — and the frame ends here, button or not. */
387
+ function attractTook(pads) {
388
+ if (!ctx.isAttractActive())
389
+ return false;
390
+ if (pads.some((gp) => gp && gp.buttons.some((b) => b && b.pressed)))
391
+ ctx.stopAttract();
392
+ return true;
393
+ }
394
+ /** Did a button go down on this frame — any position, START or SELECT? A button held is one press, not one a frame. */
395
+ const wentDown = (f) => f.startEdge || f.pauseEdge || f.selectEdge || PAD_POSITIONS.some((k) => f.edge(k));
396
+ /** A physical button used -> the on-screen pad goes away (the keyboard's rule): any of the nine positions read. */
397
+ function hideTouchPadIfHeld(f) {
398
+ if (ctx.isTouchMode() && (f.cur._start || PAD_POSITIONS.some((k) => f.cur[k])))
399
+ ctx.hideTouchControls();
400
+ }
401
+ /**
402
+ * Where this frame goes. The destinations ask for the facts, not a PHASE. The pause wins over a world running beneath
403
+ * it, and the title is derived by exclusion on purpose: in a scene this module does not know (a map, a results
404
+ * screen), the pad should navigate as on the title — the safe behaviour — instead of doing nothing.
405
+ */
406
+ function steerFrame(f) {
407
+ // SELECT OPENS THE MENUS wherever the keyboard's F does — in play and in the quick pause (ADR-0155) — and it is PRESSED,
408
+ // not decided here: the virtual controller hands the position to the engine, which answers it as F (ADR-0144 §1). An open
409
+ // card or panel refuses it there. A pad nobody has seated yet is seat 0's, as on the pause card below.
410
+ if (f.selectEdge) {
411
+ ctx.press('select', 'gamepad', f.owner < 0 ? 0 : f.owner);
412
+ return;
445
413
  }
414
+ if (ctx.pauseMenu())
415
+ steerPause(f);
416
+ else if (ctx.worldRunning())
417
+ steerGame(f);
418
+ else
419
+ steerTitle(f);
446
420
  }
447
- const cancelBtn = ctx.$('#padwiz-cancel');
448
- if (cancelBtn)
449
- cancelBtn.addEventListener('click', () => closePadWiz(false));
450
- // ----- poll (chamado a cada frame do loop) -----
451
421
  function pollPads() {
452
- if (padWiz)
453
- return; // durante o wizard, os pads falam só com ele
422
+ if (wizard.state())
423
+ return; // during the wizard, the pads talk only to it
454
424
  const pads = ctx.getGamepads();
455
- if (!pads)
456
- return;
457
- if (ctx.isAttractActive()) {
458
- for (const gp of pads) {
459
- if (gp && gp.buttons.some((b) => b && b.pressed)) {
460
- ctx.stopAttract();
461
- return;
462
- }
463
- } // botão de pad encerra a demo
425
+ if (!pads || attractTook(pads))
464
426
  return;
465
- }
466
427
  for (const gp of pads) {
467
428
  if (!gp)
468
429
  continue;
469
- const gi = gp.index;
470
- // controle fora do padrão (DirectInput) SEM mapa salvo apertou algo -> pausa geral + wizard direto
471
- if (gp.mapping !== 'standard' && !padMapFor(gp.id) && gp.buttons.some((b) => b && b.pressed)) {
472
- padWizAutoResume = ctx.mundoRodando();
473
- if (ctx.mundoRodando())
474
- ctx.pausar();
475
- openPadWizFor(gp);
430
+ // a non-standard (DirectInput) pad with NO saved map pressed something -> pause everything + the wizard directly
431
+ if (wizardTookOver(gp))
476
432
  return;
477
- }
478
- const prev = padPrevAct[gi] || {};
479
- // ⚠️ O ASSENTO SUBIU PARA AQUI, e a razão é o ADR-0115: a tabela de botões deste jogo é declarada POR
480
- // ASSENTO, e ela é lida dentro do `actionsFor`. Enquanto o `owner` só se resolvia lá em baixo, por ramo,
481
- // a leitura acontecia antes de se saber de quem era o controle — e um padrão por assento chegava tarde.
482
- // 📌 Os ramos abaixo passaram a usar esta constante em vez de recalcularem a mesma linha três vezes.
483
- const players = ctx.getPlayers();
484
- const owner = players.findIndex((p) => p.pad === gi);
485
- // ⚠️ E O CONTROLE AINDA NÃO ATRIBUÍDO (`owner < 0`) LÊ O ASSENTO 0, e não «nenhum»: ele está a produzir
486
- // arestas na tela do título, e um mapa vazio ali deixaria a criança sem como escolher o próprio jogo.
487
- // A EMPATIA MOTORA APLICADA AO CONTROLE (issue #120). Sem esta linha, uma criança com o modo de
488
- // um botão ligado e um pad na mão NÃO ESTAVA no modo — e nada em lado nenhum o dizia.
489
- const cur = umBotaoPorVez(prev, actionsFor(gp, tabelaDoPad(ctx.getNumPlayers(), owner < 0 ? 0 : owner)), estadoDoJogo.oneButton);
490
- if (ctx.isTouchMode() && (cur.left || cur.right || cur.up || cur.down || cur.action2 || cur.action1 || cur.action4 || cur.action3 || cur._start)) {
491
- ctx.hideTouchControls(); // botão físico usado -> some o gamepad virtual (mesma regra do teclado)
492
- }
493
- const startEdge = cur._start && !padPrevStart[gi];
494
- padPrevStart[gi] = cur._start;
495
- const pauseEdge = cur._pause && !prev._pause;
496
- const edge = (k) => cur[k] && !prev[k];
497
- padCur[gi] = cur;
498
- padPrevAct[gi] = cur;
499
- const winOv = ctx.$('#win-overlay');
500
- if (winOv && !winOv.hidden) { // Vitória: START/pulo fecham o modal (Jogar de novo)
501
- if (startEdge) {
502
- const b = ctx.$('#btn-again');
503
- if (b)
504
- b.click();
505
- }
506
- continue;
507
- }
508
- // Os três ramos abaixo pediam a FASE; hoje pedem os fatos. O `telaDeTitulo` é derivado por exclusão
509
- // de propósito: numa cena que este módulo não conheça (um mapa, uma tela de resultados), o controle
510
- // deve navegar como no título — que é o comportamento seguro — em vez de não fazer nada.
511
- const rodando = ctx.mundoRodando(), pausado = ctx.menuDePausa();
512
- if (!rodando && !pausado) {
513
- const k = { yes: edge('action2') || startEdge, no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
514
- const any = k.yes || k.no || k.up || k.down || k.left || k.right;
515
- if (ctx.getNumPlayers() > 1 && owner > 0) {
516
- if (any)
517
- ctx.srSay(t('sr.title.waitP1'));
518
- continue;
519
- } // só o J1 escolhe
520
- if (any)
521
- ctx.navTitle(k); // menu inicial navegável pelo pad
522
- continue;
523
- }
524
- if (pausado) {
525
- const pi = owner < 0 ? 0 : owner;
526
- if (pauseEdge) {
527
- ctx.retomar();
528
- continue;
529
- } // START retoma
530
- const k = { yes: edge('action2'), no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
531
- if (k.yes || k.no || k.up || k.down || k.left || k.right) {
532
- const dlg = ctx.sharedDialogOpen();
533
- if (dlg)
534
- ctx.navDialog(dlg, k);
535
- else {
536
- const menu = ctx.getPauseMenu(pi);
537
- if (menu && !menu.hidden)
538
- ctx.navPause(menu, pi, k);
539
- }
540
- }
541
- continue;
542
- }
543
- if (rodando) {
544
- // ===================== O MODO `accessibility` (ADR-0044, item 7) =====================
545
- // Com o jogo ANDANDO, o direcional deste jogador dirige a BARRA RÁPIDA e não o personagem. Vem antes
546
- // de tudo o que é de jogo, porque enquanto o modo está ligado nada mais deste controle é de jogo.
547
- //
548
- // As DUAS saídas chegam juntas: `especial` é o VOLTAR do projeto (X no PlayStation, B no Xbox, A no
549
- // Nintendo) e `startEdge` é o botão que abre a pausa — de onde se entrou aqui. Quem se perde tenta
550
- // voltar por onde veio, e quem já conhece o jogo tenta o voltar de sempre; as duas dão certo.
551
- if (owner >= 0 && ctx.naBarraDe(owner)) {
552
- const k = { yes: edge('action2'), no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
553
- if (startEdge || k.yes || k.no || k.up || k.down || k.left || k.right)
554
- ctx.navBar(owner, k, !!startEdge);
555
- continue;
556
- }
557
- if (owner < 0) { // atribuição POR ORDEM DE AÇÃO: qualquer botão associa -> 1º controle a agir -> 1º jogador sem pad
558
- const anyEdge = edge('action2') || edge('action1') || edge('action4') || edge('action3') || startEdge || edge('left') || edge('right') || edge('up') || edge('down');
559
- if (anyEdge) {
560
- const waitI = players.findIndex((p) => p && p.waiting);
561
- const free = waitI >= 0 ? waitI : players.findIndex((p) => p && p.pad < 0 && !p.quit);
562
- if (free >= 0) {
563
- players[free].pad = gi;
564
- if (players[free].waiting) {
565
- players[free].waiting = false;
566
- ctx.clearWaitingBadge(free);
567
- }
568
- ctx.srSay(t('sr.pad.assigned', { n: free + 1 }));
569
- }
570
- else {
571
- ctx.joinPlayer(gi);
572
- }
573
- }
574
- }
575
- else if (players[owner].quit) {
576
- if (startEdge)
577
- ctx.respawnPlayer(owner); // tela abandonada -> recomeça SÓ ela
578
- }
579
- else {
580
- const p = players[owner];
581
- if (pauseEdge) {
582
- ctx.pausar();
583
- ctx.setPauseActor(owner);
584
- continue;
585
- } // START pausa (todos pausam; cada tela navega a sua)
586
- if (ctx.hasModal(owner)) { // o pad navega o modal do PRÓPRIO jogador (o jogo dos outros segue)
587
- // A ORDEM é a do original: esquerda, direita, cima, baixo, confirmar, apagar. O que saiu foi o
588
- // SIGNIFICADO — o ±1/±3 da grade e o desvio de Braille, que agora são decisão do jogo.
589
- const intent = edge('left') ? 'left' : edge('right') ? 'right'
590
- : edge('up') ? 'up' : edge('down') ? 'down'
591
- : edge('action2') ? 'confirm' : edge('action3') ? 'erase' : null;
592
- if (intent)
593
- ctx.modalInput(owner, intent);
594
- continue;
595
- }
596
- // A tabela e a guarda do Fácil vêm de input/edges.ts, as MESMAS que keydown e touch usam. Antes eram
597
- // seis `if` à mão aqui, seis lá e seis no toque — e o do toque tinha esquecido o `!p.easy`.
598
- let algumaAresta = false;
599
- for (const [act, flag] of EDGE_BY_ACTION) {
600
- if (edge(act))
601
- algumaAresta = true;
602
- if (edgeAllowed(act, p.easy) && edge(act))
603
- p[flag] = true;
604
- }
605
- // 📌 A ARESTA DO CONTROLE, e ela conta MESMO QUANDO O FÁCIL A FILTRA (ADR-0113 cláusula 4): a
606
- // criança carregou no botão — que a regra do Modo Fácil não levante a bandeira do jogo não muda
607
- // o facto de o aparelho em uso ser este. Ler a mesma condição do `p[flag]` faria uma criança em
608
- // Modo Fácil ficar com a alternância do teclado enquanto joga no controle.
609
- // ⚠️ E É AQUI, no ramo de JOGO, e não nos de menu: `naBarraDe`, o título e a pausa são navegação, e
610
- // a pergunta que isto alimenta — que alternância vale AGORA — é sobre jogar.
611
- if (algumaAresta)
612
- ctx.arestaDoJogador(owner, 'gamepad');
613
- }
614
- }
433
+ // ⚠️ `getPlayers()` IS READ HERE, once per pad, and not once before the loop: the seat branch WRITES into the list
434
+ // (`players[free].pad = gi`), and reading it only once would change what the next pad sees.
435
+ const f = readPad(gp, ctx.getPlayers());
436
+ releaseWhatCameUp(f); // before every branch: the finger leaves the button wherever it likes (ADR-0223)
437
+ hideTouchPadIfHeld(f);
438
+ if (wentDown(f) && ctx.takeShown?.())
439
+ continue; // one button only: the button is the switch (ADR-0218 §4)
440
+ if (!winOverlayTook(f))
441
+ steerFrame(f);
615
442
  }
616
443
  }
617
- return { pollPads, openPadWiz, openPadWizFor, closePadWiz, padWizTick, padMapFor, getPadWiz: () => padWiz };
444
+ return { pollPads, openPadWiz, openPadWizFor, closePadWiz, padWizTick, padMapFor, getPadWiz: () => wizard.state() };
618
445
  }