@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,83 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/keyboard-runtime.ts — keyboard ROUTING at play time: which key belongs to which player, which action
3
+ // it fires. Extracted from game.js (kbFor/actionOf/whichPlayer/assignControls/applyControls). Pure logic, DI
4
+ // via initKeyboardRuntime(ctx): reads the live KB config (input/keyboard.ts owns load/save/reset) and the live
5
+ // numPlayers/players (still in game.js) through injected getters — never imports game.js, never touches
6
+ // `document`. KJUMP/KLEFT/.../GAME_KEYS come back as a plain VALUE object (computeControlsState) instead of
7
+ // being reassigned here: imported bindings can't be reassigned from outside their own module, so the game.js
8
+ // wrapper is the one that copies the returned fields onto its own `let`s (same trap `core/state.ts` documents
9
+ // for `coins`/`players`).
10
+ // ---------------------------------------------------------------------------------------------
11
+ // Pure logic (no ctx, no `document` — directly testable with plain KeyScheme fixtures)
12
+ // ---------------------------------------------------------------------------------------------
13
+ /** code -> action index for ONE scheme, first action wins when a code is bound twice in the same scheme
14
+ * (mirrors ui/settings-controls.ts's keyUsedByOther: a Map built over the scheme, not a re-scan per query). */
15
+ function buildActionIndex(scheme) {
16
+ const index = new Map();
17
+ for (const action in scheme) {
18
+ for (const code of scheme[action]) {
19
+ if (!index.has(code))
20
+ index.set(code, action);
21
+ }
22
+ }
23
+ return index;
24
+ }
25
+ /** Which action does `code` trigger in this ONE scheme? Pure — no player/KB involved. */
26
+ export function actionForCode(scheme, code) {
27
+ return buildActionIndex(scheme).get(code) ?? null;
28
+ }
29
+ /** Which player owns `code`, given one scheme per player (index = player index)? -1 if none. Pure. */
30
+ export function ownerOfCode(schemesByPlayer, code) {
31
+ for (let i = 0; i < schemesByPlayer.length; i++) {
32
+ if (actionForCode(schemesByPlayer[i], code))
33
+ return i;
34
+ }
35
+ return -1;
36
+ }
37
+ // ---------------------------------------------------------------------------------------------
38
+ // DI runtime (closes over ctx — still no `document`)
39
+ // ---------------------------------------------------------------------------------------------
40
+ export function initKeyboardRuntime(ctx) {
41
+ function kbFor(playerIndex) {
42
+ const kb = ctx.getKB();
43
+ const n = ctx.getNumPlayers();
44
+ if (n <= 1)
45
+ return kb.solo;
46
+ if (n <= 2)
47
+ return kb.p2[playerIndex] || kb.p2[0];
48
+ if (n <= 3)
49
+ return kb.p3[playerIndex] || kb.p3[0];
50
+ return kb.p4[playerIndex] || kb.p4[0];
51
+ }
52
+ function schemesForActivePlayers() {
53
+ const n = ctx.getNumPlayers();
54
+ return Array.from({ length: n }, (_, i) => kbFor(i));
55
+ }
56
+ function actionOf(code, playerIndex) {
57
+ return actionForCode(kbFor(playerIndex), code);
58
+ }
59
+ function whichPlayer(code) {
60
+ return ownerOfCode(schemesForActivePlayers(), code);
61
+ }
62
+ function assignControls() {
63
+ ctx.getPlayers().forEach((p, i) => { p.ctrl = kbFor(i); });
64
+ }
65
+ function computeControlsState() {
66
+ const kb = ctx.getKB();
67
+ const controls = kb.solo; // alias do P1 — SEMPRE kb.solo, mesmo com numPlayers>1 (comportamento original; ver relato)
68
+ const { action2, left, right, up, down, action1 } = controls;
69
+ const gameKeySet = new Set();
70
+ ctx.getPlayers().forEach((_, i) => {
71
+ const scheme = kbFor(i);
72
+ for (const action in scheme)
73
+ for (const code of scheme[action])
74
+ gameKeySet.add(code);
75
+ });
76
+ const gameKeys = gameKeySet.size ? [...gameKeySet] : [...action2, ...left, ...right, ...up, ...down];
77
+ return { controls, action2, left, right, up, down, action1, gameKeys };
78
+ }
79
+ let cache = null; // memória do estado derivado; só refreshControls a invalida
80
+ function controlsState() { return cache ?? (cache = computeControlsState()); }
81
+ function refreshControls() { cache = computeControlsState(); return cache; }
82
+ return { kbFor, actionOf, whichPlayer, assignControls, computeControlsState, controlsState, refreshControls };
83
+ }
@@ -0,0 +1,45 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { KeyScheme } from '../core/entity.js';
3
+ export type { KeyScheme } from '../core/entity.js';
4
+ /**
5
+ * O CONJUNTO DE ESQUEMAS de teclado — solo mais os de 2, 3 e 4 jogadores.
6
+ *
7
+ * Exportado desde 2026-08-26, e o motivo é que ele já era copiado: `input/keyboard-runtime` e
8
+ * `ui/settings-controls` declaravam cada um o seu `KeyboardConfig`, e AMBOS diziam no comentário que
9
+ * era "a forma do KBDefaults de input/keyboard". Sabiam que eram cópia e copiavam mesmo assim.
10
+ *
11
+ * A de `settings-controls` era `Record<string, unknown>` — pensada como opaca, para o módulo não
12
+ * depender da forma. Mas em posição de PARÂMETRO isso se inverte: para aceitar `(next: KBDefaults)`,
13
+ * o tipo declarado tem de ser SUBTIPO, não supertipo. A opacidade se preserva por disciplina — não
14
+ * indexar o valor — e não por escrever um tipo mais largo (ADR-0039).
15
+ */
16
+ export type KBDefaults = {
17
+ solo: KeyScheme;
18
+ p2: KeyScheme[];
19
+ p3: KeyScheme[];
20
+ p4: KeyScheme[];
21
+ };
22
+ export declare const KB_SCHEMES4: KeyScheme[];
23
+ export declare const KB_DEFAULTS: KBDefaults;
24
+ export declare function loadKB(): KBDefaults;
25
+ export declare function saveKB(kb: KBDefaults): void;
26
+ /**
27
+ * O MAPA DE TECLAS VIVO. Migrado do composition root (#50): era um `let KB` do main.js com um envoltório
28
+ * `setKB: (k) => { KB = k; }` fabricado à mão para o painel de controles reatribuí-lo.
29
+ *
30
+ * Mora AQUI, e não em core/state como os outros migrados, porque o dono é evidente: `loadKB`, `saveKB` e
31
+ * `resetKB` já viviam neste arquivo. Separar o valor das três funções que o gerenciam seria mover o problema
32
+ * de lugar em vez de resolvê-lo.
33
+ *
34
+ * NASCE COM OS PADRÕES E NÃO LÊ DISCO NO IMPORT. `initKB()` é quem lê, chamado uma vez pelo boot. A regra vale
35
+ * para todo módulo do projeto, e aqui ela tem um custo concreto se for quebrada: um teste que importe qualquer
36
+ * coisa deste arquivo passaria a depender do localStorage do ambiente, e um mapa de teclas herdado de outro
37
+ * caso é uma falha que aparece longe da causa.
38
+ */
39
+ export declare let kb: KBDefaults;
40
+ /** Lê o mapa persistido para dentro de `kb`. O boot chama uma vez; devolve o valor para quem quiser encadear. */
41
+ export declare function initKB(): KBDefaults;
42
+ /** Troca o mapa inteiro. Só o "restaurar padrões" do painel de controles precisa disto — remapear uma tecla
43
+ * MUTA o objeto, e reatribuir por engano faria as referências vivas apontarem para o mapa antigo. */
44
+ export declare function setKB(next: KBDefaults): void;
45
+ export declare function resetKB(): KBDefaults;
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/keyboard.ts — esquemas de teclado (config) + persistência. Módulo-folha (só depende de storage).
3
+ // ⚠️ AS AÇÕES SÃO POSIÇÕES, NÃO VERBOS, desde 2026-09-06: `up`, `down`, `left`, `right` e `action1`..`action4`.
4
+ // A linha anterior listava «run(corre/interage), jump, swap(troca poder), especial» — vocabulário de PLATAFORMA
5
+ // dentro da engine, que é exatamente o que o ADR-0074 tirou daqui. Qual verbo mora em qual posição é do JOGO,
6
+ // e o ADR-0086 §2 diz qual é para a plataforma. Esquemas por contagem de jogadores (solo/p2/p3/p4).
7
+ // A INSTÂNCIA atual (KB) e o remap ficam no composition root — aqui só config/load/save/reset.
8
+ import * as store from '../platform/storage.js';
9
+ const CKEY = 'inclusionist.kbcontrols.v3';
10
+ // 4 esquemas base p/ 3–4 jogadores (modos 3 e 4 têm esquemas SEPARADOS, p3 e p4, editáveis por jogador)
11
+ export const KB_SCHEMES4 = [
12
+ { left: ['KeyA'], right: ['KeyD'], up: ['KeyW'], down: ['KeyS'], action1: ['KeyZ'], action2: ['KeyX'], action4: ['KeyC'], action3: ['KeyV'] },
13
+ { left: ['KeyJ'], right: ['KeyL'], up: ['KeyI'], down: ['KeyK'], action1: ['KeyM'], action2: ['Comma'], action4: ['Period'], action3: ['Semicolon', 'Slash'] },
14
+ { left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Home'], action2: ['End'], action4: ['PageUp'], action3: ['PageDown'] },
15
+ { left: ['Numpad4'], right: ['Numpad6'], up: ['Numpad8'], down: ['Numpad5'], action1: ['Numpad2'], action2: ['Numpad0'], action4: ['Numpad3'], action3: ['NumpadDecimal'] },
16
+ ];
17
+ export const KB_DEFAULTS = {
18
+ // 1 jogador: WASD + setas; pulo J/Espaço; UJIK como na mão pequena do DOS. Sem Alt/AltGr/Ctrl/Shift.
19
+ solo: { left: ['KeyA', 'ArrowLeft'], right: ['KeyD', 'ArrowRight'], up: ['KeyW', 'ArrowUp'], down: ['KeyS', 'ArrowDown'],
20
+ action1: ['KeyU'], action2: ['KeyJ', 'Space'], action4: ['KeyI'], action3: ['KeyK'] },
21
+ p2: [{ left: ['KeyA'], right: ['KeyD'], up: ['KeyW'], down: ['KeyS'], action1: ['KeyU'], action2: ['KeyJ'], action4: ['KeyI'], action3: ['KeyK'] },
22
+ { left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Numpad8'], action2: ['Numpad5'], action4: ['Numpad9'], action3: ['Numpad6'] }],
23
+ p3: JSON.parse(JSON.stringify(KB_SCHEMES4.slice(0, 3))), // modo 3 jogadores (independente do 4)
24
+ p4: JSON.parse(JSON.stringify(KB_SCHEMES4)), // modo 4 jogadores
25
+ };
26
+ // dado salvo (parcial): sobrepõe os defaults; p34 é o formato ANTIGO (migra p/ p3+p4).
27
+ // A FORMA vem de `vocabulary-migration`, que é quem a traduz — declarar aqui outra vez seria a
28
+ // cópia que o `core/entity` passou o mês a eliminar.
29
+ import { migrarSalvo } from './vocabulary-migration.js';
30
+ // ⚠️ A MIGRAÇÃO DE VOCABULÁRIO MORA NOUTRO FICHEIRO, e a separação é deliberada:
31
+ // `input/vocabulary-migration.ts` é o ÚNICO sítio da engine autorizado a dizer `jump`, porque traduzir o
32
+ // nome antigo é a função dele. Deixá-la aqui punha o acoplamento num módulo que não é histórico, e o gate
33
+ // `action-vocabulary-boundary` reprovou — corretamente. Ver o cabeçalho de lá para saber quando se apaga.
34
+ // carrega os esquemas salvos SOBRE os defaults (com migração do dado antigo p34 → p3+p4)
35
+ export function loadKB() {
36
+ const d = JSON.parse(JSON.stringify(KB_DEFAULTS));
37
+ // ⚠️ O DADO SALVO ATRAVESSA O TRADUTOR ANTES DE TOCAR NOS PADRÕES. Sem esta linha, um esquema gravado com
38
+ // as chaves antigas (`run`, `jump`, `swap`, `especial`) seria fundido sobre defaults que já usam
39
+ // `action1`..`action4`: o objeto ficaria com AS DUAS famílias de chaves, os transportes leriam só as novas,
40
+ // e o remapeamento da criança viraria dado morto no navegador dela. Nada erraria em voz alta — as teclas
41
+ // dela simplesmente parariam de responder. Ver `input/vocabulary-migration.ts`.
42
+ const s = migrarSalvo(store.getJSON(CKEY, null));
43
+ if (s) {
44
+ if (s.solo)
45
+ Object.assign(d.solo, s.solo);
46
+ if (Array.isArray(s.p34)) {
47
+ s.p34.forEach((m, i) => { if (m) {
48
+ if (d.p4[i])
49
+ Object.assign(d.p4[i], m);
50
+ if (i < 3 && d.p3[i])
51
+ Object.assign(d.p3[i], m);
52
+ } });
53
+ }
54
+ ['p2', 'p3', 'p4'].forEach((g) => { const arr = s[g]; if (Array.isArray(arr))
55
+ arr.forEach((m, i) => { if (d[g][i] && m)
56
+ Object.assign(d[g][i], m); }); });
57
+ }
58
+ return d;
59
+ }
60
+ export function saveKB(kb) { store.setJSON(CKEY, kb); }
61
+ /**
62
+ * O MAPA DE TECLAS VIVO. Migrado do composition root (#50): era um `let KB` do main.js com um envoltório
63
+ * `setKB: (k) => { KB = k; }` fabricado à mão para o painel de controles reatribuí-lo.
64
+ *
65
+ * Mora AQUI, e não em core/state como os outros migrados, porque o dono é evidente: `loadKB`, `saveKB` e
66
+ * `resetKB` já viviam neste arquivo. Separar o valor das três funções que o gerenciam seria mover o problema
67
+ * de lugar em vez de resolvê-lo.
68
+ *
69
+ * NASCE COM OS PADRÕES E NÃO LÊ DISCO NO IMPORT. `initKB()` é quem lê, chamado uma vez pelo boot. A regra vale
70
+ * para todo módulo do projeto, e aqui ela tem um custo concreto se for quebrada: um teste que importe qualquer
71
+ * coisa deste arquivo passaria a depender do localStorage do ambiente, e um mapa de teclas herdado de outro
72
+ * caso é uma falha que aparece longe da causa.
73
+ */
74
+ export let kb = JSON.parse(JSON.stringify(KB_DEFAULTS));
75
+ /** Lê o mapa persistido para dentro de `kb`. O boot chama uma vez; devolve o valor para quem quiser encadear. */
76
+ export function initKB() { kb = loadKB(); return kb; }
77
+ /** Troca o mapa inteiro. Só o "restaurar padrões" do painel de controles precisa disto — remapear uma tecla
78
+ * MUTA o objeto, e reatribuir por engano faria as referências vivas apontarem para o mapa antigo. */
79
+ export function setKB(next) { kb = next; }
80
+ export function resetKB() { store.remove(CKEY); return JSON.parse(JSON.stringify(KB_DEFAULTS)); }
@@ -0,0 +1,270 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { PlayerView } from '../core/entity.js';
3
+ import { type EdgeFlag } from './edges.js';
4
+ /** ui/dom.ts `$` — injetado; o módulo nunca alcança `document`. */
5
+ export type { DomQuery } from '../core/dom-query.js';
6
+ /** O que este módulo lê e faz com um `KeyboardEvent` de `keydown`. */
7
+ export interface KeydownEventLike {
8
+ code: string;
9
+ altKey: boolean;
10
+ ctrlKey: boolean;
11
+ preventDefault(): void;
12
+ }
13
+ /** `keyup` só precisa do código. */
14
+ export interface KeyupEventLike {
15
+ code: string;
16
+ }
17
+ /** ação -> lista de códigos físicos. Cópia ESTRUTURAL do `KeyScheme` de input/keyboard-runtime.ts: a casa
18
+ * prefere a cópia a puxar um alias de tipo através de camadas (ver o cabeçalho de keyboard-runtime). */
19
+ export type { KeyScheme } from '../core/entity.js';
20
+ /** As seis bordas de entrada que o keydown levanta no jogador (consumidas e zeradas pela física). */
21
+ /**
22
+ * O que este módulo lê (e escreve) de um jogador — e SÓ isso. DERIVADA de core/entity.
23
+ *
24
+ * · `i` — índice PRÓPRIO do jogador (`makePlayer(i)`). O original roteia por ele, não pela posição.
25
+ * · `ctrl` — esquema de teclas; `null` antes do assignControls.
26
+ * (`quiz` SAIU em 2026-08-25 — ADR-0033. O desafio aberto chega por `modalOpen` no snapshot, e o que a
27
+ * tecla SIGNIFICA lá dentro é decisão do jogo. Ver o bloco "MODAL" abaixo.)
28
+ * · `waiting` — tela em espera (multi-tela): a 1ª tecla DAQUELE jogador entra na partida.
29
+ * · `easy` — modo Fácil (deficiência motora): sem correr, e ganha os atalhos Ctrl/Shift no solo.
30
+ */
31
+ export type KeydownPlayer = PlayerView<'i' | 'ctrl' | 'waiting' | 'easy' | 'jumpEdge' | 'runEdge' | 'leftEdge' | 'rightEdge' | 'swapEdge' | 'specialEdge'>;
32
+ /** Subconjunto do `ControlsState` de input/keyboard-runtime.ts que a decisão consulta (`controls` não entra:
33
+ * o original nunca o usa aqui, só os seis aliases e a lista achatada). */
34
+ export interface ControlsSnapshot {
35
+ action2: string[];
36
+ left: string[];
37
+ right: string[];
38
+ up: string[];
39
+ down: string[];
40
+ action1: string[];
41
+ gameKeys: string[];
42
+ }
43
+ /** A intenção de navegação do menu inicial. Era uma quarta cópia estrutural, sob outro nome; agora é o
44
+ * `NavKeys` de input/edges, reexportado com o nome que os chamadores deste módulo já usam. */
45
+ import type { NavKeys as TitleNav } from './edges.js';
46
+ export type { NavKeys as TitleNav } from './edges.js';
47
+ /** TUDO o que a decisão precisa saber do mundo, num objeto só, montado ANTES de qualquer efeito. */
48
+ export interface KeydownSnapshot {
49
+ /** A tela de título está no topo? (Só o Jogador 1 escolhe o jogo; os outros ouvem um aviso.) */
50
+ telaDeTitulo: boolean;
51
+ /** Há jogo em curso — rodando OU pausado? (Alt+N e a tecla de pausa exigem isto, e quiz fechado.) */
52
+ emJogo: boolean;
53
+ numPlayers: number;
54
+ players: readonly KeydownPlayer[];
55
+ /**
56
+ * Há MODAL aberto para o jogador de cada POSIÇÃO? — a resposta, ao lado dos jogadores (ADR-0033).
57
+ *
58
+ * Era `players[i].quiz`, e o custo nunca foi a leitura: `players` já chega por injeção. O custo era que
59
+ * ler o campo obrigava `core/entity.Player` — a entidade canônica da ENGINE — a DECLARÁ-LO, e com isso
60
+ * todo jogo do catálogo a ter um "quiz" com aquele formato.
61
+ *
62
+ * Por que aqui e não em duas funções de ctx: este módulo já monta um snapshot antes de qualquer efeito, e
63
+ * acrescentar ctx a quem já tem snapshot é peça a mais. Por que um array PARALELO e não um campo no
64
+ * `KeydownPlayer`: aquele tipo é `PlayerView<>` derivado de `Player`, então o campo voltaria a ser exigido
65
+ * na entidade — nome melhor, mesmo lugar errado. E por que não embrulhar os jogadores numa cópia: este
66
+ * módulo ESCREVE neles (as seis bordas), e a cópia perderia a escrita.
67
+ */
68
+ modalOpen: readonly boolean[];
69
+ controls: ControlsSnapshot;
70
+ /** `keys` de input/state.ts: as teclas seguradas AGORA (inclui códigos injetados por toque/webcam). */
71
+ heldKeys: ReadonlySet<string>;
72
+ /** empatia motora: um botão de jogo por vez. */
73
+ oneButton: boolean;
74
+ /** `overlays.escapeTarget()` — id do 1º diálogo REGISTRADO que está aberto, ou null. */
75
+ escapeTargetId: string | null;
76
+ touchCfgVisible: boolean;
77
+ padWizVisible: boolean;
78
+ winVisible: boolean;
79
+ actionOf(code: string, playerIndex: number): string | null;
80
+ whichPlayer(code: string): number;
81
+ }
82
+ export type ModalIntent = 'left' | 'right' | 'up' | 'down' | 'confirm' | 'erase';
83
+ /** Uma borda a levantar: qual jogador (POSIÇÃO no array) e qual flag. */
84
+ export interface EdgeRaise {
85
+ playerIndex: number;
86
+ edge: EdgeFlag;
87
+ }
88
+ /**
89
+ * A resposta única: o que esta tecla SIGNIFICA. `kind` é o ramo da ordem de precedência que venceu — e é essa
90
+ * palavra, e não o efeito, que os testes de precedência afirmam.
91
+ */
92
+ export type KeydownDecision =
93
+ /** 1º · diálogo REGISTRADO aberto: Escape fecha (`closeId`), qualquer outra tecla é engolida. */
94
+ {
95
+ kind: 'overlay';
96
+ closeId: string | null;
97
+ preventDefault: boolean;
98
+ }
99
+ /** 2º · `#touchcfg` visível: Escape esconde, o resto é engolido. */
100
+ | {
101
+ kind: 'touchcfg';
102
+ close: boolean;
103
+ preventDefault: boolean;
104
+ }
105
+ /** 3º · `#padwiz` (assistente de gamepad) visível: Escape CANCELA (`save:false`), o resto é engolido. */
106
+ | {
107
+ kind: 'padwiz';
108
+ close: boolean;
109
+ preventDefault: boolean;
110
+ }
111
+ /** 4º · tela de vitória: pulo (de qualquer jogador) ou Escape/Enter apertam "Jogar de novo". */
112
+ | {
113
+ kind: 'win';
114
+ again: boolean;
115
+ preventDefault: boolean;
116
+ }
117
+ /** 5º · tela de título: `wait` = multi-tela e a tecla é de outro jogador (só o J1 comanda). */
118
+ | {
119
+ kind: 'title';
120
+ wait: boolean;
121
+ nav: TitleNav | null;
122
+ preventDefault: boolean;
123
+ }
124
+ /** 6º · Alt+1..4 = número de telas. */
125
+ | {
126
+ kind: 'screens';
127
+ count: number;
128
+ preventDefault: boolean;
129
+ }
130
+ /** 7º · Escape/Enter = pausa. */
131
+ | {
132
+ kind: 'pause';
133
+ preventDefault: boolean;
134
+ }
135
+ /** 8º · modal aberto: a tecla age no modal do DONO dela (`playerIndex` = posição no array). */
136
+ | {
137
+ kind: 'modal';
138
+ playerIndex: number;
139
+ intent: ModalIntent | null;
140
+ preventDefault: boolean;
141
+ }
142
+ /** 9º · jogo normal. */
143
+ | {
144
+ kind: 'play';
145
+ gameKey: boolean;
146
+ wake: number[];
147
+ edges: EdgeRaise[];
148
+ releaseKeys: string[];
149
+ preventDefault: boolean;
150
+ };
151
+ /** Fácil (SOLO): Ctrl = Especial, Shift = Trocar poder. Fora de Win/Alt/AltGr, de propósito. */
152
+ export declare const EASY_SHORTCUTS: ReadonlySet<string>;
153
+ /** Alt + fileira de números (NÃO o teclado numérico) = 1..4 telas. Alt fica livre: o solo não o usa. */
154
+ export declare const SCREEN_DIGITS: RegExp;
155
+ /** Pausa: Escape ou o Enter CENTRAL. `NumpadEnter` NÃO pausa (E14, verbatim). */
156
+ export declare const PAUSE_KEYS: ReadonlySet<string>;
157
+ export { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
158
+ export type { EdgeFlag } from './edges.js';
159
+ /** A tecla vale como PULO para alguém? O alias do J1 OU a ação `jump` de qualquer jogador (é o que faz
160
+ * "qualquer um pode apertar Jogar de novo" e "qualquer um confirma no título" serem verdade). */
161
+ export declare function isJumpKey(code: string, s: KeydownSnapshot): boolean;
162
+ /** A tecla é de JOGO? Verbatim do `isGameKeyCode` do game.js — o alias achatado OU o esquema de algum
163
+ * jogador. NÃO inclui os atalhos do Fácil (o original também não: por isso `easyKey` é somado à parte). */
164
+ export declare function isGameKeyCode(code: string, s: KeydownSnapshot): boolean;
165
+ /** Os atalhos de acessibilidade do Fácil só existem SOLO (`numPlayers<=1`) e só para o Jogador 1. */
166
+ export declare function isEasyShortcut(code: string, s: KeydownSnapshot): boolean;
167
+ /**
168
+ * A intenção no menu inicial. Repare na ASSIMETRIA, preservada verbatim: `up`/`down` aceitam ArrowUp/ArrowDown
169
+ * ALÉM do alias configurado, `left`/`right` não aceitam ArrowLeft/ArrowRight. No esquema de fábrica isso não
170
+ * aparece (o solo já tem as setas nos quatro lados); aparece para quem REMAPEIA — remapeou esquerda para KeyA
171
+ * e a seta deixa de andar no menu, enquanto cima continua funcionando pelas duas. Está anotado como caso de
172
+ * teste em tests/keydown.node.test.js, e é candidato a conserto, não a "limpeza" silenciosa.
173
+ */
174
+ export declare function titleNavOf(code: string, s: KeydownSnapshot, action2: boolean): TitleNav;
175
+ import type { EventTargetLike } from './touch-bindings.js';
176
+ import type { DomQuery } from '../core/dom-query.js';
177
+ export { hasNavIntent as hasTitleIntent } from './edges.js';
178
+ /**
179
+ * Quem é o dono do modal que esta tecla comanda? Devolve a POSIÇÃO no array, ou -1.
180
+ * Regra verbatim: tecla de um jogador vai para o modal DAQUELE jogador (e só se ele tiver um aberto); tecla
181
+ * genérica cai no Jogador 1. Tecla de um jogador SEM modal aberto não cai no P1 — ela desce para o jogo
182
+ * normal, que é o que deixa a partida dele continuar enquanto o outro resolve o desafio.
183
+ */
184
+ export declare function modalOwnerIndex(code: string, s: KeydownSnapshot): number;
185
+ /**
186
+ * Que INTENÇÃO esta tecla exprime dentro do modal do dono. `owner` é a posição no array; `generic` diz se a
187
+ * tecla chegou sem dono (aí as seis leituras usam os aliases do J1 em vez da ação remapeada).
188
+ *
189
+ * As seis leituras são as mesmas de antes, letra por letra. O que saiu foi a linha seguinte: a que trocava
190
+ * "cima" por `delta:-3` e "esquerda" por `delta:-1`, e o desvio de Braille. Isso é grade, e grade é do jogo.
191
+ */
192
+ export declare function modalIntentOf(code: string, s: KeydownSnapshot, owner: number, generic: boolean): ModalIntent | null;
193
+ /**
194
+ * As bordas que esta tecla levanta. Só é chamada quando a tecla AINDA NÃO está segurada — é isso que a torna
195
+ * uma BORDA e não um estado, e é o que impede o auto-repeat do sistema de virar dez pulos.
196
+ * `p.ctrl[act] || []`: o original alcançava `p.ctrl.jump` direto e estouraria num esquema sem a ação. Trocar
197
+ * um TypeError por um no-op não remove rede de conserto nenhuma (o precedente é a guarda de `navPause` em
198
+ * ui/menu-nav.ts) — é a única linha desta função que não é cópia literal.
199
+ */
200
+ export declare function edgesFor(code: string, s: KeydownSnapshot): EdgeRaise[];
201
+ /**
202
+ * A CADEIA. Nove perguntas, nesta ordem — e a ordem É a especificação.
203
+ * O que NÃO está aqui, e vem antes no wrapper: a demo (attract) e a captura de remapeamento. Ver o cabeçalho.
204
+ */
205
+ export declare function decideKeydown(ev: {
206
+ code: string;
207
+ altKey?: boolean;
208
+ ctrlKey?: boolean;
209
+ }, s: KeydownSnapshot): KeydownDecision;
210
+ export interface KeydownCtx {
211
+ /** game/attract.ts: zera a ociosidade e encerra a demo; `true` = a tecla foi só para acordar. */
212
+ attractOnInput: () => boolean;
213
+ /** ui/settings-controls.ts: remapeamento em curso — a próxima tecla VIRA o controle. */
214
+ handleCaptureKeydown: (e: KeydownEventLike) => boolean;
215
+ /** Os dois fatos da cena. Ver `KeydownSnapshot`: booleanos, nunca a fase. */
216
+ isTelaDeTitulo: () => boolean;
217
+ isEmJogo: () => boolean;
218
+ getNumPlayers: () => number;
219
+ getPlayers: () => readonly KeydownPlayer[];
220
+ /** `kbRuntime.controlsState()` — memorizado do lado de lá; uma chamada por tecla, como no original. */
221
+ getControls: () => ControlsSnapshot;
222
+ /** `keys` de input/state.ts: `const` mutado in place → entra por VALOR (é sempre o mesmo objeto). */
223
+ heldKeys: Set<string>;
224
+ /** `let oneButton` do game.js (empatia motora) → getter. */
225
+ isOneButton: () => boolean;
226
+ actionOf: (code: string, playerIndex: number) => string | null;
227
+ whichPlayer: (code: string) => number;
228
+ $: DomQuery;
229
+ /** `overlays.escapeTarget()` de ui/settings-panel.ts. */
230
+ escapeTarget: () => string | null;
231
+ /** `overlays.closeById(id)` — o retorno é ignorado, verbatim. */
232
+ closeOverlayById: (id: string) => void;
233
+ /** input/gamepad.ts. LAZY: `gamepadApi` é `const` declarado ~800 linhas abaixo do ponto de init. */
234
+ closePadWiz: (save: boolean) => void;
235
+ /** input/touch.ts via o envelope içado do game.js. Sem motivo no título; `'teclado'` no jogo (E13). */
236
+ hideTouchControls: (reason?: string) => void;
237
+ srSay: (msg: string) => void;
238
+ navTitle: (k: TitleNav) => void;
239
+ activateScreens: (n: number) => void;
240
+ togglePause: () => void;
241
+ /**
242
+ * A INTENÇÃO chega, e o JOGO decide o que ela significa (ADR-0033).
243
+ *
244
+ * Eram quatro: `quizMove(pl, delta)`, `quizConfirm`, `quizErase` e `announceBraille` — e as quatro só
245
+ * existiam porque a decisão de qual chamar morava aqui, com a grade de três colunas e o desvio de Braille.
246
+ * Viraram uma. Um jogo com grade de quatro colunas, ou com lista vertical, responde diferente sem que este
247
+ * módulo saiba que existe grade.
248
+ */
249
+ /** O ÍNDICE do jogador, não o jogador (ADR-0033/0039). Ver a mesma nota em input/gamepad. */
250
+ modalInput: (playerIndex: number, intent: ModalIntent) => void;
251
+ /** O jogador da POSIÇÃO `i` tem um modal aberto? Uma pergunta, e não o objeto: ver `modalOpen` no snapshot. */
252
+ hasModal: (playerIndex: number) => boolean;
253
+ /** ui/hud.ts: some com o selo "tela em espera" quando o jogador daquela tela entra. */
254
+ clearWaitingBadge: (playerIndex: number) => void;
255
+ /** `window` — só para `attach()` instalar os dois ouvintes de BOLHA, exatamente como o game.js fazia. */
256
+ /** A escuta de teclado. Ver `EventTargetLike` em input/touch-bindings: genérica sobre `WindowEventMap`,
257
+ * porque a versão com `fn: (e: never)` obrigava um cast aqui e um adaptador em cada consumidor. */
258
+ win: EventTargetLike;
259
+ }
260
+ export interface KeydownApi {
261
+ /** O snapshot que a decisão consome. Exposto para o teste poder montá-lo e comparar. */
262
+ snapshot: () => KeydownSnapshot;
263
+ /** O ouvinte de bolha inteiro (sondas + decisão + efeito). Exportado à parte para o teste disparar sem DOM. */
264
+ onKeydown: (e: KeydownEventLike) => void;
265
+ /** A outra metade do `keys.add`. */
266
+ onKeyup: (e: KeyupEventLike) => void;
267
+ /** Instala os dois na janela, em BOLHA, na mesma ordem de antes. */
268
+ attach: () => void;
269
+ }
270
+ export declare function initKeydown(ctx: KeydownCtx): KeydownApi;