@the-inclusionist/engine 6.36.1

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 (267) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +107 -0
  3. package/app/css/style.css +622 -0
  4. package/app/public/vendor/fonts/andika-400-ext.woff2 +0 -0
  5. package/app/public/vendor/fonts/andika-400.woff2 +0 -0
  6. package/app/public/vendor/fonts/andika-700-ext.woff2 +0 -0
  7. package/app/public/vendor/fonts/andika-700.woff2 +0 -0
  8. package/app/public/vendor/fonts/atkinson-400-ext.woff2 +0 -0
  9. package/app/public/vendor/fonts/atkinson-400.woff2 +0 -0
  10. package/app/public/vendor/fonts/atkinson-700-ext.woff2 +0 -0
  11. package/app/public/vendor/fonts/atkinson-700.woff2 +0 -0
  12. package/app/public/vendor/fonts/atkinson-mono-400-ext.woff2 +0 -0
  13. package/app/public/vendor/fonts/atkinson-mono-400.woff2 +0 -0
  14. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  15. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  16. package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
  17. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  18. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  19. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  20. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  21. package/app/public/vendor/fonts/lexend-400-ext.woff2 +0 -0
  22. package/app/public/vendor/fonts/lexend-400.woff2 +0 -0
  23. package/app/public/vendor/fonts/lexend-700-ext.woff2 +0 -0
  24. package/app/public/vendor/fonts/lexend-700.woff2 +0 -0
  25. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  26. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  27. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  28. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  29. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  30. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  31. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  32. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  33. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  34. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  35. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  36. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  37. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  38. package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
  39. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  40. package/app/public/vendor/fonts.css +88 -0
  41. package/dist-pkg/boot/create-game.d.ts +159 -0
  42. package/dist-pkg/boot/create-game.js +266 -0
  43. package/dist-pkg/core/a11y-sr.d.ts +4 -0
  44. package/dist-pkg/core/a11y-sr.js +20 -0
  45. package/dist-pkg/core/actions.d.ts +100 -0
  46. package/dist-pkg/core/actions.js +149 -0
  47. package/dist-pkg/core/anel.d.ts +17 -0
  48. package/dist-pkg/core/anel.js +31 -0
  49. package/dist-pkg/core/collision.d.ts +21 -0
  50. package/dist-pkg/core/collision.js +62 -0
  51. package/dist-pkg/core/constants.d.ts +84 -0
  52. package/dist-pkg/core/constants.js +64 -0
  53. package/dist-pkg/core/contract.d.ts +227 -0
  54. package/dist-pkg/core/contract.js +184 -0
  55. package/dist-pkg/core/dom-query.d.ts +9 -0
  56. package/dist-pkg/core/dom-query.js +25 -0
  57. package/dist-pkg/core/entity.d.ts +150 -0
  58. package/dist-pkg/core/entity.js +38 -0
  59. package/dist-pkg/core/escape-html.d.ts +16 -0
  60. package/dist-pkg/core/escape-html.js +32 -0
  61. package/dist-pkg/core/i18n.d.ts +71 -0
  62. package/dist-pkg/core/i18n.js +236 -0
  63. package/dist-pkg/core/layers.d.ts +39 -0
  64. package/dist-pkg/core/layers.js +71 -0
  65. package/dist-pkg/core/letter-grid.d.ts +55 -0
  66. package/dist-pkg/core/letter-grid.js +116 -0
  67. package/dist-pkg/core/loop.d.ts +17 -0
  68. package/dist-pkg/core/loop.js +36 -0
  69. package/dist-pkg/core/password.d.ts +34 -0
  70. package/dist-pkg/core/password.js +205 -0
  71. package/dist-pkg/core/rng.d.ts +20 -0
  72. package/dist-pkg/core/rng.js +61 -0
  73. package/dist-pkg/core/rotulo-acessivel.d.ts +17 -0
  74. package/dist-pkg/core/rotulo-acessivel.js +41 -0
  75. package/dist-pkg/core/run-state.d.ts +115 -0
  76. package/dist-pkg/core/run-state.js +40 -0
  77. package/dist-pkg/core/scenes.d.ts +68 -0
  78. package/dist-pkg/core/scenes.js +64 -0
  79. package/dist-pkg/core/screens.d.ts +16 -0
  80. package/dist-pkg/core/screens.js +28 -0
  81. package/dist-pkg/core/state.d.ts +138 -0
  82. package/dist-pkg/core/state.js +290 -0
  83. package/dist-pkg/core/tiles.d.ts +12 -0
  84. package/dist-pkg/core/tiles.js +61 -0
  85. package/dist-pkg/core/world.d.ts +4 -0
  86. package/dist-pkg/core/world.js +58 -0
  87. package/dist-pkg/educational/activities-registry.d.ts +47 -0
  88. package/dist-pkg/educational/activities-registry.js +95 -0
  89. package/dist-pkg/i18n/en.d.ts +3 -0
  90. package/dist-pkg/i18n/en.js +560 -0
  91. package/dist-pkg/i18n/es.d.ts +3 -0
  92. package/dist-pkg/i18n/es.js +558 -0
  93. package/dist-pkg/i18n/pt.d.ts +3 -0
  94. package/dist-pkg/i18n/pt.js +618 -0
  95. package/dist-pkg/input/default-bindings.d.ts +24 -0
  96. package/dist-pkg/input/default-bindings.js +122 -0
  97. package/dist-pkg/input/devices.d.ts +15 -0
  98. package/dist-pkg/input/devices.js +31 -0
  99. package/dist-pkg/input/edges.d.ts +58 -0
  100. package/dist-pkg/input/edges.js +49 -0
  101. package/dist-pkg/input/gamepad.d.ts +203 -0
  102. package/dist-pkg/input/gamepad.js +531 -0
  103. package/dist-pkg/input/keyboard-runtime.d.ts +61 -0
  104. package/dist-pkg/input/keyboard-runtime.js +83 -0
  105. package/dist-pkg/input/keyboard.d.ts +45 -0
  106. package/dist-pkg/input/keyboard.js +80 -0
  107. package/dist-pkg/input/keydown.d.ts +270 -0
  108. package/dist-pkg/input/keydown.js +381 -0
  109. package/dist-pkg/input/latch.d.ts +30 -0
  110. package/dist-pkg/input/latch.js +63 -0
  111. package/dist-pkg/input/pointer-space.d.ts +40 -0
  112. package/dist-pkg/input/pointer-space.js +51 -0
  113. package/dist-pkg/input/state.d.ts +11 -0
  114. package/dist-pkg/input/state.js +12 -0
  115. package/dist-pkg/input/touch-bindings.d.ts +210 -0
  116. package/dist-pkg/input/touch-bindings.js +365 -0
  117. package/dist-pkg/input/touch.d.ts +173 -0
  118. package/dist-pkg/input/touch.js +314 -0
  119. package/dist-pkg/input/transports.d.ts +90 -0
  120. package/dist-pkg/input/transports.js +96 -0
  121. package/dist-pkg/input/vocabulary-migration.d.ts +58 -0
  122. package/dist-pkg/input/vocabulary-migration.js +125 -0
  123. package/dist-pkg/platform/audio-ambient.d.ts +27 -0
  124. package/dist-pkg/platform/audio-ambient.js +89 -0
  125. package/dist-pkg/platform/audio-earcons.d.ts +24 -0
  126. package/dist-pkg/platform/audio-earcons.js +62 -0
  127. package/dist-pkg/platform/audio-jingles.d.ts +17 -0
  128. package/dist-pkg/platform/audio-jingles.js +54 -0
  129. package/dist-pkg/platform/audio-mixer.d.ts +14 -0
  130. package/dist-pkg/platform/audio-mixer.js +62 -0
  131. package/dist-pkg/platform/audio-nav.d.ts +51 -0
  132. package/dist-pkg/platform/audio-nav.js +99 -0
  133. package/dist-pkg/platform/audio-sonar.d.ts +71 -0
  134. package/dist-pkg/platform/audio-sonar.js +173 -0
  135. package/dist-pkg/platform/audio.d.ts +32 -0
  136. package/dist-pkg/platform/audio.js +191 -0
  137. package/dist-pkg/platform/interruptible-speech.d.ts +19 -0
  138. package/dist-pkg/platform/interruptible-speech.js +79 -0
  139. package/dist-pkg/platform/speech.d.ts +2 -0
  140. package/dist-pkg/platform/speech.js +35 -0
  141. package/dist-pkg/platform/storage.d.ts +87 -0
  142. package/dist-pkg/platform/storage.js +148 -0
  143. package/dist-pkg/platform/tts.d.ts +33 -0
  144. package/dist-pkg/platform/tts.js +142 -0
  145. package/dist-pkg/render/camera.d.ts +58 -0
  146. package/dist-pkg/render/camera.js +105 -0
  147. package/dist-pkg/render/canvas.d.ts +13 -0
  148. package/dist-pkg/render/canvas.js +26 -0
  149. package/dist-pkg/render/cenario-data.d.ts +127 -0
  150. package/dist-pkg/render/cenario-data.js +145 -0
  151. package/dist-pkg/render/city-tex.d.ts +63 -0
  152. package/dist-pkg/render/city-tex.js +228 -0
  153. package/dist-pkg/render/city-tiles.d.ts +11 -0
  154. package/dist-pkg/render/city-tiles.js +140 -0
  155. package/dist-pkg/render/crt.d.ts +19 -0
  156. package/dist-pkg/render/crt.js +92 -0
  157. package/dist-pkg/render/cvd-matrices.d.ts +35 -0
  158. package/dist-pkg/render/cvd-matrices.js +91 -0
  159. package/dist-pkg/render/draw.d.ts +149 -0
  160. package/dist-pkg/render/draw.js +221 -0
  161. package/dist-pkg/render/fx.d.ts +63 -0
  162. package/dist-pkg/render/fx.js +123 -0
  163. package/dist-pkg/render/hc-role-data.d.ts +14 -0
  164. package/dist-pkg/render/hc-role-data.js +24 -0
  165. package/dist-pkg/render/high-contrast.d.ts +109 -0
  166. package/dist-pkg/render/high-contrast.js +254 -0
  167. package/dist-pkg/render/lq-filter.d.ts +37 -0
  168. package/dist-pkg/render/lq-filter.js +94 -0
  169. package/dist-pkg/render/minimap.d.ts +11 -0
  170. package/dist-pkg/render/minimap.js +94 -0
  171. package/dist-pkg/render/parallax.d.ts +85 -0
  172. package/dist-pkg/render/parallax.js +158 -0
  173. package/dist-pkg/render/player-anim.d.ts +71 -0
  174. package/dist-pkg/render/player-anim.js +152 -0
  175. package/dist-pkg/render/port.d.ts +143 -0
  176. package/dist-pkg/render/port.js +29 -0
  177. package/dist-pkg/render/recycling-tex.d.ts +48 -0
  178. package/dist-pkg/render/recycling-tex.js +164 -0
  179. package/dist-pkg/render/scene-city.d.ts +77 -0
  180. package/dist-pkg/render/scene-city.js +181 -0
  181. package/dist-pkg/render/scene-parallax.d.ts +72 -0
  182. package/dist-pkg/render/scene-parallax.js +426 -0
  183. package/dist-pkg/render/scene-sky.d.ts +126 -0
  184. package/dist-pkg/render/scene-sky.js +295 -0
  185. package/dist-pkg/render/screen-pipeline.d.ts +134 -0
  186. package/dist-pkg/render/screen-pipeline.js +177 -0
  187. package/dist-pkg/render/set-cenario.d.ts +54 -0
  188. package/dist-pkg/render/set-cenario.js +103 -0
  189. package/dist-pkg/render/sprite-fx.d.ts +3 -0
  190. package/dist-pkg/render/sprite-fx.js +40 -0
  191. package/dist-pkg/render/textures.d.ts +75 -0
  192. package/dist-pkg/render/textures.js +240 -0
  193. package/dist-pkg/render/title-scene.d.ts +46 -0
  194. package/dist-pkg/render/title-scene.js +75 -0
  195. package/dist-pkg/render/viewports.d.ts +52 -0
  196. package/dist-pkg/render/viewports.js +185 -0
  197. package/dist-pkg/render/viz-axes.d.ts +98 -0
  198. package/dist-pkg/render/viz-axes.js +184 -0
  199. package/dist-pkg/render/viz-modes.d.ts +61 -0
  200. package/dist-pkg/render/viz-modes.js +74 -0
  201. package/dist-pkg/render/viz-setters.d.ts +130 -0
  202. package/dist-pkg/render/viz-setters.js +244 -0
  203. package/dist-pkg/render/weather.d.ts +96 -0
  204. package/dist-pkg/render/weather.js +171 -0
  205. package/dist-pkg/render/wheelchair-sprites.d.ts +24 -0
  206. package/dist-pkg/render/wheelchair-sprites.js +63 -0
  207. package/dist-pkg/render/world-tex.d.ts +18 -0
  208. package/dist-pkg/render/world-tex.js +87 -0
  209. package/dist-pkg/ui/activities-menu.d.ts +258 -0
  210. package/dist-pkg/ui/activities-menu.js +648 -0
  211. package/dist-pkg/ui/caa-sets.d.ts +39 -0
  212. package/dist-pkg/ui/caa-sets.js +65 -0
  213. package/dist-pkg/ui/changed-mark.d.ts +15 -0
  214. package/dist-pkg/ui/changed-mark.js +96 -0
  215. package/dist-pkg/ui/debug-panel.d.ts +67 -0
  216. package/dist-pkg/ui/debug-panel.js +173 -0
  217. package/dist-pkg/ui/dom.d.ts +24 -0
  218. package/dist-pkg/ui/dom.js +47 -0
  219. package/dist-pkg/ui/focus-trap.d.ts +71 -0
  220. package/dist-pkg/ui/focus-trap.js +99 -0
  221. package/dist-pkg/ui/fonts.d.ts +41 -0
  222. package/dist-pkg/ui/fonts.js +59 -0
  223. package/dist-pkg/ui/hud.d.ts +137 -0
  224. package/dist-pkg/ui/hud.js +229 -0
  225. package/dist-pkg/ui/item-announcement.d.ts +19 -0
  226. package/dist-pkg/ui/item-announcement.js +36 -0
  227. package/dist-pkg/ui/layout.d.ts +6 -0
  228. package/dist-pkg/ui/layout.js +54 -0
  229. package/dist-pkg/ui/loop-crash.d.ts +19 -0
  230. package/dist-pkg/ui/loop-crash.js +60 -0
  231. package/dist-pkg/ui/map-hub.d.ts +56 -0
  232. package/dist-pkg/ui/map-hub.js +138 -0
  233. package/dist-pkg/ui/menu-nav.d.ts +141 -0
  234. package/dist-pkg/ui/menu-nav.js +390 -0
  235. package/dist-pkg/ui/pause-icons.d.ts +327 -0
  236. package/dist-pkg/ui/pause-icons.js +620 -0
  237. package/dist-pkg/ui/reach-notice.d.ts +34 -0
  238. package/dist-pkg/ui/reach-notice.js +71 -0
  239. package/dist-pkg/ui/settings-audio.d.ts +139 -0
  240. package/dist-pkg/ui/settings-audio.js +544 -0
  241. package/dist-pkg/ui/settings-caa.d.ts +45 -0
  242. package/dist-pkg/ui/settings-caa.js +137 -0
  243. package/dist-pkg/ui/settings-controls.d.ts +100 -0
  244. package/dist-pkg/ui/settings-controls.js +152 -0
  245. package/dist-pkg/ui/settings-empathy.d.ts +62 -0
  246. package/dist-pkg/ui/settings-empathy.js +137 -0
  247. package/dist-pkg/ui/settings-motion.d.ts +95 -0
  248. package/dist-pkg/ui/settings-motion.js +282 -0
  249. package/dist-pkg/ui/settings-motor.d.ts +77 -0
  250. package/dist-pkg/ui/settings-motor.js +215 -0
  251. package/dist-pkg/ui/settings-panel.d.ts +76 -0
  252. package/dist-pkg/ui/settings-panel.js +187 -0
  253. package/dist-pkg/ui/settings-typo.d.ts +74 -0
  254. package/dist-pkg/ui/settings-typo.js +150 -0
  255. package/dist-pkg/ui/settings-visual.d.ts +111 -0
  256. package/dist-pkg/ui/settings-visual.js +252 -0
  257. package/dist-pkg/ui/shell.d.ts +239 -0
  258. package/dist-pkg/ui/shell.js +430 -0
  259. package/dist-pkg/ui/title.d.ts +36 -0
  260. package/dist-pkg/ui/title.js +60 -0
  261. package/dist-pkg/ui/vlibras.d.ts +11 -0
  262. package/dist-pkg/ui/vlibras.js +87 -0
  263. package/dist-pkg/ui/webcam.d.ts +7 -0
  264. package/dist-pkg/ui/webcam.js +88 -0
  265. package/docs/CREDITS.md +64 -0
  266. package/docs/LICENSES.md +132 -0
  267. package/package.json +115 -0
@@ -0,0 +1,531 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/gamepad.ts — gamepad READING (stdDirs/padActions/bindActive/padMapFor) + the poll dispatcher
3
+ // (pollPads) + the accessibility MAPPING WIZARD (openPadWiz/openPadWizFor/closePadWiz/padWizTick). Pure
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.
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';
10
+ 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 { padCur, padPrevAct, padPrevStart, PAD_DEAD } from './state.js';
14
+ import * as store from '../platform/storage.js';
15
+ function bindingAt(map, key) {
16
+ const v = map[key];
17
+ return typeof v === 'object' && v !== null ? v : undefined;
18
+ }
19
+ // [axisValue, up, down, left, right] — os 8 passos de um D-pad "POV hat" (eixo alto do DirectInput), repouso ~1.286.
20
+ const HAT_STEPS = [
21
+ [-1, 1, 0, 0, 0], [-0.7143, 1, 0, 0, 1], [-0.4286, 0, 0, 0, 1], [-0.1429, 0, 1, 0, 1],
22
+ [0.1429, 0, 1, 0, 0], [0.4286, 0, 1, 1, 0], [0.7143, 0, 0, 1, 0], [1, 1, 0, 1, 0],
23
+ ];
24
+ /** 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
25
+ * DirectInput). Controles com os dois direcionais mapeados ficam com ambos vivos (dedo no stick não mata o D-pad). */
26
+ export function stdDirs(gp) {
27
+ const b = (i) => !!(gp.buttons[i] && gp.buttons[i].pressed);
28
+ const ax = (i) => gp.axes[i] || 0;
29
+ 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) };
30
+ for (let i = 6; i < gp.axes.length; i++) {
31
+ const v = gp.axes[i];
32
+ if (typeof v !== 'number' || Math.abs(v) > 1.001)
33
+ continue; // fora do repouso do hat
34
+ for (const [hv, u, dn, l, r] of HAT_STEPS) {
35
+ if (Math.abs(v - hv) <= 0.09) {
36
+ if (u)
37
+ d.up = true;
38
+ if (dn)
39
+ d.down = true;
40
+ if (l)
41
+ d.left = true;
42
+ if (r)
43
+ d.right = true;
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ return d;
49
+ }
50
+ /** Está o binding `bd` ativo agora neste gamepad? Digital = pressed; analógico ({ax,s}) = limiar por sinal
51
+ * (metade do curso); hat ({av,v}) = valor exato do passo (±0.13 — os 8 passos distam ~0.286). */
52
+ export function bindActive(gp, bd) {
53
+ if (!bd)
54
+ return false;
55
+ if (bd.b != null)
56
+ return !!(gp.buttons[bd.b] && gp.buttons[bd.b].pressed);
57
+ if (bd.ax != null)
58
+ return ((gp.axes[bd.ax] || 0) * (bd.s ?? 0)) > 0.5;
59
+ if (bd.av != null)
60
+ return Math.abs((gp.axes[bd.av] || 0) - (bd.v ?? 0)) <= 0.13;
61
+ return false;
62
+ }
63
+ /** Ações do frame para este gamepad. `custom` = mapa salvo pelo wizard para este `gp.id` (null/`_skip` = usa o
64
+ * mapa PADRÃO da Gamepad API "standard": 0=pulo · 1=especial · 2/5/7=correr · 3=troca · 9=START). Direções
65
+ * custom caem de volta em stdDirs quando o binding do usuário não está ativo (D-pad/stick continuam vivos). */
66
+ export function padActions(gp, custom) {
67
+ if (custom && !custom._skip) {
68
+ const A = (k) => bindActive(gp, bindingAt(custom, k));
69
+ const sd = stdDirs(gp);
70
+ return {
71
+ left: A('left') || sd.left, right: A('right') || sd.right, up: A('up') || sd.up, down: A('down') || sd.down,
72
+ action2: A('action2'), action1: A('action1'), action4: A('action4'), action3: A('action3'), _start: A('action2') || A('start'), _pause: A('start'),
73
+ };
74
+ }
75
+ const b = (i) => !!(gp.buttons[i] && gp.buttons[i].pressed);
76
+ const sd = stdDirs(gp);
77
+ // ⚠️ OS ÍNDICES SAEM DA TABELA DECLARADA, e não de literais aqui. Enquanto eram literais, esta linha e
78
+ // `input/default-bindings` DISCORDAVAM e nada notava — a mesma forma de defeito que o gate do toque
79
+ // apanhou: duas tabelas que concordam entre si não provam nada sobre um terceiro que as lê.
80
+ //
81
+ // ⚠️ E A DISCORDÂNCIA ERA REAL: aqui estava `action1: b(2) || b(5) || b(7)`, ou seja X, R1 e R2 todos a
82
+ // correr, enquanto a tabela declara R1 como `rightShoulder` e R2 como `rightTrigger`. O ADR-0086 registrou
83
+ // esta mudança como o asterisco do seu «zero movimento»: nenhum VERBO muda de botão, mas `run` perde dois
84
+ // dos seus três. Quem usava R1 para correr sente — e é o preço de os quatro ombros existirem.
85
+ const B = GAMEPAD_STANDARD;
86
+ const at = (a) => { const i = B[a]; return typeof i === 'number' ? b(i) : false; };
87
+ return {
88
+ left: sd.left, right: sd.right, up: sd.up, down: sd.down,
89
+ action1: at('action1'), action2: at('action2'), action3: at('action3'), action4: at('action4'),
90
+ leftShoulder: at('leftShoulder'), leftTrigger: at('leftTrigger'),
91
+ rightShoulder: at('rightShoulder'), rightTrigger: at('rightTrigger'),
92
+ // ⚠️ `start` COMO POSIÇÃO, e não só como os derivados abaixo. Faltava, e o gate da tabela foi quem
93
+ // o encontrou: quem quisesse saber «o START está apertado?» tinha de ler `_start`, que começa por
94
+ // underscore e quer dizer outra coisa (fecha diálogo, e aceita a ação 2 também).
95
+ start: at('start'), select: at('select'),
96
+ // `_start` e `_pause` são DERIVADOS e não posições: «fecha diálogo» aceita a ação 2 ou o START, «pausa»
97
+ // só o START. Ficam escritos aqui porque descrevem o que a raiz faz com duas posições, não uma terceira.
98
+ _start: at('action2') || at('start'), _pause: at('start'),
99
+ };
100
+ }
101
+ // ---------------------------------------------------------------------------------------------
102
+ // Wizard data (a ORDEM dos passos + a animação de demonstração; as PALAVRAS vêm do jogo)
103
+ // ---------------------------------------------------------------------------------------------
104
+ /**
105
+ * A ORDEM em que o assistente pergunta. SÓ a ordem.
106
+ *
107
+ * ⚠️ ELA CARREGAVA AS PALAVRAS, EM PORTUGUÊS CRU, DENTRO DA ENGINE: `['action2', 'PULAR']`,
108
+ * `['action1', 'CORRER / INTERAGIR']`. Era o defeito do ADR-0074 na sua forma mais visível — não só
109
+ * vocabulário de plataforma dentro do motor, mas vocabulário de plataforma NUM IDIOMA SÓ, à frente de uma
110
+ * criança, num ficheiro que o pilar 3 obriga a ser localizável.
111
+ *
112
+ * A palavra vem agora de `ctx.rotuloDaAcao`, que o jogo fornece pelo seu preset. Aqui fica o que é mesmo da
113
+ * engine: a sequência em que se pergunta — direções primeiro, ação depois, sistema no fim —, que é uma
114
+ * decisão de ergonomia do assistente e não do jogo.
115
+ *
116
+ * ⚠️ E UMA AÇÃO QUE O JOGO NÃO NOMEIA NÃO É PERGUNTADA. `wizPrompt` salta-a, porque um jogo que não usa a
117
+ * posição não tem o que mapear nela — e perguntar produziria um passo mudo, ou pior, um passo a dizer
118
+ * `action7` em voz alta.
119
+ */
120
+ export const PADWIZ_ORDER = [
121
+ // Direções primeiro: são o que a criança encontra sem pensar, e acertar as quatro dá confiança para as
122
+ // outras dez.
123
+ 'up', 'down', 'left', 'right',
124
+ // O losango, na ordem em que o dedo o percorre neste projeto (ADR-0086 §2).
125
+ 'action2', 'action1', 'action4', 'action3',
126
+ // ⚠️ OS QUATRO OMBROS FALTAVAM AQUI ATÉ 2026-09-06, e a falta era um buraco de acessibilidade e não uma
127
+ // omissão cosmética: o assistente existe PARA controles que não são «standard» — genéricos, adaptados,
128
+ // de uma mão —, e um jogo que declarasse `leftShoulder` não tinha por onde a criança o mapear. Cinco das
129
+ // quatorze posições eram inalcançáveis exatamente para quem mais precisa do assistente.
130
+ 'leftShoulder', 'leftTrigger', 'rightShoulder', 'rightTrigger',
131
+ // Sistema por último: `start` e `select` costumam ser os botões mais pequenos e mais escondidos, e pedi-los
132
+ // no fim deixa a criança já habituada ao ritmo do assistente quando chega neles.
133
+ 'start', 'select',
134
+ ];
135
+ /** Demonstração animada de cada ação (frames reais do jogo) mostrada durante o passo correspondente do wizard. */
136
+ export const PADWIZ_ANIM = {
137
+ up: { seq: ['escada/0', 'escada/1'], hold: 9, cls: 'pw-up' },
138
+ down: { seq: ['escada/1', 'escada/0'], hold: 9, cls: 'pw-down' },
139
+ 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 },
140
+ right: { seq: ['andar/0', 'andar/1', 'andar/2', 'andar/3', 'andar/4', 'andar/5', 'andar/6', 'andar/7'], hold: 4, cls: 'pw-right' },
141
+ action2: { seq: ['pulo/0', 'pulo/0', 'pulo/1', 'pulo/1'], hold: 7, cls: 'pw-jump' },
142
+ action1: { seq: ['correr/0', 'correr/1', 'correr/2', 'correr/3'], hold: 3, cls: 'pw-run' },
143
+ action4: { fx: '👟 🕷️ 🎈 🐇 🦘', cls: 'pw-swap', noimg: 1 },
144
+ action3: { seq: ['idle/0', 'idle/1', 'idle/2', 'idle/3'], hold: 8, fx: '✨', cls: 'pw-especial' },
145
+ start: { fx: 'PAUSA', cls: 'pw-start', noimg: 1 },
146
+ };
147
+ export function initGamepad(ctx) {
148
+ const _padMaps = new Map(); // cache id -> mapa custom (evita reparsear JSON a cada frame)
149
+ let padWiz = null;
150
+ let padWizAutoResume = false; // wizard aberto automaticamente no meio do jogo -> retoma a fase ao fechar
151
+ let padWizAnim = null;
152
+ function padMapFor(id) {
153
+ // ⚠️ O MAPA SALVO PASSA PELO TRADUTOR NA LEITURA. Ele é indexado por AÇÃO — `{ jump: { b: 0 } }` — e um
154
+ // mapa gravado antes do ADR-0086 tem as chaves antigas, que `bindingAt` já não procura: o controle
155
+ // custom simplesmente pararia de responder, e o jogo cairia no mapa padrão sem dizer nada.
156
+ // É o mais caro dos três formatos a perder: um mapa destes existe porque alguém passou por um assistente
157
+ // de nove passos, botão a botão, quase sempre porque o controle NÃO é «standard» — e controles genéricos
158
+ // ou adaptados raramente são. Ver `input/vocabulary-migration.ts`.
159
+ if (!_padMaps.has(id)) {
160
+ _padMaps.set(id, migrarMapaDeControle(store.getJSON('incl_padmap_' + id, null)));
161
+ }
162
+ return _padMaps.get(id) ?? null;
163
+ }
164
+ function actionsFor(gp) { return padActions(gp, padMapFor(gp.id)); }
165
+ // ----- wizard: anúncio + demo animada (DOM-facing, thin) -----
166
+ function wizSay(t) {
167
+ const el = ctx.$('#padwiz-prompt');
168
+ if (el)
169
+ el.textContent = t;
170
+ ctx.srSay(t);
171
+ }
172
+ function wizDemo(k) {
173
+ const d = ctx.$('#padwiz-demo');
174
+ const img = ctx.$('#padwiz-demo-img');
175
+ const fx = ctx.$('#padwiz-demo-fx');
176
+ if (!d)
177
+ return;
178
+ const a = k ? PADWIZ_ANIM[k] : null;
179
+ d.className = a ? a.cls : '';
180
+ padWizAnim = null;
181
+ if (fx)
182
+ fx.textContent = (a && a.fx) || '';
183
+ if (img) {
184
+ img.style.display = a && a.noimg ? 'none' : '';
185
+ img.style.transform = a && a.flip ? 'scaleX(-1)' : '';
186
+ if (a && a.seq) {
187
+ img.src = ctx.spriteBase + a.seq[0] + '.png';
188
+ padWizAnim = { seq: a.seq, hold: a.hold || 6, t: 0 };
189
+ }
190
+ else if (!a)
191
+ img.src = ctx.spriteBase + 'idle/0.png';
192
+ }
193
+ }
194
+ function wizDemoTick() {
195
+ if (!padWizAnim)
196
+ return;
197
+ const a = padWizAnim;
198
+ a.t++;
199
+ const img = ctx.$('#padwiz-demo-img');
200
+ if (img)
201
+ img.src = ctx.spriteBase + a.seq[Math.floor(a.t / a.hold) % a.seq.length] + '.png';
202
+ }
203
+ /**
204
+ * Anda até o próximo passo que ESTE jogo usa, ou fecha se não houver mais.
205
+ *
206
+ * ⚠️ UMA FUNÇÃO SÓ, e antes eram duas com regras diferentes: `wizBind` incrementava e fechava no fim,
207
+ * `wizPrompt` saltava as não nomeadas. Depois do último passo nomeado o assistente ficava aberto a
208
+ * apontar para uma posição que o jogo não usa, e só fechava no tique seguinte — uma criança veria o
209
+ * assistente pendurado, sem pergunta nenhuma na tela.
210
+ */
211
+ function wizAvancar() {
212
+ if (!padWiz)
213
+ return;
214
+ while (padWiz.step < PADWIZ_ORDER.length && !ctx.rotuloDaAcao(PADWIZ_ORDER[padWiz.step]))
215
+ padWiz.step++;
216
+ if (padWiz.step >= PADWIZ_ORDER.length)
217
+ closePadWiz(true);
218
+ }
219
+ function wizPrompt() {
220
+ if (!padWiz)
221
+ return;
222
+ wizAvancar();
223
+ if (!padWiz)
224
+ return; // fechou ao avançar
225
+ const acao = PADWIZ_ORDER[padWiz.step];
226
+ const rotulo = ctx.rotuloDaAcao(acao);
227
+ wizSay((padWiz.step + 1) + ' de ' + PADWIZ_ORDER.length + ' — aperte: ' + rotulo);
228
+ wizDemo(acao); // demonstração animada do que a ação FAZ
229
+ const pr = ctx.$('#padwiz-progress');
230
+ if (pr)
231
+ pr.textContent = 'Mapeados: ' + (Object.keys(padWiz.map).join(' · ') || '—');
232
+ }
233
+ function wizBind(bd) {
234
+ if (!padWiz)
235
+ return;
236
+ padWiz.map[PADWIZ_ORDER[padWiz.step]] = bd;
237
+ padWiz.step++;
238
+ padWiz.release = true; // exige soltar antes do próximo passo (mesmo botão segurado não dobra pro passo seguinte)
239
+ wizAvancar(); // salta o que este jogo não usa, e fecha se o resto da lista for tudo isso
240
+ }
241
+ // ----- wizard: abrir/fechar -----
242
+ function openPadWiz() {
243
+ const ov = ctx.$('#padwiz');
244
+ if (!ov)
245
+ return;
246
+ ov.hidden = false;
247
+ ctx.frontOverlay(ov);
248
+ padWiz = { gi: -1, id: '', step: -1, base: null, map: {}, release: false, baseWait: false, axTrack: null, timer: null };
249
+ wizSay('Aperte QUALQUER botão no controle que deseja mapear.');
250
+ wizDemo(null);
251
+ const pr = ctx.$('#padwiz-progress');
252
+ if (pr)
253
+ pr.textContent = '';
254
+ padWiz.timer = setInterval(padWizTick, 30);
255
+ }
256
+ // Wizard aberto AUTOMATICAMENTE (controle DirectInput sem mapa apertou algo): já sabemos qual controle é.
257
+ function openPadWizFor(gp) {
258
+ const ov = ctx.$('#padwiz');
259
+ if (!ov)
260
+ return;
261
+ ov.hidden = false;
262
+ ctx.frontOverlay(ov);
263
+ padWiz = { gi: gp.index, id: gp.id, step: -1, base: null, map: {}, release: false, baseWait: true, axTrack: null, timer: null };
264
+ wizSay('Controle novo detectado: ' + gp.id + '. O jogo pausou para você configurá-lo. SOLTE tudo para começar.');
265
+ wizDemo(null);
266
+ const pr = ctx.$('#padwiz-progress');
267
+ if (pr)
268
+ pr.textContent = '';
269
+ padWiz.timer = setInterval(padWizTick, 30);
270
+ }
271
+ function closePadWiz(save) {
272
+ if (!padWiz)
273
+ return;
274
+ if (padWiz.timer != null)
275
+ clearInterval(padWiz.timer);
276
+ if (save && padWiz.id) {
277
+ store.setJSON('incl_padmap_' + padWiz.id, padWiz.map);
278
+ _padMaps.set(padWiz.id, padWiz.map);
279
+ ctx.srAlert(t('sr.pad.mapSaved', { id: padWiz.id }));
280
+ }
281
+ else if (padWiz.id && !_padMaps.get(padWiz.id)) {
282
+ _padMaps.set(padWiz.id, { _skip: true }); // cancelou: usa o mapa PADRÃO nesta sessão (não persiste, evita reabrir em loop)
283
+ }
284
+ const gi = padWiz.gi;
285
+ padWiz = null;
286
+ const ov = ctx.$('#padwiz');
287
+ if (ov)
288
+ ov.hidden = true;
289
+ // sem edges fantasmas: o botão ainda SEGURADO do último passo (START) não pode pausar/agir ao retomar
290
+ try {
291
+ const pads = ctx.getGamepads() ?? [];
292
+ const gp = pads[gi];
293
+ if (gp) {
294
+ const c = actionsFor(gp);
295
+ padCur[gi] = c;
296
+ padPrevAct[gi] = c;
297
+ padPrevStart[gi] = c._start;
298
+ }
299
+ }
300
+ catch { /* espelha o try/catch silencioso do original */ }
301
+ if (padWizAutoResume) {
302
+ padWizAutoResume = false;
303
+ if (ctx.menuDePausa())
304
+ ctx.retomar();
305
+ }
306
+ }
307
+ // ----- wizard: tick (roda a cada 30ms enquanto aberto) -----
308
+ function padWizTick() {
309
+ if (!padWiz)
310
+ return;
311
+ wizDemoTick();
312
+ const pads = ctx.getGamepads() ?? [];
313
+ if (padWiz.gi < 0) {
314
+ for (const gp of pads) {
315
+ if (gp && gp.buttons.some((b) => b && b.pressed)) {
316
+ padWiz.gi = gp.index;
317
+ padWiz.id = gp.id;
318
+ padWiz.baseWait = true;
319
+ wizSay('Controle: ' + gp.id + '. Agora SOLTE tudo.');
320
+ break;
321
+ }
322
+ }
323
+ return;
324
+ }
325
+ const gp = pads[padWiz.gi];
326
+ if (!gp)
327
+ return; // controle desconectado (ou índice ainda não populado): congela até voltar
328
+ if (padWiz.baseWait) {
329
+ if (!gp.buttons.some((b) => b && b.pressed)) {
330
+ padWiz.baseWait = false;
331
+ padWiz.base = { b: gp.buttons.map((x) => !!(x && x.pressed)), a: gp.axes.slice() };
332
+ padWiz.step = 0;
333
+ wizPrompt();
334
+ }
335
+ return;
336
+ }
337
+ const base = padWiz.base;
338
+ if (padWiz.release) {
339
+ 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);
340
+ if (idle) {
341
+ padWiz.release = false;
342
+ wizPrompt();
343
+ }
344
+ return;
345
+ }
346
+ // eixo em rastreio (~240ms): classifica pelo COMPORTAMENTO — varia continuamente = analógico (limiar por
347
+ // sinal); salta e FICA CONSTANTE = D-pad/POV hat (valor exato, ±0.13).
348
+ if (padWiz.axTrack) {
349
+ const t = padWiz.axTrack;
350
+ const v = gp.axes[t.i] || 0;
351
+ if (Math.abs(v - t.last) > 0.03)
352
+ t.changes++;
353
+ t.last = v;
354
+ if (Math.abs(v - base.a[t.i]) > Math.abs(t.v - base.a[t.i]))
355
+ t.v = v;
356
+ if (++t.ticks >= 8) {
357
+ const pv = t.v;
358
+ padWiz.axTrack = null;
359
+ wizBind(t.changes >= 2 ? { ax: t.i, s: pv > 0 ? 1 : -1 } : { av: t.i, v: Math.round(pv * 10000) / 10000 });
360
+ }
361
+ return;
362
+ }
363
+ for (let i = 0; i < gp.buttons.length; i++) {
364
+ if (gp.buttons[i] && gp.buttons[i].pressed && !base.b[i]) {
365
+ wizBind({ b: i });
366
+ return;
367
+ }
368
+ }
369
+ for (let i = 0; i < gp.axes.length; i++) {
370
+ const v = gp.axes[i] || 0;
371
+ if (Math.abs(v - base.a[i]) > 0.45) {
372
+ padWiz.axTrack = { i, v, last: v, changes: 0, ticks: 0 };
373
+ return;
374
+ }
375
+ }
376
+ }
377
+ const cancelBtn = ctx.$('#padwiz-cancel');
378
+ if (cancelBtn)
379
+ cancelBtn.addEventListener('click', () => closePadWiz(false));
380
+ // ----- poll (chamado a cada frame do loop) -----
381
+ function pollPads() {
382
+ if (padWiz)
383
+ return; // durante o wizard, os pads falam só com ele
384
+ const pads = ctx.getGamepads();
385
+ if (!pads)
386
+ return;
387
+ if (ctx.isAttractActive()) {
388
+ for (const gp of pads) {
389
+ if (gp && gp.buttons.some((b) => b && b.pressed)) {
390
+ ctx.stopAttract();
391
+ return;
392
+ }
393
+ } // botão de pad encerra a demo
394
+ return;
395
+ }
396
+ for (const gp of pads) {
397
+ if (!gp)
398
+ continue;
399
+ const gi = gp.index;
400
+ // controle fora do padrão (DirectInput) SEM mapa salvo apertou algo -> pausa geral + wizard direto
401
+ if (gp.mapping !== 'standard' && !padMapFor(gp.id) && gp.buttons.some((b) => b && b.pressed)) {
402
+ padWizAutoResume = ctx.mundoRodando();
403
+ if (ctx.mundoRodando())
404
+ ctx.pausar();
405
+ openPadWizFor(gp);
406
+ return;
407
+ }
408
+ const cur = actionsFor(gp);
409
+ const prev = padPrevAct[gi] || {};
410
+ if (ctx.isTouchMode() && (cur.left || cur.right || cur.up || cur.down || cur.action2 || cur.action1 || cur.action4 || cur.action3 || cur._start)) {
411
+ ctx.hideTouchControls(); // botão físico usado -> some o gamepad virtual (mesma regra do teclado)
412
+ }
413
+ const startEdge = cur._start && !padPrevStart[gi];
414
+ padPrevStart[gi] = cur._start;
415
+ const pauseEdge = cur._pause && !prev._pause;
416
+ const edge = (k) => cur[k] && !prev[k];
417
+ padCur[gi] = cur;
418
+ padPrevAct[gi] = cur;
419
+ const winOv = ctx.$('#win-overlay');
420
+ if (winOv && !winOv.hidden) { // Vitória: START/pulo fecham o modal (Jogar de novo)
421
+ if (startEdge) {
422
+ const b = ctx.$('#btn-again');
423
+ if (b)
424
+ b.click();
425
+ }
426
+ continue;
427
+ }
428
+ // Os três ramos abaixo pediam a FASE; hoje pedem os fatos. O `telaDeTitulo` é derivado por exclusão
429
+ // de propósito: numa cena que este módulo não conheça (um mapa, uma tela de resultados), o controle
430
+ // deve navegar como no título — que é o comportamento seguro — em vez de não fazer nada.
431
+ const rodando = ctx.mundoRodando(), pausado = ctx.menuDePausa();
432
+ const players = ctx.getPlayers();
433
+ if (!rodando && !pausado) {
434
+ const k = { yes: edge('action2') || startEdge, no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
435
+ const any = k.yes || k.no || k.up || k.down || k.left || k.right;
436
+ const owner = players.findIndex((p) => p.pad === gi);
437
+ if (ctx.getNumPlayers() > 1 && owner > 0) {
438
+ if (any)
439
+ ctx.srSay(t('sr.title.waitP1'));
440
+ continue;
441
+ } // só o J1 escolhe
442
+ if (any)
443
+ ctx.navTitle(k); // menu inicial navegável pelo pad
444
+ continue;
445
+ }
446
+ if (pausado) {
447
+ const owner = players.findIndex((p) => p.pad === gi);
448
+ const pi = owner < 0 ? 0 : owner;
449
+ if (pauseEdge) {
450
+ ctx.retomar();
451
+ continue;
452
+ } // START retoma
453
+ const k = { yes: edge('action2'), no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
454
+ if (k.yes || k.no || k.up || k.down || k.left || k.right) {
455
+ const dlg = ctx.sharedDialogOpen();
456
+ if (dlg)
457
+ ctx.navDialog(dlg, k);
458
+ else {
459
+ const menu = ctx.getPauseMenu(pi);
460
+ if (menu && !menu.hidden)
461
+ ctx.navPause(menu, pi, k);
462
+ }
463
+ }
464
+ continue;
465
+ }
466
+ if (rodando) {
467
+ const owner = players.findIndex((p) => p.pad === gi);
468
+ // ===================== O MODO `accessibility` (ADR-0044, item 7) =====================
469
+ // Com o jogo ANDANDO, o direcional deste jogador dirige a BARRA RÁPIDA e não o personagem. Vem antes
470
+ // de tudo o que é de jogo, porque enquanto o modo está ligado nada mais deste controle é de jogo.
471
+ //
472
+ // As DUAS saídas chegam juntas: `especial` é o VOLTAR do projeto (X no PlayStation, B no Xbox, A no
473
+ // Nintendo) e `startEdge` é o botão que abre a pausa — de onde se entrou aqui. Quem se perde tenta
474
+ // voltar por onde veio, e quem já conhece o jogo tenta o voltar de sempre; as duas dão certo.
475
+ if (owner >= 0 && ctx.naBarraDe(owner)) {
476
+ const k = { yes: edge('action2'), no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
477
+ if (startEdge || k.yes || k.no || k.up || k.down || k.left || k.right)
478
+ ctx.navBar(owner, k, !!startEdge);
479
+ continue;
480
+ }
481
+ if (owner < 0) { // atribuição POR ORDEM DE AÇÃO: qualquer botão associa -> 1º controle a agir -> 1º jogador sem pad
482
+ const anyEdge = edge('action2') || edge('action1') || edge('action4') || edge('action3') || startEdge || edge('left') || edge('right') || edge('up') || edge('down');
483
+ if (anyEdge) {
484
+ const waitI = players.findIndex((p) => p && p.waiting);
485
+ const free = waitI >= 0 ? waitI : players.findIndex((p) => p && p.pad < 0 && !p.quit);
486
+ if (free >= 0) {
487
+ players[free].pad = gi;
488
+ if (players[free].waiting) {
489
+ players[free].waiting = false;
490
+ ctx.clearWaitingBadge(free);
491
+ }
492
+ ctx.srSay(t('sr.pad.assigned', { n: free + 1 }));
493
+ }
494
+ else {
495
+ ctx.joinPlayer(gi);
496
+ }
497
+ }
498
+ }
499
+ else if (players[owner].quit) {
500
+ if (startEdge)
501
+ ctx.respawnPlayer(owner); // tela abandonada -> recomeça SÓ ela
502
+ }
503
+ else {
504
+ const p = players[owner];
505
+ if (pauseEdge) {
506
+ ctx.pausar();
507
+ ctx.setPauseActor(owner);
508
+ continue;
509
+ } // START pausa (todos pausam; cada tela navega a sua)
510
+ if (ctx.hasModal(owner)) { // o pad navega o modal do PRÓPRIO jogador (o jogo dos outros segue)
511
+ // A ORDEM é a do original: esquerda, direita, cima, baixo, confirmar, apagar. O que saiu foi o
512
+ // SIGNIFICADO — o ±1/±3 da grade e o desvio de Braille, que agora são decisão do jogo.
513
+ const intent = edge('left') ? 'left' : edge('right') ? 'right'
514
+ : edge('up') ? 'up' : edge('down') ? 'down'
515
+ : edge('action2') ? 'confirm' : edge('action3') ? 'erase' : null;
516
+ if (intent)
517
+ ctx.modalInput(owner, intent);
518
+ continue;
519
+ }
520
+ // A tabela e a guarda do Fácil vêm de input/edges.ts, as MESMAS que keydown e touch usam. Antes eram
521
+ // seis `if` à mão aqui, seis lá e seis no toque — e o do toque tinha esquecido o `!p.easy`.
522
+ for (const [act, flag] of EDGE_BY_ACTION) {
523
+ if (edgeAllowed(act, p.easy) && edge(act))
524
+ p[flag] = true;
525
+ }
526
+ }
527
+ }
528
+ }
529
+ }
530
+ return { pollPads, openPadWiz, openPadWizFor, closePadWiz, padWizTick, padMapFor, getPadWiz: () => padWiz };
531
+ }
@@ -0,0 +1,61 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { ControlledPlayer } from '../core/entity.js';
3
+ import type { KeyScheme } from '../core/entity.js';
4
+ import type { KBDefaults } from '../input/keyboard.js';
5
+ /** action -> list of physical key codes (KeyboardEvent.code), e.g. {jump:['KeyJ','Space']}. Mirrors the shape
6
+ * ui/settings-controls.ts also defines locally (input/keyboard.ts's KeyScheme is not exported — each consumer
7
+ * keeps its own structural copy rather than reaching across layers for a type alias). */
8
+ export type { KeyScheme } from '../core/entity.js';
9
+ /** input/keyboard.ts's KBDefaults shape ({solo,p2,p3,p4}) — the live `KB` value in game.js. */
10
+ /** O `KBDefaults` de `input/keyboard`, que é o dono. O nome local sobrevive porque os consumidores o usam. */
11
+ export type KeyboardConfig = KBDefaults;
12
+ /** Minimal player shape this module needs: only `ctrl` is read/written — derived from core/entity.
13
+ * `ControlledPlayer` rather than `Player` because this module runs after assignControls, so `ctrl` is no
14
+ * longer null. That was already assumed here; now it is stated. */
15
+ export type KeyboardRuntimePlayer = Pick<ControlledPlayer, 'ctrl'>;
16
+ export interface KeyboardRuntimeCtx {
17
+ /** The live keyboard config (game.js's `KB`), read fresh on every call — never cached. */
18
+ getKB(): KeyboardConfig;
19
+ /** Current player count (core/state.ts's `numPlayers`, read live via game.js). */
20
+ getNumPlayers(): number;
21
+ /** The live players array (core/state.ts's `players`, mutated in place — never reassigned). */
22
+ getPlayers(): KeyboardRuntimePlayer[];
23
+ }
24
+ /** Result of the old `applyControls()` mutation, as a value: `controls` + its per-action aliases (game.js's
25
+ * KJUMP/KLEFT/KRIGHT/KUP/KDOWN/KRUN) + the flattened `GAME_KEYS` list. */
26
+ export interface ControlsState {
27
+ controls: KeyScheme;
28
+ action2: string[];
29
+ left: string[];
30
+ right: string[];
31
+ up: string[];
32
+ down: string[];
33
+ action1: string[];
34
+ gameKeys: string[];
35
+ }
36
+ export interface KeyboardRuntime {
37
+ /** The key scheme for player `playerIndex`, given the current KB config + player count (game.js's kbFor). */
38
+ kbFor(playerIndex: number): KeyScheme;
39
+ /** Which action (if any) `code` triggers FOR that player, given their scheme (game.js's actionOf). */
40
+ actionOf(code: string, playerIndex: number): string | null;
41
+ /** Which player (0-based) owns `code` among the active players; -1 if none (game.js's whichPlayer). */
42
+ whichPlayer(code: string): number;
43
+ /** Propagates `kb` -> each active player's `p.ctrl`, mutated in place (game.js's assignControls). */
44
+ assignControls(): void;
45
+ /** Computes the P1-alias `controls` + `GAME_KEYS` (game.js's applyControls, minus the reassignment). */
46
+ computeControlsState(): ControlsState;
47
+ /** O estado ATUAL, memorizado. Antes eram oito `let` no game.js (`controls`, `KJUMP`..`KRUN`, `GAME_KEYS`)
48
+ * copiados de `computeControlsState()` por um `applyControls()` que existia só para fazer a cópia. Eram
49
+ * derivados de `KB` + `players`, guardados em variável — a forma de estado que mais apodrece, porque nada
50
+ * obriga a cópia a acompanhar a origem. Aqui a memória fica com quem é dono da conta, e a invalidação é
51
+ * explícita e única: `refreshControls()`. NÃO recalcula a cada leitura de propósito — recalcular mudaria
52
+ * o comportamento (passaria a enxergar remapeamento que ainda não foi aplicado), e isto é refatoração. */
53
+ controlsState(): ControlsState;
54
+ /** Recalcula e memoriza. É o `applyControls()` do game.js, agora do lado de cá. */
55
+ refreshControls(): ControlsState;
56
+ }
57
+ /** Which action does `code` trigger in this ONE scheme? Pure — no player/KB involved. */
58
+ export declare function actionForCode(scheme: KeyScheme, code: string): string | null;
59
+ /** Which player owns `code`, given one scheme per player (index = player index)? -1 if none. Pure. */
60
+ export declare function ownerOfCode(schemesByPlayer: readonly KeyScheme[], code: string): number;
61
+ export declare function initKeyboardRuntime(ctx: KeyboardRuntimeCtx): KeyboardRuntime;