@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,173 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { DomQuery } from '../core/dom-query.js';
3
+ /** Minimal DOM-selector shape (matches ui/dom.ts's `$`). */
4
+ export type { DomQuery } from '../core/dom-query.js';
5
+ /** Minimal platform/storage.ts shape this module needs. */
6
+ export interface TouchStore {
7
+ get(key: string, fallback?: string | null): string | null;
8
+ set(key: string, value: string | number | boolean): boolean;
9
+ getNum(key: string, fallback?: number): number;
10
+ getJSON<T = unknown>(key: string, fallback?: T | null): T | null;
11
+ setJSON(key: string, obj: unknown): void;
12
+ }
13
+ export interface TouchCtx {
14
+ /** DOM selector (querySelector), injected — never reaches `document` globally. */
15
+ $: DomQuery;
16
+ /** Screen-reader "polite" announcement (core/a11y-sr's srSay), injected. */
17
+ srSay: (msg: string) => void;
18
+ /**
19
+ * AS POSIÇÕES QUE ESTE JOGO USA, cada uma com a palavra dele, no idioma vigente.
20
+ *
21
+ * ⚠️ Substitui `TOUCH_ACTS` + `TOUCH_ACT_LABELS`. O menu de cada slot oferecia NOVE opções fixas — quer
22
+ * dizer, a engine decidia que todo jogo tem pular, correr, trocar e especial, e uma criança num quiz
23
+ * poderia atribuir «Trocar poder» a um botão da tela, que depois não faria nada.
24
+ *
25
+ * A ordem também vem daqui: é a ordem canônica de `core/actions`, a mesma que a tela de remapeamento usa,
26
+ * para a criança não ter de reaprender a lista ao trocar de painel.
27
+ */
28
+ acoesDoJogo: () => readonly {
29
+ readonly acao: string;
30
+ readonly rotulo: string;
31
+ }[];
32
+ /** Persistence (platform/storage.ts), injected. */
33
+ store: TouchStore;
34
+ /** Element the --pad-* CSS custom properties are written to: document.documentElement in production
35
+ * (mirrors ui/settings-typo.ts's `root` injection for the same reason — no direct `document` access). */
36
+ root: HTMLElement;
37
+ /** Shared device-class check (game.js's isMobile(), used by several subsystems) — injected, not duplicated. */
38
+ isMobile: () => boolean;
39
+ /** Screen dimensions, injected (never reads `window.innerWidth/innerHeight` directly). */
40
+ viewport: () => {
41
+ w: number;
42
+ h: number;
43
+ };
44
+ /** Shared overlay z-index/focus helper (game.js's frontOverlay), used by every panel — injected. */
45
+ frontOverlay: (el: HTMLElement | null) => void;
46
+ /** Optional hook fired after applyPadDesign() changes padDesign — game.js's renderPauseLegend() (Sim/Não
47
+ * glyphs in the pause menu) is NOT part of this module's boundary but must still refresh; see report. */
48
+ onPadDesignApplied?: () => void;
49
+ /**
50
+ * O controle de tela ENTROU em cena. Existe para que a raiz possa ligar sozinha o que só faz sentido no
51
+ * toque — hoje, a alternância do botão de correr: num botão virtual ninguém "segura" com conforto, porque
52
+ * o dedo que segura é o mesmo que precisa alcançar os outros.
53
+ *
54
+ * Gancho e não regra aqui dentro: este módulo desenha controles e não conhece ajuste de acessibilidade
55
+ * nenhum. Quem sabe o que ligar é quem possui os jogadores.
56
+ */
57
+ onTouchControlsShown?: () => void;
58
+ /**
59
+ * PODE mostrar o pad virtual AGORA? Injetado, e é o corte do item 19 neste módulo.
60
+ *
61
+ * Era uma linha que lia TRÊS coisas por importação de `core/state`:
62
+ * `if (numPlayers > 1 || phase !== 'playing' || players.some((p) => p.quiz)) return;`
63
+ * — o número de jogadores, a fase, e se algum jogador tem um QUIZ aberto. A última é a que denunciava: a
64
+ * camada de TOQUE sabia que existe atividade de alfabetização. E as três juntas eram uma POLÍTICA do jogo
65
+ * escrita dentro da engine.
66
+ *
67
+ * É o mesmo movimento do achado 10 (`isNavigable`): injetar o BOOLEANO, não o estado. A plataforma responde
68
+ * "um jogador, jogando, sem desafio aberto"; um jogo de outro gênero responde o que for verdade nele. E o
69
+ * módulo deixou de importar `core/state` — não sobrou leitura de estado compartilhado nenhuma.
70
+ */
71
+ padAllowed: () => boolean;
72
+ }
73
+ export declare const IPHONE16_LONG_MM = 141.1;
74
+ export declare const IPHONE16_LONG_PX = 852;
75
+ export declare const IPHONE16_PXMM: number;
76
+ /** mm→px CSS. No celular, ancora na aresta longa da JANELA (físico exato, retrato ou paisagem). No
77
+ * desktop/notebook, fixa no ratio do iPhone 16 (não cresce com a largura do monitor). Determinístico e puro:
78
+ * `mobile`/`viewW`/`viewH` chegam já resolvidos (isMobile()/innerWidth/innerHeight ficam no lado impuro). */
79
+ export declare function padPxPerMm(mobile: boolean, viewW: number, viewH: number): number;
80
+ export type HandTag = 'crianca' | 'adulto' | 'inter';
81
+ /** Classifica um valor em mm na faixa mão-de-criança / intermediário / mão-de-adulto (usado nas etiquetas
82
+ * do painel de configuração de toque). */
83
+ export declare function padHandTag(v: number, lo: number, hi: number): HandTag;
84
+ export interface PadMm {
85
+ btn: number;
86
+ gap: number;
87
+ stick: number;
88
+ travel: number;
89
+ dpad: number;
90
+ }
91
+ export interface PadPhysicalPx {
92
+ btnPx: number;
93
+ diamPx: number;
94
+ knobPx: number;
95
+ basePx: number;
96
+ armPx: number;
97
+ armWPx: number;
98
+ spanPx: number;
99
+ stickTravelPx: number;
100
+ stickDeadPx: number;
101
+ }
102
+ /** Toda a geometria em px a partir dos mm configurados + do fator px/mm — extraída de applyPadPhysical() para
103
+ * ficar pura/testável (dpr alto/baixo, tela pequena, valores extremos). applyPadPhysical() só chama isto e
104
+ * escreve o resultado em custom properties CSS + nos rótulos do painel. */
105
+ export declare function computePadPhysicalPx(mm: PadMm, pxPerMm: number): PadPhysicalPx;
106
+ export type PadLayout = 'sony' | 'nintendo' | 'microsoft' | 'generic';
107
+ /** Layout de rotulagem dos botões pelo id reportado do controle (heurística por substring/VID). */
108
+ export declare function padLayoutFromId(id: string | null | undefined): PadLayout;
109
+ /** As 9 posições de toque remapeáveis (direcional×4, START, botões 0–3) — `lbl` é a chave i18n do rótulo. */
110
+ export declare const TOUCH_SLOTS: ReadonlyArray<{
111
+ k: string;
112
+ lbl: string;
113
+ }>;
114
+ /**
115
+ * As ações que o TRANSPORTE de toque consegue carregar.
116
+ *
117
+ * ⚠️ DEIXOU DE SER A LISTA DO `<select>`: as opções vêm agora de `ctx.acoesDoJogo()`, porque quem decide
118
+ * quais ações existem é o jogo. Esta lista continua a valer como o que ESTE transporte alcança — e é contra
119
+ * ela que o gate de `input/touch-bindings` confere se o despacho reconhece tudo o que se pode oferecer.
120
+ * As duas coisas eram a mesma por acidente enquanto só havia um jogo.
121
+ */
122
+ export declare const TOUCH_ACTS: readonly string[];
123
+ /** Funde o mapa persistido (JSON solto do localStorage) sobre TOUCH_DEFAULT. Mantido IDÊNTICO ao original:
124
+ * NÃO valida chaves/valores contra TOUCH_SLOTS/TOUCH_ACTS — um JSON malformado com chaves/valores estranhos
125
+ * passa como está (só falha se não for um objeto). Ver a nota "bug surfaced" no retorno da extração. */
126
+ export declare function normalizeTouchMap(stored: unknown): Record<string, string>;
127
+ export type PadKind = 'kb' | 'x' | 'd';
128
+ /** UNUSED hoje — grep em game.js (`\bpadKind\b`) só acha a própria definição, nenhum chamador. Extraído porque
129
+ * a fronteira explicitamente listou; NÃO apagado (relatado, não conserto). Lê navigator.getGamepads() direto,
130
+ * no mesmo padrão de ui/settings-audio.ts / core/i18n.ts (acesso direto a navigator, sem DI) — mesmo assim,
131
+ * é semanticamente "ler o controle", território do agente de input/gamepad; ver a nota de fronteira no retorno. */
132
+ export declare function padKind(): PadKind;
133
+ export interface PadMmPatch {
134
+ btn?: number;
135
+ gap?: number;
136
+ stick?: number;
137
+ travel?: number;
138
+ dpad?: number;
139
+ }
140
+ export interface TouchApi {
141
+ /** (Re)desenha #touchmap-list a partir do touchMap atual e prende os <select> de cada slot. */
142
+ renderTouchMap(): void;
143
+ /** Abre o diálogo "Botões de tela touch" (#touchcfg). */
144
+ openTouchCfg(): void;
145
+ /** Fecha o diálogo #touchcfg e devolve o foco ao botão que o abriu. */
146
+ closeTouchCfg(): void;
147
+ /** Esconde os controles de toque (teclado/controle físico assumiu, ou um menu abriu por cima). O parâmetro
148
+ * `reason` é só um rótulo de depuração nos call-sites originais — o game.js já o descartava (função de
149
+ * zero parâmetros); mantido aqui só de fachada para não quebrar chamadas existentes. */
150
+ hideTouchControls(reason?: string): void;
151
+ /** Mostra os controles de toque (toque/clique detectado). No-op em multi-tela, fora de 'playing', ou com
152
+ * quiz aberto — dá pra tocar direto nos botões da tela nesses casos. */
153
+ showTouchControls(): void;
154
+ /** Alterna a visibilidade do analógico virtual vs. da cruz (D-pad), conforme `padDir`. */
155
+ applyDirStyle(): void;
156
+ /** Recalcula toda a geometria (mm→px) e escreve as custom properties + os rótulos do painel. */
157
+ applyPadPhysical(): void;
158
+ /** Atualiza um ou mais tamanhos (mm), persiste e reaplica a geometria. */
159
+ setPadMm(patch: PadMmPatch): void;
160
+ /** Aplica (e opcionalmente troca) o desenho dos botões; persiste; repinta os botões físicos do losango de
161
+ * toque (#pad-diamond); dispara ctx.onPadDesignApplied() (legenda Sim/Não da pausa, fora daqui). Retorna o
162
+ * design resultante. */
163
+ applyPadDesign(d?: string): string;
164
+ /** Design de botão atualmente ativo ('generic'|'microsoft'|'sony'|'nintendo'). */
165
+ getPadDesign(): string;
166
+ /** Referência viva do mapa de toque atual (slot → ação) — mutada por renderTouchMap(); NÃO é uma cópia. */
167
+ getTouchMap(): Record<string, string>;
168
+ /** Deslocamento útil do analógico virtual em px, recalculado a cada applyPadPhysical(). */
169
+ getStickTravelPx(): number;
170
+ /** Zona-morta do analógico virtual em px, recalculada a cada applyPadPhysical(). */
171
+ getStickDeadPx(): number;
172
+ }
173
+ export declare function initTouch(ctx: TouchCtx): TouchApi;
@@ -0,0 +1,314 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/touch.ts — on-screen touch controls (Estágio 4): the physical geometry of the touch pad (mm→px) and
3
+ // its config UI (#touchcfg, #touch-controls visibility). Extracted from game.js's E13 block. Geometry/layout/
4
+ // touch-map-merge are pure (padPxPerMm/padHandTag/computePadPhysicalPx/padLayoutFromId/normalizeTouchMap —
5
+ // project node); DOM wiring + persisted state (touchMap/padDesign/padBtnMm/padGapMm/padStickMm/padTravelMm/
6
+ // padDpadMm/padDir) live behind initTouch(ctx). PAD_DESIGNS/TOUCH_ACT_LABELS/TOUCH_DEFAULT come from
7
+ // ./devices.js (not reimplemented). Reading the real Gamepad API (polling, mapping wizard) is input/gamepad's
8
+ // territory, not this module's — see the header note on padKind() for the one deliberate exception.
9
+ import { PAD_DESIGNS, TOUCH_DEFAULT } from './devices.js';
10
+ import { migrarMapaDeToque } from './vocabulary-migration.js';
11
+ import { t } from '../core/i18n.js';
12
+ import { KEYS } from '../platform/storage.js'; // só as CHAVES (constantes) — leitura/escrita passam por ctx.store (DI)
13
+ import { setMinimapCorner } from '../render/minimap.js'; // já módulo próprio (Estágio 4, Tier 1) — importado direto
14
+ // ===================== PURO (sem DOM/store — project node) =====================
15
+ // Alvo físico ancorado no iPhone 16 a tela cheia (aresta longa 141,1mm do display 1179×2556 @460ppi →
16
+ // ~6,04 px CSS/mm). Ver o comentário original em game.js para a justificativa completa (WCAG 2.5.5 / GAG).
17
+ export const IPHONE16_LONG_MM = 141.1;
18
+ export const IPHONE16_LONG_PX = 852;
19
+ export const IPHONE16_PXMM = IPHONE16_LONG_PX / IPHONE16_LONG_MM; // ~6,04 px CSS/mm
20
+ /** mm→px CSS. No celular, ancora na aresta longa da JANELA (físico exato, retrato ou paisagem). No
21
+ * desktop/notebook, fixa no ratio do iPhone 16 (não cresce com a largura do monitor). Determinístico e puro:
22
+ * `mobile`/`viewW`/`viewH` chegam já resolvidos (isMobile()/innerWidth/innerHeight ficam no lado impuro). */
23
+ export function padPxPerMm(mobile, viewW, viewH) {
24
+ return mobile ? Math.max(viewW, viewH) / IPHONE16_LONG_MM : IPHONE16_PXMM;
25
+ }
26
+ /** Classifica um valor em mm na faixa mão-de-criança / intermediário / mão-de-adulto (usado nas etiquetas
27
+ * do painel de configuração de toque). */
28
+ export function padHandTag(v, lo, hi) {
29
+ return v <= lo ? 'crianca' : v >= hi ? 'adulto' : 'inter';
30
+ }
31
+ /** Toda a geometria em px a partir dos mm configurados + do fator px/mm — extraída de applyPadPhysical() para
32
+ * ficar pura/testável (dpr alto/baixo, tela pequena, valores extremos). applyPadPhysical() só chama isto e
33
+ * escreve o resultado em custom properties CSS + nos rótulos do painel. */
34
+ export function computePadPhysicalPx(mm, pxPerMm) {
35
+ const btnPx = mm.btn * pxPerMm, gapPx = mm.gap * pxPerMm;
36
+ const diamPx = btnPx + Math.SQRT2 * (btnPx + gapPx); // losango: folga de aresta = gap
37
+ const knobPx = mm.stick * pxPerMm, travelPx = mm.travel * pxPerMm;
38
+ const basePx = knobPx + 2 * travelPx + 16; // base do analógico = contato + curso
39
+ const armPx = mm.dpad * pxPerMm, armWPx = armPx * 0.8, spanPx = 2 * armPx + armWPx; // cruz: braço + largura(0,8×)
40
+ const stickTravelPx = travelPx, stickDeadPx = Math.max(6, travelPx * 0.4); // zona-morta ~40% do curso
41
+ return { btnPx, diamPx, knobPx, basePx, armPx, armWPx, spanPx, stickTravelPx, stickDeadPx };
42
+ }
43
+ /** Layout de rotulagem dos botões pelo id reportado do controle (heurística por substring/VID). */
44
+ export function padLayoutFromId(id) {
45
+ const s = (id || '').toLowerCase();
46
+ if (/dualshock|dualsense|playstation|054c/.test(s))
47
+ return 'sony';
48
+ if (/switch|nintendo|joy-con|057e/.test(s))
49
+ return 'nintendo';
50
+ if (/xbox|xinput|microsoft|045e/.test(s))
51
+ return 'microsoft';
52
+ return 'generic';
53
+ }
54
+ /** As 9 posições de toque remapeáveis (direcional×4, START, botões 0–3) — `lbl` é a chave i18n do rótulo. */
55
+ // `lbl` guarda a CHAVE i18n, não o texto: tabela de módulo resolvida no import congelaria o idioma no boot
56
+ // (ver a nota em input/devices). A seta e o nome do botão viajam DENTRO da tradução, porque em inglês o
57
+ // "(cima)" vira "(up)" e a seta fica onde está — é moldura inteira, não conteúdo interpolado.
58
+ export const TOUCH_SLOTS = [
59
+ { k: 'up', lbl: 'touch.slot.up' }, { k: 'down', lbl: 'touch.slot.down' },
60
+ { k: 'left', lbl: 'touch.slot.left' }, { k: 'right', lbl: 'touch.slot.right' },
61
+ { k: 'start', lbl: 'touch.slot.start' },
62
+ { k: 'b0', lbl: 'touch.slot.b0' }, { k: 'b1', lbl: 'touch.slot.b1' },
63
+ { k: 'b2', lbl: 'touch.slot.b2' }, { k: 'b3', lbl: 'touch.slot.b3' },
64
+ ];
65
+ /**
66
+ * As ações que o TRANSPORTE de toque consegue carregar.
67
+ *
68
+ * ⚠️ DEIXOU DE SER A LISTA DO `<select>`: as opções vêm agora de `ctx.acoesDoJogo()`, porque quem decide
69
+ * quais ações existem é o jogo. Esta lista continua a valer como o que ESTE transporte alcança — e é contra
70
+ * ela que o gate de `input/touch-bindings` confere se o despacho reconhece tudo o que se pode oferecer.
71
+ * As duas coisas eram a mesma por acidente enquanto só havia um jogo.
72
+ */
73
+ export const TOUCH_ACTS = ['left', 'right', 'up', 'down', 'action2', 'action1', 'action3', 'action4', 'start'];
74
+ /** Funde o mapa persistido (JSON solto do localStorage) sobre TOUCH_DEFAULT. Mantido IDÊNTICO ao original:
75
+ * NÃO valida chaves/valores contra TOUCH_SLOTS/TOUCH_ACTS — um JSON malformado com chaves/valores estranhos
76
+ * passa como está (só falha se não for um objeto). Ver a nota "bug surfaced" no retorno da extração. */
77
+ export function normalizeTouchMap(stored) {
78
+ // ⚠️ O GUARDADO PASSA PELO TRADUTOR ANTES DA FUSÃO, e a ordem é o que importa: `Object.assign` deixa o
79
+ // guardado VENCER o padrão, então um `b0: 'jump'` de antes do ADR-0086 sobrescreveria o `b0: 'action2'`
80
+ // correto e o botão da tela deixaria de fazer nada — sem erro nenhum. Aqui o nome da ação está no VALOR,
81
+ // não na chave, e por isso precisa de um tradutor próprio. Ver `input/vocabulary-migration.ts`.
82
+ const migrado = migrarMapaDeToque(stored && typeof stored === 'object' ? stored : null);
83
+ return Object.assign({}, TOUCH_DEFAULT, migrado || {});
84
+ }
85
+ /** UNUSED hoje — grep em game.js (`\bpadKind\b`) só acha a própria definição, nenhum chamador. Extraído porque
86
+ * a fronteira explicitamente listou; NÃO apagado (relatado, não conserto). Lê navigator.getGamepads() direto,
87
+ * no mesmo padrão de ui/settings-audio.ts / core/i18n.ts (acesso direto a navigator, sem DI) — mesmo assim,
88
+ * é semanticamente "ler o controle", território do agente de input/gamepad; ver a nota de fronteira no retorno. */
89
+ export function padKind() {
90
+ let kind = 'kb';
91
+ const pads = navigator.getGamepads ? navigator.getGamepads() : [];
92
+ for (const gp of pads) {
93
+ if (!gp)
94
+ continue;
95
+ kind = gp.mapping === 'standard' ? 'x' : 'd';
96
+ if (kind === 'x')
97
+ break;
98
+ }
99
+ return kind;
100
+ }
101
+ export function initTouch(ctx) {
102
+ let padDesign = ctx.store.get(KEYS.padDesign, 'generic') || 'generic';
103
+ let padBtnMm = ctx.store.getNum(KEYS.padBtnMm, 12.5);
104
+ let padGapMm = ctx.store.getNum(KEYS.padGapMm, 3);
105
+ let padStickMm = ctx.store.getNum(KEYS.padStickMm, 18);
106
+ let padTravelMm = ctx.store.getNum(KEYS.padTravelMm, 4.5);
107
+ let padDpadMm = ctx.store.getNum(KEYS.padDpadMm, 12);
108
+ let padDir = ctx.store.get(KEYS.padDir, 'stick') || 'stick';
109
+ let _stickTravelPx = 42, _stickDeadPx = 12; // valores de arranque; applyPadPhysical() atualiza de verdade
110
+ const touchMap = normalizeTouchMap(ctx.store.getJSON(KEYS.touchmap, null));
111
+ function renderTouchMap() {
112
+ const el = ctx.$('#touchmap-list');
113
+ if (!el)
114
+ return;
115
+ el.innerHTML = TOUCH_SLOTS.map((s) => `<div class="ctrl-row"><label for="tm-${s.k}">${t(s.lbl)}</label><select id="tm-${s.k}" class="vol" data-slot="${s.k}">` +
116
+ // ⚠️ A OPÇÃO NASCE VAZIA e o `rotulo` entra logo abaixo por `textContent` (issue #106). Ele é a PALAVRA
117
+ // do jogo — `acoesDoJogo()` sai do preset —, e um jogo vive noutro repositório (ADR-0083), então este
118
+ // texto não é revisto por esta árvore. `value="${acao}"` fica: `acao` é o nome ABSTRATO, e a engine
119
+ // enumera-o em `core/actions`; é dela e não do jogo.
120
+ ctx.acoesDoJogo().map(({ acao }) => `<option value="${acao}"${touchMap[s.k] === acao ? ' selected' : ''}></option>`).join('') +
121
+ `</select></div>`).join('');
122
+ // As palavras do jogo, por `textContent` — que escapa por construção. A ordem casa porque é a mesma lista.
123
+ for (const sel of el.querySelectorAll('select[data-slot]')) {
124
+ const palavras = ctx.acoesDoJogo();
125
+ for (let i = 0; i < sel.options.length && i < palavras.length; i++) {
126
+ sel.options[i].textContent = palavras[i].rotulo;
127
+ }
128
+ }
129
+ el.querySelectorAll('select[data-slot]').forEach((sel) => {
130
+ sel.addEventListener('change', () => {
131
+ const slot = sel.dataset.slot || '';
132
+ touchMap[slot] = sel.value;
133
+ ctx.store.setJSON(KEYS.touchmap, touchMap);
134
+ const label = sel.previousElementSibling ? sel.previousElementSibling.textContent : null;
135
+ // ⚠️ A palavra falada é a MESMA que a lida: sai da mesma lista que acabou de montar o `<option>`.
136
+ // Antes vinham de tabelas diferentes e nada obrigava as duas a concordar.
137
+ const escolhida = ctx.acoesDoJogo().find((x) => x.acao === sel.value);
138
+ ctx.srSay(t('sr.touch.slotSet', { slot: label || t('touch.slot.fallback'), acao: escolhida ? escolhida.rotulo : sel.value }));
139
+ });
140
+ });
141
+ }
142
+ function openTouchCfg() {
143
+ const ov = ctx.$('#touchcfg');
144
+ if (!ov)
145
+ return;
146
+ renderTouchMap();
147
+ ov.hidden = false;
148
+ ctx.frontOverlay(ov);
149
+ const f = ov.querySelector('select,button');
150
+ if (f)
151
+ f.focus();
152
+ }
153
+ function closeTouchCfg() {
154
+ const ov = ctx.$('#touchcfg');
155
+ if (!ov)
156
+ return;
157
+ ov.hidden = true;
158
+ const b = ctx.$('#opt-touchcfg');
159
+ if (b)
160
+ b.focus();
161
+ }
162
+ function hideTouchControls(_reason) {
163
+ const tc = ctx.$('#touch-controls');
164
+ if (tc && !tc.hidden)
165
+ tc.hidden = true;
166
+ ctx.$('body')?.classList.remove('touch-mode');
167
+ setMinimapCorner(false);
168
+ }
169
+ function showTouchControls() {
170
+ // MENU ativo = sem controle virtual: dá pra tocar direto nos botões da tela. QUEM decide é o jogo.
171
+ if (!ctx.padAllowed())
172
+ return;
173
+ const tc = ctx.$('#touch-controls');
174
+ if (tc)
175
+ tc.hidden = false;
176
+ ctx.$('body')?.classList.add('touch-mode');
177
+ setMinimapCorner(true);
178
+ ctx.onTouchControlsShown?.();
179
+ }
180
+ function applyDirStyle() {
181
+ const st = ctx.$('#touch-stick'), cr = ctx.$('#touch-cross');
182
+ if (st)
183
+ st.hidden = padDir === 'cross';
184
+ if (cr)
185
+ cr.hidden = padDir !== 'cross';
186
+ const sel = ctx.$('#pad-dir');
187
+ if (sel && sel.value !== padDir)
188
+ sel.value = padDir;
189
+ }
190
+ function applyPadPhysical() {
191
+ const vp = ctx.viewport();
192
+ const r = padPxPerMm(ctx.isMobile(), vp.w, vp.h);
193
+ const px = computePadPhysicalPx({ btn: padBtnMm, gap: padGapMm, stick: padStickMm, travel: padTravelMm, dpad: padDpadMm }, r);
194
+ _stickTravelPx = px.stickTravelPx;
195
+ _stickDeadPx = px.stickDeadPx;
196
+ const S = ctx.root.style;
197
+ S.setProperty('--pad-btn', px.btnPx.toFixed(1) + 'px');
198
+ S.setProperty('--pad-diam', px.diamPx.toFixed(1) + 'px');
199
+ S.setProperty('--stick-knob', px.knobPx.toFixed(1) + 'px');
200
+ S.setProperty('--stick-base', px.basePx.toFixed(1) + 'px');
201
+ S.setProperty('--dpad-arm-l', px.armPx.toFixed(1) + 'px');
202
+ S.setProperty('--dpad-arm-w', px.armWPx.toFixed(1) + 'px');
203
+ S.setProperty('--dpad-span', px.spanPx.toFixed(1) + 'px');
204
+ const fmt = (n) => n.toFixed(1).replace('.', ',');
205
+ const lbl = { crianca: 'mão de criança', adulto: 'mão de adulto', inter: 'intermediário' };
206
+ const upd = (valId, tagId, mm, lo, hi, slId) => {
207
+ const v = ctx.$(valId);
208
+ if (v)
209
+ v.textContent = fmt(mm) + ' mm';
210
+ const t = ctx.$(tagId);
211
+ if (t) {
212
+ const w = padHandTag(mm, lo, hi);
213
+ t.dataset.who = w;
214
+ t.textContent = lbl[w];
215
+ }
216
+ const s = ctx.$(slId);
217
+ if (s && parseFloat(s.value) !== mm)
218
+ s.value = String(mm);
219
+ };
220
+ upd('#pad-size-val', '#pad-size-tag', padBtnMm, 12.5, 13, '#pad-size');
221
+ upd('#pad-gap-val', '#pad-gap-tag', padGapMm, 3, 4.5, '#pad-gap');
222
+ upd('#pad-stick-val', '#pad-stick-tag', padStickMm, 17, 19, '#pad-stick');
223
+ upd('#pad-travel-val', '#pad-travel-tag', padTravelMm, 4, 5.5, '#pad-travel');
224
+ upd('#pad-dpad-val', '#pad-dpad-tag', padDpadMm, 12, 14, '#pad-dpad');
225
+ }
226
+ function setPadMm(o) {
227
+ if (o.btn != null)
228
+ padBtnMm = o.btn;
229
+ if (o.gap != null)
230
+ padGapMm = o.gap;
231
+ if (o.stick != null)
232
+ padStickMm = o.stick;
233
+ if (o.travel != null)
234
+ padTravelMm = o.travel;
235
+ if (o.dpad != null)
236
+ padDpadMm = o.dpad;
237
+ ctx.store.set(KEYS.padBtnMm, padBtnMm);
238
+ ctx.store.set(KEYS.padGapMm, padGapMm);
239
+ ctx.store.set(KEYS.padStickMm, padStickMm);
240
+ ctx.store.set(KEYS.padTravelMm, padTravelMm);
241
+ ctx.store.set(KEYS.padDpadMm, padDpadMm);
242
+ applyPadPhysical();
243
+ }
244
+ function applyPadDesign(d) {
245
+ if (d && PAD_DESIGNS[d])
246
+ padDesign = d;
247
+ ctx.store.set(KEYS.padDesign, padDesign);
248
+ const set = PAD_DESIGNS[padDesign] || PAD_DESIGNS.generic;
249
+ const diamond = ctx.$('#pad-diamond');
250
+ if (diamond) {
251
+ diamond.querySelectorAll('.pad-b').forEach((b) => {
252
+ const s = set[b.dataset.btn || ''];
253
+ if (s) {
254
+ b.textContent = s[0];
255
+ b.style.background = s[1];
256
+ }
257
+ });
258
+ }
259
+ ctx.onPadDesignApplied?.();
260
+ return padDesign;
261
+ }
262
+ // --- wiring: #touchcfg (painel "Botões de tela touch") + o botão que o abre/fecha (#opt-touchcfg no painel
263
+ // de Movimento) — todo elemento abaixo mora dentro (ou controla) o overlay #touchcfg. ---
264
+ const touchCfgBtn = ctx.$('#opt-touchcfg');
265
+ if (touchCfgBtn)
266
+ touchCfgBtn.addEventListener('click', openTouchCfg);
267
+ const touchCfgClose = ctx.$('#touchcfg-close');
268
+ if (touchCfgClose)
269
+ touchCfgClose.addEventListener('click', closeTouchCfg);
270
+ const padDirSel = ctx.$('#pad-dir');
271
+ if (padDirSel) {
272
+ padDirSel.value = padDir;
273
+ padDirSel.addEventListener('change', () => {
274
+ padDir = padDirSel.value;
275
+ ctx.store.set(KEYS.padDir, padDir);
276
+ applyDirStyle();
277
+ ctx.srSay(t('sr.touch.dirSet', { tipo: t(padDir === 'cross' ? 'touch.dir.cross' : 'touch.dir.stick') }));
278
+ });
279
+ }
280
+ const padSizeEl = ctx.$('#pad-size');
281
+ if (padSizeEl)
282
+ padSizeEl.addEventListener('input', () => setPadMm({ btn: parseFloat(padSizeEl.value) }));
283
+ const padGapEl = ctx.$('#pad-gap');
284
+ if (padGapEl)
285
+ padGapEl.addEventListener('input', () => setPadMm({ gap: parseFloat(padGapEl.value) }));
286
+ const padStickEl = ctx.$('#pad-stick');
287
+ if (padStickEl)
288
+ padStickEl.addEventListener('input', () => setPadMm({ stick: parseFloat(padStickEl.value) }));
289
+ const padTravelEl = ctx.$('#pad-travel');
290
+ if (padTravelEl)
291
+ padTravelEl.addEventListener('input', () => setPadMm({ travel: parseFloat(padTravelEl.value) }));
292
+ const padDpadEl = ctx.$('#pad-dpad');
293
+ if (padDpadEl)
294
+ padDpadEl.addEventListener('input', () => setPadMm({ dpad: parseFloat(padDpadEl.value) }));
295
+ const padPresetChild = ctx.$('#pad-preset-child');
296
+ if (padPresetChild)
297
+ padPresetChild.addEventListener('click', () => { setPadMm({ btn: 12, gap: 2.5, stick: 16.5, travel: 4, dpad: 11.5 }); ctx.srSay(t('sr.touch.presetChild')); });
298
+ const padPresetAdult = ctx.$('#pad-preset-adult');
299
+ if (padPresetAdult)
300
+ padPresetAdult.addEventListener('click', () => { setPadMm({ btn: 14, gap: 4.5, stick: 20, travel: 5.5, dpad: 14 }); ctx.srSay(t('sr.touch.presetAdult')); });
301
+ addEventListener('resize', applyPadPhysical); // recalcula os px ao girar/redimensionar; os mm são fixos
302
+ // estado inicial (equivalente aos `applyPadDesign(padDesign); applyPadPhysical(); applyDirStyle();` de boot no game.js)
303
+ applyPadDesign();
304
+ applyPadPhysical();
305
+ applyDirStyle();
306
+ return {
307
+ renderTouchMap, openTouchCfg, closeTouchCfg, hideTouchControls, showTouchControls,
308
+ applyDirStyle, applyPadPhysical, setPadMm, applyPadDesign,
309
+ getPadDesign: () => padDesign,
310
+ getTouchMap: () => touchMap,
311
+ getStickTravelPx: () => _stickTravelPx,
312
+ getStickDeadPx: () => _stickDeadPx,
313
+ };
314
+ }
@@ -0,0 +1,90 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { type Action } from '../core/actions.js';
3
+ /**
4
+ * QUANTOS LUGARES CADA TRANSPORTE REAL TEM, e de onde cada número vem. Estavam medidos e viviam só no teste;
5
+ * um número que só existe num teste não chega a criança nenhuma.
6
+ *
7
+ * · `gamepad: 17` — a Gamepad API «standard» declara dezassete botões. Não é escolha nossa.
8
+ * · `toque: 9` — `TOUCH_DEFAULT` (input/devices) nomeia nove slots, e são nove desde sempre.
9
+ * · `teclado: ACTIONS.length` — ⚠️ e este é o que precisou de decisão.
10
+ *
11
+ * ⚠️ O NÚMERO DO TECLADO NÃO É O NÚMERO DE TECLAS, e também não é o número de ligações do esquema atual. Não
12
+ * é o de teclas porque cem teclas não são cem lugares que uma criança encontra e lembra; não é o do esquema
13
+ * porque o esquema cresce, e o limite passaria a ser a configuração e não o aparelho.
14
+ *
15
+ * É `ACTIONS.length` porque o que limita um teclado, na prática deste produto, é O VOCABULÁRIO QUE A ENGINE
16
+ * SABE NOMEAR: um transporte que cobre todas as posições que existem não tem como ser o curto. Derivado e não
17
+ * escrito à mão de propósito — quando o vocabulário crescer (foi de nove para catorze em 2026-09-06), este
18
+ * número cresce com ele e nunca passa a mentir.
19
+ */
20
+ export declare const LUGARES: Readonly<{
21
+ gamepad: 17;
22
+ toque: 9;
23
+ teclado: 14;
24
+ }>;
25
+ /** Como se descobre que cada transporte está aqui AGORA. Injetado: nenhuma destas perguntas é pura. */
26
+ export interface Disponibilidade {
27
+ gamepad: () => boolean;
28
+ toque: () => boolean;
29
+ teclado: () => boolean;
30
+ }
31
+ /**
32
+ * Os três transportes que esta engine sabe oferecer hoje.
33
+ *
34
+ * ⚠️ A DETECÇÃO DO TECLADO É IMPRECISA E ISSO É ACEITÁVEL — mas só porque a tela que consome isto INFORMA em
35
+ * vez de RECUSAR. Não há API que diga «há um teclado físico ligado»; o que o projeto já usa para a mesma
36
+ * pergunta é `pointer:coarse && hover:none` (o `isCoarsePointer` do `game/session`), e um tablet COM teclado
37
+ * responde «toque» a isso. Se a tela barrasse, esse tablet levaria uma recusa falsa num jogo que ele joga.
38
+ * Como ela apenas diz o que falta e deixa continuar, o erro custa uma frase a mais e nunca uma porta fechada.
39
+ */
40
+ export declare function transportesPadrao(d: Disponibilidade): Transport[];
41
+ /** Um transporte, como ele se declara. Zero significado: quantos lugares, e se está aqui agora. */
42
+ export interface Transport {
43
+ /** Identificador estável, para preferências salvas. Nunca chega a uma pessoa. */
44
+ readonly id: string;
45
+ /** Quantos lugares ATRIBUÍVEIS. É o número que a aritmética da garantia usa. */
46
+ readonly slots: number;
47
+ /**
48
+ * Está disponível NESTE aparelho, AGORA? Função e não valor: um controle é ligado no meio da partida, e
49
+ * uma tela de toque aparece quando a criança gira o tablet.
50
+ */
51
+ readonly available: () => boolean;
52
+ }
53
+ /** Este transporte carrega este conjunto de ações? Aritmética, como o ADR-0079 §3 a descreve. */
54
+ export declare function carries(t: Transport, acoes: readonly Action[]): boolean;
55
+ /**
56
+ * Os transportes DISPONÍVEIS que carregam este conjunto.
57
+ *
58
+ * ⚠️ Disponibilidade e capacidade são conferidas nesta ordem de propósito: um transporte que caberia mas não
59
+ * está ligado não é resposta para uma criança que está à frente do aparelho agora.
60
+ */
61
+ export declare function carriedBy(lista: readonly Transport[], acoes: readonly Action[]): Transport[];
62
+ /**
63
+ * A garantia do ADR-0079 §3, como pergunta de sim ou não.
64
+ *
65
+ * ⚠️ CONJUNTO VAZIO DE AÇÕES DEVOLVE `false`, e não `true` por vacuidade. Um jogo que não declara ação
66
+ * nenhuma não é um jogo que qualquer transporte serve — é um jogo que ninguém consegue jogar, e
67
+ * `actionSetProblems` já o reprova. Devolver `true` aqui esconderia esse defeito atrás desta função.
68
+ */
69
+ export declare function reachable(lista: readonly Transport[], acoes: readonly Action[]): boolean;
70
+ /** O que a tela de seleção precisa dizer, e o que ela precisa saber para o dizer. */
71
+ export interface Alcance {
72
+ /** Verdadeiro = pelo menos um transporte disponível carrega o conjunto. */
73
+ readonly ok: boolean;
74
+ /** Quantas ações o jogo pede. */
75
+ readonly pedidas: number;
76
+ /** Os que serviriam se estivessem ligados — a informação acionável: «ligue um controle». */
77
+ readonly serviriamSeLigados: readonly string[];
78
+ /** Os disponíveis que NÃO cabem, com quantos lugares têm. Para a frase dizer o número. */
79
+ readonly curtos: readonly {
80
+ readonly id: string;
81
+ readonly slots: number;
82
+ }[];
83
+ }
84
+ /**
85
+ * Mede o alcance ANTES de a criança começar.
86
+ *
87
+ * ⚠️ Devolve DADO e não texto. A frase é da interface e tem de passar por `t()`; devolver português daqui
88
+ * repetiria o defeito que o `PADWIZ_STEPS` acabou de deixar de cometer.
89
+ */
90
+ export declare function alcance(lista: readonly Transport[], acoes: readonly Action[]): Alcance;