@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,227 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** Gênero gramatical do nome. `n` = neutro/indefinido (o pt-BR usa o masculino como default nesse caso). */
3
+ export type Gender = 'm' | 'f' | 'n';
4
+ /** Um nome que pode ser FALADO (leitor de tela) e SINALIZADO (Libras, que traduz o mesmo texto). */
5
+ export interface Speakable {
6
+ /** O nome, na língua da interface. Conteúdo de currículo NÃO se traduz — ver o pilar 3 do ADR-0010. */
7
+ readonly text: string;
8
+ readonly gender: Gender;
9
+ readonly plural: boolean;
10
+ }
11
+ /**
12
+ * COMO SE CONTA UM PASSO — e é isto que decide a métrica, que não é a mesma em todo tabuleiro.
13
+ *
14
+ * ⚠️ A grade tinha UMA métrica fixa (Chebyshev), com a razão escrita ao lado: *"numa grade, a diagonal custa um
15
+ * passo, e é assim que quem joga conta"*. Verdade onde a diagonal é legal. **Falsa num quebra-cabeça
16
+ * deslizante**, onde nada anda na diagonal: uma peça duas à direita e duas abaixo está a 2 por Chebyshev e a
17
+ * QUATRO movimentos de distância. O sonar sub-relatava até 2× — e sub-relatar distância a quem não vê a tela
18
+ * não é imprecisão, é mandar a criança para o lado errado com confiança.
19
+ *
20
+ * `grid` nunca foi UMA coisa. Quem declara a grade declara também como se anda nela.
21
+ */
22
+ export type MoveRule = 'orthogonal' | 'diagonal' | 'free';
23
+ /**
24
+ * EM QUE PALAVRAS SE DIZ UMA DIREÇÃO. Não é preferência de quem ouve: é propriedade do ESPAÇO do jogo.
25
+ *
26
+ * `compass` norte · sul · leste · oeste (+ zênite e nadir) — tabuleiro, top-down, mapa, 3D
27
+ * `clock` «às 2 horas», «às 10 horas» — PLATAFORMA 2D, vista lateral
28
+ *
29
+ * ⚠️ Num jogo de plataforma, norte e sul não querem dizer nada: a criança não está a olhar um mapa, está a
30
+ * olhar de lado. O relógio é o referencial que essa vista já usa, e dá 12 posições onde a rosa dá 8.
31
+ *
32
+ * ⚠️ E O RELÓGIO PRESSUPÕE LER RELÓGIO ANALÓGICO, num público que inclui alfabetização. Não é motivo para o
33
+ * recusar — é motivo para o rótulo FALADO ser testado com criança (issue #7) antes de se declarar bom.
34
+ */
35
+ export type Frame = 'compass' | 'clock';
36
+ export type Topology =
37
+ /**
38
+ * Grade discreta. A distância é em CÉLULAS, e a métrica sai de `move`.
39
+ * `size` é `[colunas, linhas]` ou `[colunas, linhas, camadas]`.
40
+ */
41
+ {
42
+ readonly kind: 'grid';
43
+ readonly size: readonly number[];
44
+ readonly move: MoveRule;
45
+ readonly frame: Frame;
46
+ }
47
+ /**
48
+ * Espaço contínuo. A distância é em UNIDADES do mundo; `unit` diz quanto vale um "passo" para quem narra.
49
+ * `size` é `[largura, altura]` ou `[largura, altura, profundidade]`.
50
+ */
51
+ | {
52
+ readonly kind: 'continuous';
53
+ readonly size: readonly number[];
54
+ readonly unit: number;
55
+ readonly move: MoveRule;
56
+ readonly frame: Frame;
57
+ }
58
+ /** Sem espaço: uma lista ORDENADA de alvos. A distância é a diferença de índice, e não há direção. */
59
+ | {
60
+ readonly kind: 'hotspots';
61
+ readonly order: readonly string[];
62
+ };
63
+ /**
64
+ * Uma posição, na métrica da topologia declarada. Em `hotspots`, `x` é o índice e `y` é ignorado.
65
+ *
66
+ * ⚠️ ASSIMETRIA DELIBERADA COM `size`, e vale dizer por quê. A EXTENSÃO é um vetor porque a dimensão varia e
67
+ * `size.length` é o único lugar onde ela mora — sem isso, "tem profundidade?" viraria `depth !== undefined`
68
+ * espalhado por cada consumidor. O PONTO tem eixos com nome porque é lido em código a toda hora: `alvo.at.x`
69
+ * diz o que é, `alvo.at[0]` obriga a lembrar. Dimensão é 2 ou 3 e a conformidade recusa o resto, o que fecha
70
+ * a assimetria: não há `size` que `Spot` não consiga representar.
71
+ */
72
+ export interface Spot {
73
+ readonly x: number;
74
+ readonly y: number;
75
+ readonly z?: number;
76
+ }
77
+ /** A dimensão que a topologia declara. `hotspots` não tem espaço, logo não tem dimensão. */
78
+ export declare function dimension(t: Topology): number;
79
+ export type Role = 'hazard' | 'climb' | 'water' | 'goal' | 'gate' | 'key' | 'structure' | 'free';
80
+ /**
81
+ * ⚠️ `zenith` E `nadir` ENTRARAM PORQUE `up`/`down` JÁ QUEREM DIZER DUAS COISAS. `ACTIONS` (core/actions) tem
82
+ * `up` e `down` como AÇÕES DE CONTROLE — o que a criança carrega —, e o eixo vertical do ESPAÇO é outra coisa
83
+ * inteiramente. Enquanto o mundo era plano a ambiguidade não custava nada; num espaço de três dimensões
84
+ * custaria o pior tipo de defeito, o que se lê certo e faz outra coisa. Palavras próprias, antes de o 3D chegar.
85
+ */
86
+ export type Heading = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | 'zenith' | 'nadir' | 'none';
87
+ /**
88
+ * PARA ONDE FICA UM PONTO VISTO DE OUTRO, já no referencial que a topologia declara.
89
+ *
90
+ * ⚠️ NÃO É O `heading` DO FOCO, e a diferença é o defeito que isto conserta. `Focus.heading` é para onde a
91
+ * criança está VIRADA; isto é para onde está o ALVO. O sonar precisa do segundo e calculava-o à mão a partir
92
+ * de `x` cru, com zona morta de ±4, dizendo `left`/`right`/`ahead` — três palavras onde o contrato tem oito, e
93
+ * MISTURANDO REFERENCIAIS: *esquerda/direita* é relativo à tela, *à frente* é relativo ao corpo. Uma criança
94
+ * cega que ouve as duas na mesma frase não tem como saber de qual origem cada uma fala.
95
+ */
96
+ export type Bearing = {
97
+ readonly kind: 'compass';
98
+ readonly heading: Heading;
99
+ } | {
100
+ readonly kind: 'clock';
101
+ readonly hour: number;
102
+ } | {
103
+ readonly kind: 'none';
104
+ };
105
+ export interface Focus {
106
+ /** Quem está com o foco. Um id do jogo — a engine não o interpreta, só o carrega. */
107
+ readonly id: string;
108
+ readonly at: Spot;
109
+ readonly heading: Heading;
110
+ }
111
+ export interface Objective {
112
+ /** O que se está juntando/resolvendo: "moedas", "palavras", "contas". */
113
+ readonly name: Speakable;
114
+ readonly have: number;
115
+ readonly need: number;
116
+ }
117
+ export type TickOwner = 'player' | 'clock';
118
+ export type Announcement = {
119
+ readonly kind: 'state';
120
+ readonly name: Speakable;
121
+ } | {
122
+ readonly kind: 'event';
123
+ readonly name: Speakable;
124
+ readonly urgent: boolean;
125
+ };
126
+ /**
127
+ * O que a criança vê como sendo o jogo.
128
+ *
129
+ * ⚠️ `none` NÃO É UM PADRÃO, É UMA ESCOLHA ESCRITA. Existe para atividade sem espaço — uma tela de pintura
130
+ * livre, autoria, um formulário —, onde não há «mundo» para simular nem alvo para sonar. O Dev nomeou o caso:
131
+ * *"atividades como paint não são exatamente jogos, mas podem ser feitas com a engine e sonar não vai
132
+ * funcionar muito bem"*.
133
+ *
134
+ * ⚠️ E O QUE ELE NÃO PODE SER É O QUE ACONTECE QUANDO ALGUÉM ESQUECE. Um jogo de DOM puro não é «um jogo onde
135
+ * empatia não faz sentido»: o xadrez às cegas é a prova empírica de que faz. `none` é para quem DECLARA que
136
+ * não tem espaço, e a ausência do campo é reprovada — hoje as duas coisas produzem o mesmo silêncio.
137
+ */
138
+ export type WorldScope =
139
+ /** O seletor do elemento que é o mundo. A engine aplica ali o que é do mundo, e só ali. */
140
+ {
141
+ readonly kind: 'element';
142
+ readonly selector: string;
143
+ }
144
+ /** Sem espaço. Empatia e sonar NÃO são oferecidos — e a tela de seleção diz isso antes de a criança começar. */
145
+ | {
146
+ readonly kind: 'none';
147
+ };
148
+ /**
149
+ * O que um jogo entrega à engine. `tick` é dado; TODO O RESTO é função, porque a resposta muda com a posição,
150
+ * o jogador e o instante.
151
+ *
152
+ * ⚠️ NÃO IMPORTE ISTO INTEIRO num módulo consumidor. Use `Pick<GameDeclaration, 'roleAt'>` e afins — a regra é
153
+ * a de `core/entity`, e é o que mantém os fixtures de teste pequenos.
154
+ */
155
+ export interface GameDeclaration {
156
+ /**
157
+ * A forma do espaço AGORA.
158
+ *
159
+ * ⚠️ ERA UM VALOR ATÉ 2026-09-06, e a assimetria já tinha sido remendada em dois lugares antes de alguém a
160
+ * nomear: a porta do sonar sempre pediu `topology: () => Topology` (`platform/audio-sonar.ts`), e
161
+ * `boot/create-game` fazia a ponte com `() => o.declaration.topology` — uma função que devolve uma
162
+ * constante. O `game-15puzzle` (3×3/4×4/5×5) precisou de um getter para caber no tipo, e um getter que
163
+ * satisfaz uma interface é COINCIDÊNCIA DO TypeScript, não contrato: nada avisava o próximo autor de que
164
+ * era o esperado, e `conformanceProblems` lia uma vez só — quem memorizasse a topologia ficava defasado
165
+ * em silêncio. Remendo que aparece duas vezes é o contrato a pedir para mudar. ADR-0084.
166
+ */
167
+ topology(): Topology;
168
+ /**
169
+ * QUAL ELEMENTO É O MUNDO. Ver o bloco 8 acima para o defeito que este campo conserta.
170
+ *
171
+ * ⚠️ OBRIGATÓRIO, e a obrigatoriedade é a decisão. A proposta original era um campo opcional com padrão;
172
+ * o Dev recusou, e a razão é o BLINDFOLD CHESS: xadrez às cegas existe, logo um jogo de DOM puro não é um
173
+ * jogo onde empatia não faz sentido — é um jogo onde ela exige mais de quem o programa. Um padrão deixaria
174
+ * o esquecimento passar como se fosse escolha.
175
+ */
176
+ world(): WorldScope;
177
+ readonly tick: TickOwner;
178
+ /** O papel do que está em `at`. É o campo 2, e é o que substitui `roleOf`. */
179
+ roleAt(at: Spot): Role;
180
+ /** Como se chama o que está em `at`. Sem isto não há leitor de tela nem Libras. */
181
+ nameAt(at: Spot): Speakable | null;
182
+ /** Quem tem o foco agora, e para onde aponta. `null` = ninguém (menu fechado, rodada não começou). */
183
+ focusOf(playerIndex: number): Focus | null;
184
+ /** O que a rodada pede deste jogador. */
185
+ objectiveOf(playerIndex: number): Objective;
186
+ /**
187
+ * ONDE estão os alvos ainda válidos deste jogador — a segunda metade do campo 5.
188
+ *
189
+ * É o que substitui o `getCoins()` do sonar: em vez de a engine varrer um array de moedas e filtrar por
190
+ * `taken`/`owner`, o jogo devolve os pontos que ainda contam para ESTE jogador. Um quiz devolve o índice da
191
+ * pergunta em aberto; uma plataforma devolve as moedas não coletadas; um Sokoban devolve as caixas fora do
192
+ * lugar. A engine só compara distâncias, e é por isso que o sonar passa a servir a qualquer gênero.
193
+ *
194
+ * Vazio é resposta legítima e significa "não há para onde apontar" — não é erro.
195
+ */
196
+ targetsOf(playerIndex: number): readonly Spot[];
197
+ }
198
+ /**
199
+ * Uma declaração é bem-formada? Devolve a lista de problemas — VAZIA quer dizer conforme.
200
+ *
201
+ * Existe porque um preset é uma promessa, e promessa sem verificação é comentário. O ADR-0030 diz que um
202
+ * pacote de gênero "ou satisfaz os sete campos ou não satisfaz"; isto é o "ou não".
203
+ *
204
+ * Confere FORMA, não verdade: que a topologia tenha medida positiva, que as funções existam, que o objetivo
205
+ * não peça um alvo impossível. Não tem como conferir se `roleAt` devolve o papel CERTO — isso é o teste do
206
+ * preset, não deste módulo.
207
+ */
208
+ export declare function conformanceProblems(d: Partial<GameDeclaration> | null | undefined): string[];
209
+ /** Um nome falável bem-formado? Texto vazio é o defeito silencioso: o leitor de tela simplesmente cala. */
210
+ export declare function speakableProblems(s: Speakable | null | undefined): string[];
211
+ /**
212
+ * Distância entre dois pontos NA MÉTRICA declarada — a que o jogo declarou em `move`, e em quantas dimensões
213
+ * ele declarou em `size`. É o que o sonar precisa e antes calculava em pixels.
214
+ *
215
+ * ⚠️ A GRADE ERA SEMPRE CHEBYSHEV, e isso custava até 2× de erro num quebra-cabeça deslizante. As três regras
216
+ * não são gosto: são o que "um passo" significa em cada jogo, e o sonar fala em passos.
217
+ */
218
+ export declare function distance(t: Topology, a: Spot, b: Spot): number;
219
+ /**
220
+ * PARA ONDE FICA `to` VISTO DE `from`, já nas palavras que a topologia declarou.
221
+ *
222
+ * ⚠️ DOIS EIXOS COM CONVENÇÕES DIFERENTES, e o silêncio sobre isto seria o defeito. `y` CRESCE PARA BAIXO —
223
+ * é a coordenada da tela, herdada da canvas e de todo o código que já existe, não uma escolha desta função.
224
+ * `z` CRESCE PARA CIMA, e essa é escolha: nada a força, e num espaço de três dimensões «zênite» só pode
225
+ * querer dizer o lado para onde a criança olharia levantando a cabeça. Um jogo 3D tem de saber os dois.
226
+ */
227
+ export declare function bearing(t: Topology, from: Spot, to: Spot): Bearing;
@@ -0,0 +1,184 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // core/contract — OS SETE CAMPOS. A interface que o ADR-0030 escolheu como o eixo da engine.
3
+ //
4
+ // ========================= O QUE ISTO É, E O QUE NÃO É =========================
5
+ // Não é um framework nem uma classe-base. É a ÚNICA coisa que a pilha de acessibilidade sabe sobre um jogo.
6
+ // O ADR-0027 mediu que as três funções que MAIS pareciam genéricas eram as três mais amarradas à plataforma —
7
+ // `roleOf` era uma tabela de tiles, `caneProbe` usava `facing` e `BOX.w`, o sonar definia alvo como "moeda não
8
+ // coletada deste jogador". Nenhuma delas estava errada; todas estavam ADIVINHANDO o jogo em vez de perguntar.
9
+ //
10
+ // Estes sete campos são as perguntas. Um jogo que as responda ganha sonar, alto contraste, varredura, leitor de
11
+ // tela e Libras sem escrever uma linha deles — que é o produto, e não "mais uma engine 2D" (ADR-0027).
12
+ //
13
+ // ========================= PURO, E DE PROPÓSITO =========================
14
+ // Só TIPOS e funções puras. Zero dependências, zero I/O no import: é módulo-FOLHA, importável dos dois lados da
15
+ // fronteira e do project `node`. Um contrato que precisasse de PIXI já teria escolhido o gênero.
16
+ //
17
+ // ========================= COMO SE USA: FATIAS, NÃO O OBJETO GORDO =========================
18
+ // Vale aqui a mesma regra de `core/entity`: quem consome declara a FATIA MÍNIMA de que precisa, com `Pick`, em
19
+ // vez de receber a declaração inteira. O alto contraste quer `Pick<GameDeclaration, 'roleAt'>` e nada mais; o
20
+ // HUD quer `'objective'`. Trocar 23 visões estreitas por uma interface gorda destruiria a testabilidade que o
21
+ // projeto tem hoje — todo fixture passaria a inventar campos que o módulo não usa.
22
+ //
23
+ // ========================= O QUE TEM EVIDÊNCIA E O QUE AINDA NÃO TEM =========================
24
+ // Cinco campos nasceram de um consumidor REAL que hoje adivinha (marcados abaixo com "hoje quem adivinha").
25
+ // DOIS não têm consumidor no código ainda — `tick` e `Announcement` — e estão aqui porque o ADR-0027 os nomeia
26
+ // como o que decide varredura e WCAG 2.2.1. Estão marcados. Enquanto nenhum módulo os ler, são hipótese, e o
27
+ // ADR-0030 registra que "o contrato basta" só vira resultado quando DOIS presets existirem.
28
+ /** A dimensão que a topologia declara. `hotspots` não tem espaço, logo não tem dimensão. */
29
+ export function dimension(t) { return t.kind === 'hotspots' ? 0 : t.size.length; }
30
+ /** O eixo `i` de um ponto, para quem percorre dimensões em vez de as nomear. */
31
+ function eixo(s, i) { return i === 0 ? s.x : i === 1 ? s.y : (s.z ?? 0); }
32
+ /* ===================== CONFORMIDADE ===================== */
33
+ /**
34
+ * Uma declaração é bem-formada? Devolve a lista de problemas — VAZIA quer dizer conforme.
35
+ *
36
+ * Existe porque um preset é uma promessa, e promessa sem verificação é comentário. O ADR-0030 diz que um
37
+ * pacote de gênero "ou satisfaz os sete campos ou não satisfaz"; isto é o "ou não".
38
+ *
39
+ * Confere FORMA, não verdade: que a topologia tenha medida positiva, que as funções existam, que o objetivo
40
+ * não peça um alvo impossível. Não tem como conferir se `roleAt` devolve o papel CERTO — isso é o teste do
41
+ * preset, não deste módulo.
42
+ */
43
+ export function conformanceProblems(d) {
44
+ const p = [];
45
+ if (!d)
46
+ return ['declaration missing'];
47
+ // ⚠️ DUAS FALHAS DIFERENTES, E ELAS PRECISAM DE DUAS MENSAGENS. `topology` ausente é um campo que ninguém
48
+ // escreveu; `topology` que não é função é o campo escrito à moda antiga — um VALOR, que passava no
49
+ // TypeScript de quem não recompilou e morreria em produção com "topology is not a function". Dizer só
50
+ // "ausente" mandaria o autor procurar um campo que está lá, à vista.
51
+ const t = typeof d.topology === 'function' ? d.topology() : undefined;
52
+ if (d.topology === undefined || d.topology === null)
53
+ p.push('topology: missing');
54
+ else if (typeof d.topology !== 'function')
55
+ p.push('topology: must be a FUNCTION (it was a value until ADR-0084)');
56
+ else if (!t)
57
+ p.push('topology: the function returned nothing');
58
+ else if (t.kind === 'grid' || t.kind === 'continuous') {
59
+ // ⚠️ A DIMENSÃO É `size.length`, e é por isso que ela é conferida ANTES de tudo: um `size` vazio ou de
60
+ // quatro entradas não é uma medida ruim, é um espaço que `Spot` não sabe representar — e o erro apareceria
61
+ // longe daqui, como um eixo simplesmente ignorado.
62
+ if (!Array.isArray(t.size) || t.size.length < 2 || t.size.length > 3) {
63
+ p.push('topology.size: must be [w, h] or [w, h, d] - dimension is 2 or 3, and it is size.length');
64
+ }
65
+ else if (!t.size.every((n) => n > 0)) {
66
+ p.push('topology.size: every extent must be positive');
67
+ }
68
+ // `move` é o que decide a métrica. Ausente, a distância seria adivinhada — e adivinhar Chebyshev num
69
+ // quebra-cabeça deslizante sub-relata até 2×, que foi o achado §3 do ADR-0080.
70
+ if (t.move !== 'orthogonal' && t.move !== 'diagonal' && t.move !== 'free') {
71
+ p.push('topology.move: must be "orthogonal" (L1), "diagonal" (L8/Chebyshev) or "free" (L2) - it is the metric the sonar counts in');
72
+ }
73
+ // `frame` é em que PALAVRAS a direção é dita. Sem ele a engine escolheria pela criança, e num jogo de
74
+ // plataforma escolheria mal: norte e sul não querem dizer nada numa vista lateral.
75
+ if (t.frame !== 'compass' && t.frame !== 'clock') {
76
+ p.push('topology.frame: must be "compass" (board, top-down, map, 3D) or "clock" (2D side view)');
77
+ }
78
+ // `unit` é o que dá MÉTRICA a um espaço contínuo: sem ela, "a dois passos" não tem como ser dito.
79
+ if (t.kind === 'continuous' && !(t.unit > 0)) {
80
+ p.push('topology.continuous: unit must be positive (it is the metric the narration counts in)');
81
+ }
82
+ }
83
+ else if (t.kind === 'hotspots') {
84
+ if (!t.order?.length)
85
+ p.push('topology.hotspots: order is empty - there is nowhere to navigate');
86
+ else if (new Set(t.order).size !== t.order.length)
87
+ p.push('topology.hotspots: order has a repeated id');
88
+ }
89
+ else
90
+ p.push('topology: unknown kind');
91
+ // ⚠️ TRÊS FALHAS DISTINTAS, e a terceira é a que este campo existe para tornar impossível: um jogo cujo
92
+ // mundo NÃO foi declarado. Antes deste campo, esquecer e escolher «não tenho espaço» produziam o mesmo
93
+ // silêncio — e o silêncio era resolvido pela engine a adivinhar que o mundo é a canvas.
94
+ if (d.world === undefined || d.world === null) {
95
+ p.push('world: missing - declare the element that IS the game, or {kind:"none"} if it has no space');
96
+ }
97
+ else if (typeof d.world !== 'function') {
98
+ p.push('world: must be a FUNCTION');
99
+ }
100
+ else {
101
+ const w = d.world();
102
+ if (!w)
103
+ p.push('world: the function returned nothing');
104
+ else if (w.kind === 'element') {
105
+ if (!w.selector || !w.selector.trim())
106
+ p.push('world: kind "element" needs a non-empty selector');
107
+ }
108
+ else if (w.kind !== 'none')
109
+ p.push('world: unknown kind');
110
+ }
111
+ if (d.tick !== 'player' && d.tick !== 'clock')
112
+ p.push('tick: must be "player" or "clock"');
113
+ for (const f of ['roleAt', 'nameAt', 'focusOf', 'objectiveOf', 'targetsOf']) {
114
+ if (typeof d[f] !== 'function')
115
+ p.push(`${f}: missing`);
116
+ }
117
+ return p;
118
+ }
119
+ /** Um nome falável bem-formado? Texto vazio é o defeito silencioso: o leitor de tela simplesmente cala. */
120
+ export function speakableProblems(s) {
121
+ if (!s)
122
+ return ['name missing'];
123
+ const p = [];
124
+ if (!s.text.trim())
125
+ p.push('text: empty - the screen reader would fall silent');
126
+ if (s.gender !== 'm' && s.gender !== 'f' && s.gender !== 'n')
127
+ p.push('gender: must be m, f or n');
128
+ if (typeof s.plural !== 'boolean')
129
+ p.push('plural: must be a boolean');
130
+ return p;
131
+ }
132
+ /**
133
+ * Distância entre dois pontos NA MÉTRICA declarada — a que o jogo declarou em `move`, e em quantas dimensões
134
+ * ele declarou em `size`. É o que o sonar precisa e antes calculava em pixels.
135
+ *
136
+ * ⚠️ A GRADE ERA SEMPRE CHEBYSHEV, e isso custava até 2× de erro num quebra-cabeça deslizante. As três regras
137
+ * não são gosto: são o que "um passo" significa em cada jogo, e o sonar fala em passos.
138
+ */
139
+ export function distance(t, a, b) {
140
+ // Lista: a distância é quantos itens separam um do outro. Não há eixo, logo não há regra de movimento.
141
+ if (t.kind === 'hotspots')
142
+ return Math.abs(a.x - b.x);
143
+ const d = [];
144
+ for (let i = 0; i < t.size.length; i++)
145
+ d.push(Math.abs(eixo(a, i) - eixo(b, i)));
146
+ const bruta = t.move === 'orthogonal' ? d.reduce((s, v) => s + v, 0) // L¹: cada eixo custa por si
147
+ : t.move === 'diagonal' ? Math.max(...d) // L∞: a diagonal custa um passo
148
+ : Math.hypot(...d); // L²: a reta entre os dois
149
+ // Contínuo: dividida pela unidade — o resultado é "quantos passos", não "quantos pixels".
150
+ return t.kind === 'continuous' ? bruta / t.unit : bruta;
151
+ }
152
+ /**
153
+ * PARA ONDE FICA `to` VISTO DE `from`, já nas palavras que a topologia declarou.
154
+ *
155
+ * ⚠️ DOIS EIXOS COM CONVENÇÕES DIFERENTES, e o silêncio sobre isto seria o defeito. `y` CRESCE PARA BAIXO —
156
+ * é a coordenada da tela, herdada da canvas e de todo o código que já existe, não uma escolha desta função.
157
+ * `z` CRESCE PARA CIMA, e essa é escolha: nada a força, e num espaço de três dimensões «zênite» só pode
158
+ * querer dizer o lado para onde a criança olharia levantando a cabeça. Um jogo 3D tem de saber os dois.
159
+ */
160
+ export function bearing(t, from, to) {
161
+ if (t.kind === 'hotspots')
162
+ return { kind: 'none' };
163
+ const dx = to.x - from.x;
164
+ const dy = to.y - from.y;
165
+ const dz = t.size.length > 2 ? (to.z ?? 0) - (from.z ?? 0) : 0;
166
+ const plano = Math.hypot(dx, dy);
167
+ // O eixo vertical do ESPAÇO ganha quando domina o plano — e ganha em palavras próprias, porque `up`/`down`
168
+ // já são AÇÕES em `core/actions`. Ver o comentário de `Heading`.
169
+ if (Math.abs(dz) > plano)
170
+ return { kind: 'compass', heading: dz > 0 ? 'zenith' : 'nadir' };
171
+ if (plano === 0)
172
+ return { kind: 'none' }; // mesmo lugar: não há direção que dizer, e inventar uma seria mentir
173
+ // `-dy` porque o norte é para CIMA e `y` cresce para baixo. Sem esta troca a rosa sai invertida, e o teste
174
+ // que a apanharia é o único que precisa de existir aqui.
175
+ const ang = Math.atan2(-dy, dx); // 0 = leste, cresce no sentido anti-horário
176
+ if (t.frame === 'clock') {
177
+ // 12 horas é para CIMA e os ponteiros andam no sentido horário — daí `90 - graus`, e não `graus`.
178
+ const horario = ((90 - (ang * 180) / Math.PI) % 360 + 360) % 360;
179
+ const h = Math.round(horario / 30) % 12;
180
+ return { kind: 'clock', hour: h === 0 ? 12 : h };
181
+ }
182
+ const ROSA = ['e', 'ne', 'n', 'nw', 'w', 'sw', 's', 'se'];
183
+ return { kind: 'compass', heading: ROSA[(Math.round(ang / (Math.PI / 4)) % 8 + 8) % 8] };
184
+ }
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * `document.querySelector`, na forma em que os módulos o recebem por INJEÇÃO — nunca importando `document`.
4
+ *
5
+ * O padrão é `HTMLElement` e não `Element` porque é o que os chamadores usam: `.hidden`, `.textContent`,
6
+ * `.dataset`, `.focus()`. Quem precisa de outra coisa instancia — há um `$<SVGElement>` na árvore, e é por
7
+ * causa dele que a RESTRIÇÃO continua sendo `Element`.
8
+ */
9
+ export type DomQuery = <T extends Element = HTMLElement>(sel: string) => T | null;
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // core/dom-query — O SELETOR DE DOM INJETADO, declarado UMA VEZ.
3
+ //
4
+ // ========================= POR QUE ESTE ARQUIVO EXISTE =========================
5
+ // A mesma declaração de `DomQuery` — uma seta genérica sobre `Element`, devolvendo `T | null` — estava
6
+ // escrita em DEZESSEIS módulos: `game/coin-spawning`, `game/quiz`, `input/gamepad`, `input/keydown`,
7
+ // `input/touch`, `input/touch-bindings`, `ui/activities-menu`, `ui/hud`, `ui/map-hub`, `ui/menu-nav`,
8
+ // `ui/settings-audio`, `ui/settings-controls`, `ui/settings-motor`, `ui/settings-typo`, `ui/shell` e
9
+ // `ui/title`.
10
+ //
11
+ // Dezesseis cópias de uma verdade, que é o defeito do ADR-0039 um degrau acima: lá era um CAMPO descrito
12
+ // duas vezes, aqui é um TIPO descrito dezesseis.
13
+ //
14
+ // E, como sempre acontece com cópia, elas divergiram. O `game/session` declarava a sua NÃO-genérica,
15
+ // devolvendo um `SessionEl` estrutural. Uma função genérica atribuída a uma assinatura não-genérica é
16
+ // instanciada pela RESTRIÇÃO e não pelo padrão: o `$` real, que é `<T extends Element = HTMLElement>`,
17
+ // virava `Element` — e `Element` não tem `hidden`. Dois erros de tipo no `main.ts` nasciam daí, e nenhum
18
+ // deles falava de seletor.
19
+ //
20
+ // ========================= POR QUE EM `core/` =========================
21
+ // Porque é a camada que todos podem importar sem inverter nada — `game/`, `input/`, `render/` e `ui/`. E
22
+ // não custa dependência nenhuma: o arquivo é SÓ TIPO, `import type` é apagado na compilação, e `Element` é
23
+ // global de `lib.dom`, ligado no tsconfig. Nada aqui roda, então a propriedade de `core/` ser testável sem
24
+ // navegador continua intacta.
25
+ export {};
@@ -0,0 +1,150 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** Ação → lista de códigos físicos (`KeyA`, `ArrowLeft`…). Fonte única: estava triplicado em input/. */
3
+ export type KeyScheme = Record<string, string[]>;
4
+ /** Lados da ventosa-aranha: direita, esquerda, teto, chão. */
5
+ export type ClingSide = 'R' | 'L' | 'U' | 'D';
6
+ /**
7
+ * O sprite do jogador visto pelo jogo: posição, opacidade, escala e textura. Mesma forma que render/draw
8
+ * declara como `PlayerSprite` — é um PIXI.Sprite reduzido ao que o jogo escreve nele, de propósito, para que
9
+ * a lógica rode no project `node` com um sprite de mentira.
10
+ */
11
+ export interface PlayerSpriteLike {
12
+ x: number;
13
+ y: number;
14
+ alpha: number;
15
+ visible: boolean;
16
+ scale: {
17
+ set(x: number, y: number): void;
18
+ };
19
+ texture: unknown;
20
+ }
21
+ /**
22
+ * O JOGADOR. Os campos até `_swapSonar` são os que `game/player.makePlayer` cria — todos obrigatórios, porque
23
+ * a fábrica sempre os escreve. Depois vêm os que outros subsistemas ACRESCENTAM em tempo de execução, e esses
24
+ * são opcionais porque um jogador recém-criado genuinamente não os tem.
25
+ *
26
+ * A distinção não é cosmética: hoje `physics` exige `elevTarget` como opcional e `gamepad` exige `quit` como
27
+ * obrigatório, e as duas leem o MESMO objeto. Escrever de que lado da linha cada campo cai é metade do valor
28
+ * deste arquivo.
29
+ */
30
+ export interface Player {
31
+ i: number;
32
+ x: number;
33
+ y: number;
34
+ vx: number;
35
+ vy: number;
36
+ onGround: boolean;
37
+ onLadder: boolean;
38
+ inWater: boolean;
39
+ clinging: boolean;
40
+ /** Face à qual a ventosa-aranha está grudada; `null` quando não está grudado. */
41
+ clingN: ClingSide | null;
42
+ flying: boolean;
43
+ airTime: number;
44
+ facing: number;
45
+ anim: number;
46
+ walkAnim: number;
47
+ climbFrame: number;
48
+ idleNow: boolean;
49
+ idleTime: number;
50
+ groundIdle: number;
51
+ flavor: number;
52
+ flavorT: number;
53
+ /** Quadro corrente. `Frame` é `unknown` em render/player-anim — a textura é opaca para o jogo. */
54
+ _tx: unknown;
55
+ jumpBuffer: number;
56
+ jumpChain: number;
57
+ waterStroke: number;
58
+ walkDir: number;
59
+ hurtTimer: number;
60
+ jumpEdge: boolean;
61
+ runEdge: boolean;
62
+ swapEdge: boolean;
63
+ specialEdge: boolean;
64
+ leftEdge: boolean;
65
+ rightEdge: boolean;
66
+ collected: number;
67
+ owned: string[];
68
+ activePower: string;
69
+ hasKey: boolean;
70
+ /**
71
+ * Esquema de teclas. `makePlayer` nasce com `null` e `assignControls` preenche no boot — por isso o `| null`.
72
+ * `game/physics` e `input/keyboard-runtime` declaram este campo como NÃO-nulo, o que é verdade no instante
73
+ * em que eles rodam mas não é verdade no tipo. Divergência REAL, deixada visível aqui de propósito em vez de
74
+ * apagada: quem estreitar precisa fazê-lo explicitamente, no ponto onde sabe que já foi atribuído.
75
+ */
76
+ ctrl: KeyScheme | null;
77
+ /** Índice do gamepad, ou -1 quando o jogador não tem controle físico. */
78
+ pad: number;
79
+ viz: string;
80
+ easy: boolean;
81
+ toggleMove: boolean;
82
+ /**
83
+ * A ALTERNÂNCIA DO BOTÃO DE CORRER (pedido do Dev): correr vira ESTADO em vez de "segurar".
84
+ *
85
+ * Irmã de `toggleMove` e pelo mesmo motivo — quem não consegue manter pressionado andava sem segurar e
86
+ * continuava sem conseguir CORRER. Automática no controle de toque. Ver `game/run-toggle`.
87
+ */
88
+ toggleRun: boolean;
89
+ /** A trava da corrida: com `toggleRun`, é ela que diz se está correndo agora. Vida de RODADA. */
90
+ runLatch: boolean;
91
+ rmWalk: boolean;
92
+ rmBreath: boolean;
93
+ rmFlavor: boolean;
94
+ stepT: number;
95
+ guardT: number;
96
+ _swapDown: boolean;
97
+ _swapT: number;
98
+ _swapSonar: boolean;
99
+ sprite: PlayerSpriteLike | null;
100
+ /** ui/shell, input/gamepad, ui/hud: o jogador pediu para sair. */
101
+ quit?: boolean;
102
+ /** game/session: entrou no meio da partida e aguarda a próxima rodada. */
103
+ waiting?: boolean;
104
+ /** game/level-geometry, game/physics: andar de destino do elevador em que está. */
105
+ elevTarget?: number | null;
106
+ /** game/physics: velocidade de queda memorizada para o som de impacto. */
107
+ _fallV?: number;
108
+ /** game/physics: distância pisada desde a última batida de bengala. */
109
+ caneDist?: number;
110
+ /** game/session, render/player-anim: usa a bengala de corrida (o cego só corre com ela). */
111
+ runCane?: boolean;
112
+ /** render/fx → render/draw: fator de esmagamento (squash) e o temporizador que o decai (8 → 0).
113
+ * Os dois ANDAM JUNTOS — `stepSquash` escreve os dois e `drawPlayers` lê os dois. Declarar só o `sq`, como
114
+ * esta interface fazia até a conferência no navegador, é o erro de meia-dupla: o campo que sobra fica sem
115
+ * tipo nenhum e ninguém percebe, porque metade da regra continua compilando. */
116
+ sq?: number;
117
+ sqT?: number;
118
+ /** render/player-anim → render/draw: o jogador está andando / correndo. Derivados por quadro a partir da
119
+ * velocidade e do estado de contato; existem porque a BENGALA precisa saber (a de corrida só aparece
120
+ * correndo, e correr exige `runCane`). Escritos pelo render, nunca pela fábrica. */
121
+ walking?: boolean;
122
+ running?: boolean;
123
+ /** game/quiz: vitórias na atividade de alfabetização, zeradas a cada nova partida. */
124
+ alfWins?: number;
125
+ /** ui/settings-audio, ui/pause-icons: saída de áudio própria; `null`/ausente = compartilhada. */
126
+ audioSink?: string | null;
127
+ /** platform/audio-nav: temporizadores do sonar de parede e do guia. */
128
+ wnT?: number;
129
+ guideT?: number;
130
+ }
131
+ /**
132
+ * Um jogador DEPOIS de `assignControls` — o `ctrl` deixou de ser `null`.
133
+ *
134
+ * Existe para dar nome a uma invariante que hoje é assumida em silêncio: `game/physics` e
135
+ * `input/keyboard-runtime` declaram `ctrl` como não-nulo porque, no instante em que rodam, ele já foi
136
+ * atribuído. Isso é verdade e continua verdade — mas era uma afirmação escondida dentro de uma interface
137
+ * redigitada, onde ninguém a lia como afirmação. Aqui ela tem nome, e quem a usa está dizendo "eu só rodo
138
+ * depois do boot", que é uma frase verificável, em vez de simplesmente não mencionar o `null`.
139
+ */
140
+ export type ControlledPlayer = Player & {
141
+ ctrl: KeyScheme;
142
+ };
143
+ /**
144
+ * Atalho para as visões estreitas: `PlayerView<'x' | 'y'>` em vez de reescrever os campos.
145
+ *
146
+ * O ponto de não usar `Player` inteiro está no cabeçalho: o project `node` do Vitest monta jogadores de
147
+ * mentira com só os campos que o módulo lê, e uma interface gorda obrigaria todo fixture a inventar 52.
148
+ * Derivando, o módulo continua acoplado à sua fatia e o NOME e o TIPO de cada campo passam a ter fonte única.
149
+ */
150
+ export type PlayerView<K extends keyof Player> = Pick<Player, K>;
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // core/entity — O JOGADOR, escrito UMA vez. Passo 1 do ADR-0027, e o único que o ADR chama de pré-requisito
3
+ // de todos os outros. Não move nada e não muda nada em tempo de execução: `tsc --noEmit` é o teste inteiro.
4
+ //
5
+ // O PROBLEMA QUE ISTO RESOLVE. `core/state.players` é `unknown[]`. O tipo se perde exatamente na fronteira em
6
+ // que a entidade atravessa o programa, então cada módulo que precisa de um jogador escreveu à mão a sua
7
+ // própria visão estrutural e fez cast. São VINTE E TRÊS — o ADR-0027 estimou dez, contei uma a uma e são 23 —
8
+ // e elas já não concordam entre si:
9
+ //
10
+ // · `quiz` tem QUATRO formas: `unknown` (physics, session, touch), `Quiz | null` (quiz),
11
+ // `{ kind: string } | null` (gamepad) e `{ kind?: string } | null` (keydown). O `kind` é obrigatório numa
12
+ // e opcional na outra, para o MESMO objeto.
13
+ // · `easy` é obrigatório em physics, gamepad e settings-motor; opcional em session, keydown, touch-bindings
14
+ // e draw. Um jogador sem `easy` é aceito por metade do programa e rejeitado pela outra.
15
+ // · `jumpEdge`, idem: obrigatório em physics/gamepad/attract, opcional em session/keydown/touch-bindings.
16
+ // · `quit` é obrigatório em gamepad e hud, opcional em session. `toggleMove` é obrigatório em
17
+ // settings-motor, opcional em pause-icons. `viz` é obrigatório em draw, opcional em pause-icons.
18
+ // · `KeyScheme` (= `Record<string, string[]>`) está declarado três vezes: em input/keyboard-runtime,
19
+ // input/keyboard e input/keydown.
20
+ //
21
+ // Nada disso é hipótese sobre o futuro: é a mesma forma de falha que já custou uma escalada quebrada no modo
22
+ // Fácil, quando três cópias da tabela ação→borda divergiram. Uma cópia que ninguém obriga a concordar diverge.
23
+ //
24
+ // COMO ISTO É USADO — e por que os módulos NÃO devem importar `Player` inteiro. A propriedade que faz o
25
+ // projeto testável no project `node` do Vitest é que cada módulo declara a fatia MÍNIMA de que precisa e o
26
+ // teste monta um objeto de mentira com só aqueles campos. Trocar 23 visões estreitas por uma interface gorda
27
+ // destruiria isso: todo fixture de teste passaria a ter de inventar 52 campos.
28
+ //
29
+ // A saída é derivar em vez de redigitar: `Pick<Player, 'x' | 'y' | 'vx'>` em vez de reescrever os três campos.
30
+ // O módulo continua acoplado só à sua fatia; o teste continua montando só aquilo; mas o NOME e o TIPO de cada
31
+ // campo passam a ter uma fonte única, e renomear um campo quebra a compilação de todos os consumidores no
32
+ // mesmo instante, que é precisamente o que hoje não acontece.
33
+ //
34
+ // A FRONTEIRA DE CAMADA. `core/` não pode importar de `game/` nem de `render/` — seria inverter a dependência.
35
+ // Por isso os campos que apontam para outras camadas entram aqui pelo MÍNIMO ESTRUTURAL: `core` sabe que o
36
+ // jogador tem um quiz, não sabe o que é um quiz. Os tipos ricos (`Quiz` em game/quiz, `PlayerSprite` em
37
+ // render/draw) são atribuíveis a estes, e é o compilador que garante isso no ponto de uso.
38
+ export {};
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * Os CINCO caracteres, e cobre os dois contextos: elemento (`<` `>`) e ATRIBUTO (`"` `'`).
4
+ *
5
+ * O atributo é o que costuma faltar, e é o pior: dentro de um elemento uma aspa é inofensiva; dentro de
6
+ * `aria-label="…"` ela FECHA o atributo e o que vem a seguir vira atributo — um `onmouseover` sem precisar de
7
+ * uma única tag.
8
+ *
9
+ * ⚠️ O `&` É O PRIMEIRO, E A ORDEM É O DEFEITO CLÁSSICO DESTE AJUDANTE. Escapando-o por último, o `&` do
10
+ * `&lt;` que acabou de ser produzido é escapado outra vez e a tela mostra `&lt;` literal. É silencioso porque
11
+ * quem testa só com `<b>` nunca o vê: a saída ainda «parece» escapada.
12
+ *
13
+ * E ele escapa, não apaga. Apagar mudaria a palavra que a criança digitou, e uma atividade cujo texto muda
14
+ * sozinho é um defeito diferente e igualmente sério.
15
+ */
16
+ export declare function escaparHtml(s: string): string;