@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,52 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { RenderizarEm } from './port.js';
3
+ /** O que `pixiFilterFor` toca de um `PIXI.ColorMatrixFilter` — e só isso. */
4
+ interface ColorMatrixLike {
5
+ matrix: number[];
6
+ brightness(b: number, multiply: boolean): void;
7
+ contrast(amount: number, multiply: boolean): void;
8
+ }
9
+ interface ColorMatrixCtor {
10
+ new (): ColorMatrixLike;
11
+ }
12
+ /** `PIXI.BlurFilter` é opaco aqui: só se constrói com a força e se entrega para o `filters` do sprite. */
13
+ interface BlurCtor {
14
+ new (strength: number): unknown;
15
+ }
16
+ /** O sprite reaproveitado para carimbar o overlay de baixa visão — só a textura é trocada. */
17
+ interface TexturedSprite {
18
+ texture: unknown;
19
+ }
20
+ /** `app.renderer` — só a passada extra em render-texture (`clear:false` = por cima da cena já desenhada). */
21
+ export interface ViewportsCtx {
22
+ ColorMatrixFilter: ColorMatrixCtor | null | undefined;
23
+ BlurFilter: BlurCtor | null | undefined;
24
+ parallaxTexNormal: unknown[];
25
+ getTreeTexNormal: () => unknown;
26
+ getLvOverlaySpr: () => TexturedSprite;
27
+ renderizarEm: RenderizarEm;
28
+ getVpTex: () => unknown[];
29
+ cvdDefsHost: Element | null;
30
+ }
31
+ export interface ViewportsApi {
32
+ /** Camada de parallax `i` no `mode`: alto contraste recua o fundo (dessatura/escurece); resto = textura crua. */
33
+ parallaxTexFor(i: number, mode: string): unknown;
34
+ /** Decoração de fundo (árvore) no `mode`: mesma regra do parallax, cache próprio. */
35
+ treeTexFor(mode: string): unknown;
36
+ /** Quadro do jogador no `mode`: alto contraste ganha contorno escuro (salta do fundo recuado). */
37
+ playerVizTex(base: unknown, mode: string): unknown;
38
+ /** Filtro GPU do viewport para o `mode` (array de filtros, ou `null` quando o modo não usa filtro). */
39
+ pixiFilterFor(mode: string): unknown;
40
+ /** Canvas 320×180 do overlay de baixa visão (`haze`/`tunnel`/`macular`/`diabetic`). */
41
+ lvOverlayCanvas(lv: string): HTMLCanvasElement;
42
+ /** Textura do overlay de baixa visão (memoizada). `blur` não tem overlay — é filtro puro → `null`. */
43
+ lvOverlayTex(lv: string): unknown;
44
+ /** Carimba o overlay de baixa visão do jogador `i` DENTRO da render-texture do viewport, por cima da cena. */
45
+ renderVpOverlay(i: number, mode: string): void;
46
+ /** Invalida o cache de parallax recolorido — o cenário mudou, as texturas cruas são outras. */
47
+ clearParallaxTexCache(): void;
48
+ /** Invalida o cache de quadros do jogador com contorno (chamado por `rebakeDirect` em viz-setters). */
49
+ clearPlayerDirectCache(): void;
50
+ }
51
+ export declare function initViewports(ctx: ViewportsCtx): ViewportsApi;
52
+ export {};
@@ -0,0 +1,185 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/viewports — a FÁBRICA de imagem dos modos de visão acessível: como um MODO vira PIXEL.
3
+ //
4
+ // Par de render/viz-setters, que levou a POLÍTICA ("qual modo vale onde": por jogador, global, overlays, painéis).
5
+ // Aqui mora a outra metade — a produção do pixel em si, que a política consome:
6
+ // · `pixiFilterFor` — modo → filtro GPU do viewport (daltonismo, cegueira, baixa visão)
7
+ // · `parallaxTexFor` — camada de fundo i, recolorida (ou não) para o modo
8
+ // · `treeTexFor` — decoração de fundo, idem
9
+ // · `playerVizTex` — quadro do jogador com contorno escuro no alto contraste
10
+ // · `lvOverlay*` / `renderVpOverlay` — a névoa/túnel/mancha de baixa visão DENTRO da render-texture do viewport
11
+ // Os quatro primeiros entravam em viz-setters por injeção (`parallaxTexFor`/`treeTexFor`/`playerVizTex`/
12
+ // `pixiFilterFor` no `VizSettersCtx`) enquanto ainda moravam no game.js; a partir daqui quem os fornece é este
13
+ // módulo. Ver docs/5-Refactoring/plano-modularizacao-mapa.md (B2).
14
+ //
15
+ // A DUPLICAÇÃO QUE ESTA EXTRAÇÃO CUROU: as seis matrizes de daltonismo estavam escritas duas vezes — como
16
+ // `<feColorMatrix>` no app/index.html (caminho de tela única) e como `PIXI.ColorMatrixFilter` dentro de
17
+ // `pixiFilterFor` (caminho multi-tela). Saíram para render/cvd-matrices (folha, zero deps); `pixiFilterFor` lê
18
+ // de lá e `initViewports` GERA os `<filter>` do documento de lá também. O sintoma da divergência era silencioso
19
+ // e de acessibilidade: a mesma pessoa daltônica veria cores diferentes em tela única e em multi-tela.
20
+ //
21
+ // Fronteiras que este módulo NÃO reabre:
22
+ // · `parallaxTexNormal` e `treeTexNormal` (as texturas CRUAS, fonte do recolor) continuam nascendo no game.js
23
+ // — a primeira é preenchida por `setCenario` (carrega PNG por tema), a segunda pelo desenho procedural da
24
+ // árvore. Entram injetadas. `parallaxTexNormal` é `const` cujos ELEMENTOS `setCenario` troca in place → entra
25
+ // por VALOR (o array é o mesmo objeto); `vpTex` é `let` que `configureRender` REATRIBUI a cada troca de nº de
26
+ // telas → entra por GETTER.
27
+ // · `lvOverlaySpr` e o renderer são objetos PIXI criados no game.js e entram por interface ESTRUTURAL, para o
28
+ // módulo rodar no project `node` sem importar PIXI. Mesmo precedente de render/scene-sky e game/traffic.
29
+ // · `_lastSharedViz` NÃO é daqui (é o registro do render estático, fica no game.js) — ver o topo de viz-setters.
30
+ //
31
+ // POR QUE `getTreeTexNormal`/`getLvOverlaySpr` são GETTERS e não valores, sendo os dois `const` no game.js:
32
+ // não é reatribuição, é ORDEM DE BOOT. `clearParallaxTexCache` precisa existir dentro de `setCenario`, que o
33
+ // game.js chama no topo do módulo (na restauração do cenário salvo) — bem ANTES de `treeTexNormal` e
34
+ // `lvOverlaySpr` serem declarados. Com getters, `initViewports` pode ser chamado cedo o bastante para aquela
35
+ // chamada não cair em TDZ; com valores, o `const` ainda não inicializado derrubaria a restauração do tema
36
+ // dentro de um `try/catch` que apenas cai para 'cidade' — o jogador perderia o cenário escolhido em silêncio.
37
+ //
38
+ // SEM I/O no import: nada de makeCanvas/tex no corpo do módulo (precedente de render/textures). O único efeito
39
+ // de `initViewports` é gerar os `<filter>` SVG, que é justamente o ponto da cura da duplicação.
40
+ import { LOGICAL_W, LOGICAL_H } from '../core/constants.js';
41
+ import { makeCanvas, tex } from './canvas.js';
42
+ import { DIRECT_CFG, directBgTexture, directSpriteTexture } from './high-contrast.js';
43
+ import { VIZ_BY_KEY } from './viz-modes.js';
44
+ import { CVD_MATRIX, installCvdFilters } from './cvd-matrices.js';
45
+ export function initViewports(ctx) {
46
+ // Gera os seis <filter> de daltonismo do documento a partir de render/cvd-matrices — a MESMA fonte que
47
+ // pixiFilterFor lê logo abaixo. É por isso que tela única e multi-tela não podem mais divergir.
48
+ installCvdFilters(ctx.cvdDefsHost);
49
+ /* ===================== fundo: parallax e decoração ===================== */
50
+ const _parallaxTexHC = {}; // {mode: [tex,tex,tex]}
51
+ function parallaxTexFor(i, mode) {
52
+ if (DIRECT_CFG[mode]) {
53
+ (_parallaxTexHC[mode] = _parallaxTexHC[mode] || []);
54
+ if (!_parallaxTexHC[mode][i])
55
+ _parallaxTexHC[mode][i] = directBgTexture(ctx.parallaxTexNormal[i], mode);
56
+ return _parallaxTexHC[mode][i]; // direto: fundo recua
57
+ }
58
+ return ctx.parallaxTexNormal[i];
59
+ }
60
+ function clearParallaxTexCache() { for (const k in _parallaxTexHC)
61
+ delete _parallaxTexHC[k]; }
62
+ const _treeTexHC = {};
63
+ function treeTexFor(mode) {
64
+ if (DIRECT_CFG[mode]) {
65
+ if (!_treeTexHC[mode])
66
+ _treeTexHC[mode] = directBgTexture(ctx.getTreeTexNormal(), mode);
67
+ return _treeTexHC[mode]; // direto: decoração recua
68
+ }
69
+ return ctx.getTreeTexNormal();
70
+ }
71
+ /* ===================== frente: o jogador ===================== */
72
+ // {mode: Map<texturaBase, texturaComContorno>} — chaveado pela textura de ORIGEM porque o jogador troca de
73
+ // quadro toda frame; um Map por modo evita recontornar o mesmo quadro a cada volta da animação.
74
+ let _playerDirect = {};
75
+ function playerVizTex(base, mode) {
76
+ if (!base)
77
+ return base;
78
+ if (DIRECT_CFG[mode]) {
79
+ const mm = (_playerDirect[mode] = _playerDirect[mode] || new Map());
80
+ if (!mm.has(base))
81
+ mm.set(base, directSpriteTexture(base, mode));
82
+ return mm.get(base); // direto: player com contorno escuro → salta
83
+ }
84
+ return base;
85
+ }
86
+ function clearPlayerDirectCache() { _playerDirect = {}; }
87
+ /* ===================== o filtro do viewport ===================== */
88
+ // Cacheado por MODO e por identidade: o mesmo array de filtros volta sempre, então trocar de viewport não
89
+ // reconstrói o filtro (nem invalida o shader do PIXI). Guarda `null` também — `mode in cache` e não
90
+ // `cache[mode]` — para que modo sem filtro (normal, hc-*) não seja reprocessado a cada frame.
91
+ const _vpFilterCache = {};
92
+ function pixiFilterFor(mode) {
93
+ if (mode in _vpFilterCache)
94
+ return _vpFilterCache[mode];
95
+ let f = null;
96
+ const CM = ctx.ColorMatrixFilter, BL = ctx.BlurFilter;
97
+ const cvd = CVD_MATRIX[mode]; // fonte única: os MESMOS números que geram o <feColorMatrix> do HTML
98
+ if (cvd && CM) {
99
+ const c = new CM();
100
+ c.matrix = cvd.slice();
101
+ f = [c];
102
+ } // slice: o filtro não fica com o array do módulo
103
+ else if (mode === 'blind' && CM) {
104
+ const c = new CM();
105
+ c.brightness(0, false);
106
+ f = [c];
107
+ }
108
+ else if (mode === 'lv-blur' && BL) {
109
+ f = [new BL(5)];
110
+ }
111
+ else if (mode === 'lv-haze' && CM) {
112
+ const c = new CM();
113
+ c.contrast(-0.45, false);
114
+ c.brightness(1.12, true);
115
+ f = [c];
116
+ }
117
+ else if ((mode === 'lv-tunnel' || mode === 'lv-diabetic' || mode === 'lv-macular') && BL) {
118
+ f = [new BL(mode === 'lv-tunnel' ? 1.5 : 2)];
119
+ }
120
+ return _vpFilterCache[mode] = f;
121
+ }
122
+ /* ===================== baixa visão: o overlay como textura ===================== */
123
+ // O que o filtro GPU não sabe fazer: névoa de catarata, o túnel do glaucoma, a mancha central da degeneração
124
+ // macular e as manchas espalhadas da retinopatia. São desenho, não transformação de cor — vêm como textura.
125
+ function lvOverlayCanvas(lv) {
126
+ const W = LOGICAL_W, H = LOGICAL_H, cv = makeCanvas(W, H), c = cv.getContext('2d'), cx = W / 2, cy = H / 2;
127
+ if (lv === 'haze') {
128
+ c.fillStyle = 'rgba(244,246,250,0.42)';
129
+ c.fillRect(0, 0, W, H);
130
+ }
131
+ else if (lv === 'tunnel') {
132
+ const g = c.createRadialGradient(cx, cy, H * 0.12, cx, cy, H * 0.6);
133
+ g.addColorStop(0, 'rgba(0,0,0,0)');
134
+ g.addColorStop(.5, 'rgba(0,0,0,.55)');
135
+ g.addColorStop(1, 'rgba(0,0,0,.99)');
136
+ c.fillStyle = g;
137
+ c.fillRect(0, 0, W, H);
138
+ }
139
+ else if (lv === 'macular') {
140
+ const g = c.createRadialGradient(cx, cy, 2, cx, cy, H * 0.34);
141
+ g.addColorStop(0, 'rgba(12,12,15,.95)');
142
+ g.addColorStop(.55, 'rgba(12,12,15,.5)');
143
+ g.addColorStop(1, 'rgba(12,12,15,0)');
144
+ c.fillStyle = g;
145
+ c.fillRect(0, 0, W, H);
146
+ }
147
+ else if (lv === 'diabetic') {
148
+ for (const [fx, fy, fr] of [[.22, .3, .1], [.64, .22, .075], [.8, .58, .11], [.4, .7, .085], [.16, .8, .07], [.54, .48, .06]]) {
149
+ const x = fx * W, y = fy * H, r = fr * W, g = c.createRadialGradient(x, y, 1, x, y, r);
150
+ g.addColorStop(0, 'rgba(10,10,14,.95)');
151
+ g.addColorStop(.5, 'rgba(10,10,14,.7)');
152
+ g.addColorStop(1, 'rgba(10,10,14,0)');
153
+ c.fillStyle = g;
154
+ c.fillRect(x - r, y - r, 2 * r, 2 * r);
155
+ }
156
+ }
157
+ return cv;
158
+ }
159
+ const _lvOverlayTex = {};
160
+ function lvOverlayTex(lv) {
161
+ if (lv === 'blur')
162
+ return null; // desfoque é filtro puro, não tem o que carimbar
163
+ if (!_lvOverlayTex[lv])
164
+ _lvOverlayTex[lv] = tex(lvOverlayCanvas(lv));
165
+ return _lvOverlayTex[lv];
166
+ }
167
+ // Overlay DENTRO da render-texture (a bolinha indicadora do viewport fica por cima, FORA do filtro — é por
168
+ // isso que ela continua visível no modo cegueira; ver updateVpDots em render/viz-setters).
169
+ function renderVpOverlay(i, mode) {
170
+ const m = VIZ_BY_KEY[mode];
171
+ if (!m || m.kind !== 'lowvision')
172
+ return;
173
+ const t = lvOverlayTex(m.lv);
174
+ if (t) {
175
+ const spr = ctx.getLvOverlaySpr();
176
+ spr.texture = t;
177
+ ctx.renderizarEm(spr, ctx.getVpTex()[i], false);
178
+ }
179
+ }
180
+ return {
181
+ parallaxTexFor, treeTexFor, playerVizTex, pixiFilterFor,
182
+ lvOverlayCanvas, lvOverlayTex, renderVpOverlay,
183
+ clearParallaxTexCache, clearPlayerDirectCache,
184
+ };
185
+ }
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** O eixo do CONTRASTE. `padrao` não é ausência de tema: é o tema desenhado do jogo. */
3
+ export type Tema = 'padrao' | 'hc3' | 'hc45' | 'hc7';
4
+ /** O eixo da CORREÇÃO DE COR. `tricro` = visão tricromática, e é um nome, não uma ausência. */
5
+ export type Correcao = 'tricro' | 'protan' | 'deuter' | 'tritan';
6
+ /** A simulação, que NÃO é eixo. `null` = nenhuma a correr. */
7
+ export type Simulacao = null | 'sim-protan' | 'sim-deuter' | 'sim-tritan' | 'lv-blur' | 'lv-haze' | 'lv-tunnel' | 'lv-macular' | 'lv-diabetic' | 'blind';
8
+ /** O estado visual de UM jogador. Substitui a string única de `p.viz`. */
9
+ export interface VisualState {
10
+ readonly tema: Tema;
11
+ readonly correcao: Correcao;
12
+ readonly simulacao: Simulacao;
13
+ }
14
+ export declare const PADRAO: VisualState;
15
+ /**
16
+ * Os dois eixos estão no padrão?
17
+ *
18
+ * ⚠️ É A PERGUNTA QUE LIBERA A SIMULAÇÃO, e é por isso que ela é uma função e não um booleano guardado:
19
+ * guardar o resultado deixaria as duas coisas divergirem, e a divergência aqui significa uma demonstração a
20
+ * correr por cima de uma adaptação — que ensina uma coisa falsa.
21
+ */
22
+ export declare function nosPadroes(v: VisualState): boolean;
23
+ /**
24
+ * Esta simulação pode correr AGORA? E se não, por quê?
25
+ *
26
+ * ⚠️ DEVOLVE O MOTIVO E NÃO SÓ `false`. O ADR-0076 exige que a recusa seja VISÍVEL e explicada — nunca
27
+ * silenciosamente removida, nunca aceita e depois ignorada. E o motivo é um FACTO SOBRE A DEMONSTRAÇÃO, não
28
+ * uma repreensão a quem escolheu: quem ligou o alto contraste ligou-o porque precisa.
29
+ */
30
+ export type MotivoIndisponivel = 'tema' | 'correcao' | 'ambos';
31
+ export declare function simulacaoIndisponivel(v: VisualState): MotivoIndisponivel | null;
32
+ /** A chave de modo DIRETO que este tema usa, ou `null` para o tema padrão. */
33
+ export declare function temaDireto(v: VisualState): string | null;
34
+ /**
35
+ * A chave de FILTRO CSS que este estado usa, ou `null`.
36
+ *
37
+ * ⚠️ SIMULAÇÃO VENCE CORREÇÃO AQUI, e não é uma regra de precedência escondida: as duas não podem coexistir
38
+ * porque `simulacaoIndisponivel` já as separa — uma simulação só corre com a correção no padrão. Este `??`
39
+ * é o que acontece quando alguém constrói um estado à mão que a interface não deixaria montar, e escolher a
40
+ * simulação é o menos errado dos dois: ela é a intenção mais recente e mais visível.
41
+ */
42
+ export declare function filtroChave(v: VisualState): string | null;
43
+ /**
44
+ * As DUAS coisas que a raiz precisa aplicar, num objeto só.
45
+ *
46
+ * ⚠️ Devolver os dois JUNTOS é o ponto da issue #104: enquanto eram um campo, aplicar um apagava o outro.
47
+ * Aqui um estado com tema `hc7` e correção `deuter` devolve os dois preenchidos, e é o que o gate afirma.
48
+ */
49
+ export interface Aplicacao {
50
+ /** A chave do modo direto (alto contraste), ou `null`. */
51
+ readonly direto: string | null;
52
+ /** A chave do filtro CSS (correção ou simulação), ou `null`. */
53
+ readonly filtro: string | null;
54
+ }
55
+ export declare function aplicacao(v: VisualState): Aplicacao;
56
+ /** Há uma simulação a correr? É a pergunta que `simulatesDisability` fazia à string. */
57
+ export declare function ehSimulacao(v: VisualState): boolean;
58
+ /** É a simulação de CEGUEIRA? O quiz e o sonar perguntam isto para se comportarem sem tela. */
59
+ export declare function ehCego(v: VisualState): boolean;
60
+ /** É uma das cinco simulações de BAIXA VISÃO? Elas pedem o overlay como textura, e não só um filtro. */
61
+ export declare function ehBaixaVisao(v: VisualState): boolean;
62
+ /** O tema está fora do padrão? Era o `/^hc-direto/.test(s.viz)` espalhado pela interface. */
63
+ export declare function temAltoContraste(v: VisualState): boolean;
64
+ /** Próximo TEMA no ciclo do ícone da barra rápida. Anda só no seu eixo, e não toca na correção. */
65
+ export declare function proximoTema(v: VisualState): VisualState;
66
+ /**
67
+ * Próxima CORREÇÃO no ciclo do ícone. Anda só no seu eixo, e não toca no tema.
68
+ *
69
+ * ⚠️ A ASSIMETRIA DO ORIGINAL FICA REGISTRADA E NÃO É COPIADA: `nextCvd` mapeava um valor desconhecido para
70
+ * o ÍNDICE 1 (`fix-protan`) enquanto `nextContrast` mapeava para 0. Era um comentário no ficheiro a explicar
71
+ * uma diferença que ninguém tinha decidido. Aqui as duas começam no padrão, porque um valor desconhecido é
72
+ * exatamente o caso em que não se sabe o que a criança queria — e o padrão é a única resposta que não
73
+ * escolhe por ela.
74
+ */
75
+ export declare function proximaCorrecao(v: VisualState): VisualState;
76
+ /**
77
+ * A chave que o sprite do jogador usa para escolher textura.
78
+ *
79
+ * ⚠️ É a SIMULAÇÃO quando há uma, e o TEMA quando não há — nesta ordem porque é a ordem do que a criança vê:
80
+ * uma cegueira simulada apaga a tela inteira, e nesse instante o tema não muda nada do que ela percebe.
81
+ */
82
+ export declare function chaveDeTextura(v: VisualState): string;
83
+ /**
84
+ * Traduz o valor salvo. Aceita o antigo (string) e o novo (objeto), e devolve sempre um estado válido.
85
+ *
86
+ * ⚠️ IDEMPOTENTE POR CONSTRUÇÃO: um objeto já migrado atravessa com os campos conferidos. Importa porque a
87
+ * leitura acontece por jogador e mais de uma vez por sessão.
88
+ *
89
+ * ⚠️ E DESCONHECIDO CAI NO PADRÃO EM VEZ DE ESTOURAR. O dado vem do navegador de uma criança e pode ser de
90
+ * uma versão futura, de outra máquina, ou lixo. Um `throw` aqui tiraria o jogo do ar por causa de uma
91
+ * preferência; o padrão apenas devolve o jogo como ele nasce.
92
+ */
93
+ export declare function migrarVisual(salvo: unknown): VisualState;
94
+ export declare const TEMAS: readonly Tema[];
95
+ export declare const CORRECOES: readonly Correcao[];
96
+ export declare const SIMULACOES: readonly Simulacao[];
97
+ /** As chaves antigas que a migração conhece — exportada para o gate poder exigir que TODAS estejam cobertas. */
98
+ export declare const CHAVES_ANTIGAS: readonly string[];
@@ -0,0 +1,184 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/viz-axes — OS DOIS EIXOS, e a simulação que NÃO é um deles (ADR-0076). Módulo-folha, zero deps.
3
+ //
4
+ // ========================= O DEFEITO QUE ISTO CONSERTA =========================
5
+ // Hoje o menu visual é UM rádio e `p.viz` guarda UMA string. Escolher `fix-deuter` desliga o contraste 7:1;
6
+ // escolher um nível de contraste desliga a correção. ⚠️ UMA CRIANÇA COM DALTONISMO QUE TAMBÉM PRECISE DE
7
+ // ALTO CONTRASTE NÃO PODE TER OS DOIS — e as duas necessidades coexistem numa mesma pessoa com frequência.
8
+ //
9
+ // ========================= POR QUE A SIMULAÇÃO NÃO É UM EIXO =========================
10
+ // Correção e simulação são ambas implementadas como filtro e servem a PROPÓSITOS OPOSTOS: uma deixa uma
11
+ // criança JOGAR, a outra deixa alguém SENTIR como é não conseguir. Agrupá-las pelo mecanismo já as fundiu
12
+ // duas vezes — no ADR-0011 (as correções listadas no painel de Empatia, de modo que *«a criança que
13
+ // PRECISAVA da correção tinha de a procurar no menu sobre fingir»*) e no ADR-0075.
14
+ //
15
+ // ⚠️ UM GRUPO DE AJUSTES CHAMA-SE PELO QUE ELE SERVE, NUNCA PELO COMO É IMPLEMENTADO.
16
+ //
17
+ // ========================= E POR QUE A SIMULAÇÃO É TRAVADA NOS DOIS PADRÕES =========================
18
+ // De uma tela já corrigida, uma simulação não mostra nem a deficiência nem a correção; por cima de um tema
19
+ // de alto contraste, mostra o que o tema faz e não o que a deuteranopia faz. Uma demonstração a correr em
20
+ // cima de uma adaptação não é uma demonstração mais fraca — **ela ensina uma coisa falsa**.
21
+ export const PADRAO = Object.freeze({ tema: 'padrao', correcao: 'tricro', simulacao: null });
22
+ /**
23
+ * Os dois eixos estão no padrão?
24
+ *
25
+ * ⚠️ É A PERGUNTA QUE LIBERA A SIMULAÇÃO, e é por isso que ela é uma função e não um booleano guardado:
26
+ * guardar o resultado deixaria as duas coisas divergirem, e a divergência aqui significa uma demonstração a
27
+ * correr por cima de uma adaptação — que ensina uma coisa falsa.
28
+ */
29
+ export function nosPadroes(v) {
30
+ return v.tema === 'padrao' && v.correcao === 'tricro';
31
+ }
32
+ export function simulacaoIndisponivel(v) {
33
+ const t = v.tema !== 'padrao';
34
+ const c = v.correcao !== 'tricro';
35
+ if (t && c)
36
+ return 'ambos';
37
+ if (t)
38
+ return 'tema';
39
+ if (c)
40
+ return 'correcao';
41
+ return null;
42
+ }
43
+ /* ===================== A COMPOSIÇÃO ===================== */
44
+ //
45
+ // ⚠️ ELA JÁ ERA MECANICAMENTE POSSÍVEL, e é isso que torna o defeito mais caro do que parecia: o TEMA de
46
+ // alto contraste vai pela RENDERIZAÇÃO DIRETA (`DIRECT_CFG`/PIXI) e a CORREÇÃO vai por FILTRO CSS
47
+ // (`url(#cvd-fix-*)`). São dois mecanismos que não colidem. O que impedia os dois de coexistir não era a
48
+ // máquina — era o campo único que só cabia um valor.
49
+ /** A chave de modo DIRETO que este tema usa, ou `null` para o tema padrão. */
50
+ export function temaDireto(v) {
51
+ return v.tema === 'hc3' ? 'hc-direto'
52
+ : v.tema === 'hc45' ? 'hc-direto-45'
53
+ : v.tema === 'hc7' ? 'hc-direto-7'
54
+ : null;
55
+ }
56
+ /**
57
+ * A chave de FILTRO CSS que este estado usa, ou `null`.
58
+ *
59
+ * ⚠️ SIMULAÇÃO VENCE CORREÇÃO AQUI, e não é uma regra de precedência escondida: as duas não podem coexistir
60
+ * porque `simulacaoIndisponivel` já as separa — uma simulação só corre com a correção no padrão. Este `??`
61
+ * é o que acontece quando alguém constrói um estado à mão que a interface não deixaria montar, e escolher a
62
+ * simulação é o menos errado dos dois: ela é a intenção mais recente e mais visível.
63
+ */
64
+ export function filtroChave(v) {
65
+ if (v.simulacao)
66
+ return v.simulacao;
67
+ return v.correcao === 'tricro' ? null : 'fix-' + (v.correcao === 'deuter' ? 'deuter' : v.correcao);
68
+ }
69
+ export function aplicacao(v) {
70
+ return { direto: temaDireto(v), filtro: filtroChave(v) };
71
+ }
72
+ /* ===================== O QUE OS LEITORES DE FACTO PERGUNTAM ===================== */
73
+ //
74
+ // ⚠️ ESTAS FUNÇÕES SAÍRAM DE UMA MEDIDA, e não de um desenho a priori. Os 31 leitores de `p.viz` foram
75
+ // classificados pelo que PERGUNTAM, e a lista curta abaixo é o resultado — quatro perguntam o «kind», duas
76
+ // se é simulação, uma a textura, e o resto é escrita. Um leitor que precise de algo fora daqui é sinal de
77
+ // que a pergunta dele merecia um nome.
78
+ //
79
+ // ⚠️ E A MEDIDA TROUXE UM ACHADO: `ui/pause-icons` já tem `nextContrast` e `nextCvd`, cada uma a ciclar
80
+ // DENTRO do seu eixo. A interface já pensava em dois eixos há muito tempo; era o ARMAZENAMENTO que os
81
+ // colapsava num campo. As funções de ciclo abaixo são as mesmas duas, agora com onde guardar o resultado.
82
+ /** Há uma simulação a correr? É a pergunta que `simulatesDisability` fazia à string. */
83
+ export function ehSimulacao(v) {
84
+ return v.simulacao !== null;
85
+ }
86
+ /** É a simulação de CEGUEIRA? O quiz e o sonar perguntam isto para se comportarem sem tela. */
87
+ export function ehCego(v) {
88
+ return v.simulacao === 'blind';
89
+ }
90
+ /** É uma das cinco simulações de BAIXA VISÃO? Elas pedem o overlay como textura, e não só um filtro. */
91
+ export function ehBaixaVisao(v) {
92
+ return v.simulacao !== null && v.simulacao.startsWith('lv-');
93
+ }
94
+ /** O tema está fora do padrão? Era o `/^hc-direto/.test(s.viz)` espalhado pela interface. */
95
+ export function temAltoContraste(v) {
96
+ return v.tema !== 'padrao';
97
+ }
98
+ /** Próximo TEMA no ciclo do ícone da barra rápida. Anda só no seu eixo, e não toca na correção. */
99
+ export function proximoTema(v) {
100
+ const i = TEMAS.indexOf(v.tema);
101
+ return { ...v, tema: TEMAS[(i < 0 ? 0 : i + 1) % TEMAS.length] };
102
+ }
103
+ /**
104
+ * Próxima CORREÇÃO no ciclo do ícone. Anda só no seu eixo, e não toca no tema.
105
+ *
106
+ * ⚠️ A ASSIMETRIA DO ORIGINAL FICA REGISTRADA E NÃO É COPIADA: `nextCvd` mapeava um valor desconhecido para
107
+ * o ÍNDICE 1 (`fix-protan`) enquanto `nextContrast` mapeava para 0. Era um comentário no ficheiro a explicar
108
+ * uma diferença que ninguém tinha decidido. Aqui as duas começam no padrão, porque um valor desconhecido é
109
+ * exatamente o caso em que não se sabe o que a criança queria — e o padrão é a única resposta que não
110
+ * escolhe por ela.
111
+ */
112
+ export function proximaCorrecao(v) {
113
+ const i = CORRECOES.indexOf(v.correcao);
114
+ return { ...v, correcao: CORRECOES[(i < 0 ? 0 : i + 1) % CORRECOES.length] };
115
+ }
116
+ /**
117
+ * A chave que o sprite do jogador usa para escolher textura.
118
+ *
119
+ * ⚠️ É a SIMULAÇÃO quando há uma, e o TEMA quando não há — nesta ordem porque é a ordem do que a criança vê:
120
+ * uma cegueira simulada apaga a tela inteira, e nesse instante o tema não muda nada do que ela percebe.
121
+ */
122
+ export function chaveDeTextura(v) {
123
+ return v.simulacao ?? temaDireto(v) ?? 'normal';
124
+ }
125
+ /* ===================== A MIGRAÇÃO ===================== */
126
+ //
127
+ // ⚠️ ELA NÃO É OPCIONAL E VEM ANTES DA PRIMEIRA LEITURA DA FORMA NOVA. O ajuste salvo guarda o valor único
128
+ // antigo; sem a tradução, o modo visual que cada criança já escolheu é DESCARTADO — e quem escolheu um
129
+ // desses valores escolheu-o porque enxerga assim.
130
+ /** O valor único antigo → o estado de dois eixos. Chave desconhecida cai no padrão, e nunca estoura. */
131
+ const DE_CHAVE_UNICA = Object.freeze({
132
+ normal: PADRAO,
133
+ // Os três níveis de contraste viram TEMA, e a correção fica no padrão.
134
+ 'hc-direto': { tema: 'hc3', correcao: 'tricro', simulacao: null },
135
+ 'hc-direto-45': { tema: 'hc45', correcao: 'tricro', simulacao: null },
136
+ 'hc-direto-7': { tema: 'hc7', correcao: 'tricro', simulacao: null },
137
+ // As três correções viram CORREÇÃO, e o tema fica no padrão.
138
+ 'fix-protan': { tema: 'padrao', correcao: 'protan', simulacao: null },
139
+ 'fix-deuter': { tema: 'padrao', correcao: 'deuter', simulacao: null },
140
+ 'fix-tritan': { tema: 'padrao', correcao: 'tritan', simulacao: null },
141
+ // ⚠️ AS NOVE SIMULAÇÕES VOLTAM COM OS DOIS EIXOS NO PADRÃO, e não é perda de informação: uma simulação
142
+ // só era possível a partir do padrão de qualquer maneira, porque ela SUBSTITUÍA tudo o resto. O que a
143
+ // forma nova acrescenta é dizer isso em vez de o deixar implícito.
144
+ 'sim-protan': { tema: 'padrao', correcao: 'tricro', simulacao: 'sim-protan' },
145
+ 'sim-deuter': { tema: 'padrao', correcao: 'tricro', simulacao: 'sim-deuter' },
146
+ 'sim-tritan': { tema: 'padrao', correcao: 'tricro', simulacao: 'sim-tritan' },
147
+ 'lv-blur': { tema: 'padrao', correcao: 'tricro', simulacao: 'lv-blur' },
148
+ 'lv-haze': { tema: 'padrao', correcao: 'tricro', simulacao: 'lv-haze' },
149
+ 'lv-tunnel': { tema: 'padrao', correcao: 'tricro', simulacao: 'lv-tunnel' },
150
+ 'lv-macular': { tema: 'padrao', correcao: 'tricro', simulacao: 'lv-macular' },
151
+ 'lv-diabetic': { tema: 'padrao', correcao: 'tricro', simulacao: 'lv-diabetic' },
152
+ blind: { tema: 'padrao', correcao: 'tricro', simulacao: 'blind' },
153
+ });
154
+ /**
155
+ * Traduz o valor salvo. Aceita o antigo (string) e o novo (objeto), e devolve sempre um estado válido.
156
+ *
157
+ * ⚠️ IDEMPOTENTE POR CONSTRUÇÃO: um objeto já migrado atravessa com os campos conferidos. Importa porque a
158
+ * leitura acontece por jogador e mais de uma vez por sessão.
159
+ *
160
+ * ⚠️ E DESCONHECIDO CAI NO PADRÃO EM VEZ DE ESTOURAR. O dado vem do navegador de uma criança e pode ser de
161
+ * uma versão futura, de outra máquina, ou lixo. Um `throw` aqui tiraria o jogo do ar por causa de uma
162
+ * preferência; o padrão apenas devolve o jogo como ele nasce.
163
+ */
164
+ export function migrarVisual(salvo) {
165
+ if (typeof salvo === 'string')
166
+ return DE_CHAVE_UNICA[salvo] ?? PADRAO;
167
+ if (salvo && typeof salvo === 'object') {
168
+ const o = salvo;
169
+ return {
170
+ tema: TEMAS.includes(o.tema) ? o.tema : PADRAO.tema,
171
+ correcao: CORRECOES.includes(o.correcao) ? o.correcao : PADRAO.correcao,
172
+ simulacao: SIMULACOES.includes(o.simulacao) ? o.simulacao : null,
173
+ };
174
+ }
175
+ return PADRAO;
176
+ }
177
+ export const TEMAS = ['padrao', 'hc3', 'hc45', 'hc7'];
178
+ export const CORRECOES = ['tricro', 'protan', 'deuter', 'tritan'];
179
+ export const SIMULACOES = [
180
+ null, 'sim-protan', 'sim-deuter', 'sim-tritan',
181
+ 'lv-blur', 'lv-haze', 'lv-tunnel', 'lv-macular', 'lv-diabetic', 'blind',
182
+ ];
183
+ /** As chaves antigas que a migração conhece — exportada para o gate poder exigir que TODAS estejam cobertas. */
184
+ export const CHAVES_ANTIGAS = Object.keys(DE_CHAVE_UNICA);
@@ -0,0 +1,61 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * `sim` — este modo SIMULA uma deficiência em quem não a tem, em vez de CORRIGIR a tela para quem a tem.
4
+ *
5
+ * O `kind` não responde a isso e nunca respondeu: `sim-deuter` (simular daltonismo) e `fix-deuter` (corrigir
6
+ * daltonismo) compartilham `kind:'filter'`, ainda que sirvam a duas pessoas opostas. Enquanto ninguém
7
+ * precisava distinguir, a lacuna era inofensiva. Passou a não ser quando o menu de empatia ganhou um
8
+ * "restaurar padrões" (ADR-0028): desligar as simulações é o objetivo do botão, e desligar as correções junto
9
+ * seria tirar de uma criança daltônica a única correção que ela tem — a partir do menu que existe para quem
10
+ * NÃO tem a deficiência. Um reset que faz isso é pior que a armadilha que ele deveria desfazer.
11
+ *
12
+ * Só os 9 modos marcados simulam. Os 3 `fix-*` corrigem, os 3 `hc-direto*` corrigem, e `normal` não faz nada.
13
+ */
14
+ /**
15
+ * Um modo de visão. `nome` e `desc` guardam CHAVE i18n, não texto — mesma decisão de `CenarioTema.nome` e
16
+ * `RM_LABEL`, e pelo mesmo motivo: uma tabela de `const` com texto resolve UMA vez, no import, e fica
17
+ * congelada no idioma do boot. Este menu é o que uma criança de baixa visão ou daltônica lê para configurar o
18
+ * PRÓPRIO jogo; deixá-lo em português numa build em inglês tira dela a única página que ela precisava ler.
19
+ *
20
+ * Quem EXIBE resolve (`render/viz-setters`, `consumer-quiz`), e por isso este módulo continua FOLHA: dado
21
+ * puro, sem dependência nenhuma, importável dos dois lados da fronteira.
22
+ */
23
+ export type VizMode = {
24
+ key: string;
25
+ kind: string;
26
+ nome: string;
27
+ desc: string;
28
+ lv?: string;
29
+ sim?: true;
30
+ };
31
+ export declare const VIZ_MODES: VizMode[];
32
+ export declare const VIZ_BY_KEY: Record<string, VizMode>;
33
+ export declare const VIZ_FILTER: Record<string, string>;
34
+ export declare const VIZ_CYCLE: string[];
35
+ /**
36
+ * Este modo simula uma deficiência? Chave desconhecida (ou vazia) → false, porque a pergunta que o chamador
37
+ * está fazendo é "posso desligar isto sem tirar nada de ninguém?", e a resposta honesta diante do
38
+ * desconhecido é não.
39
+ */
40
+ export declare function simulatesDisability(key: string): boolean;
41
+ /**
42
+ * As CORREÇÕES de daltonismo: daltonizam a tela para quem TEM a condição. Derivadas do catálogo, nunca
43
+ * listadas à mão — são exatamente os filtros que não simulam.
44
+ *
45
+ * Existem como lista própria porque moram no menu de ACESSIBILIDADE VISUAL, e não no de empatia (decisão do
46
+ * Dev, issue #60). Ficaram anos no menu errado por um motivo que este arquivo agora conserta: `kind` não
47
+ * distinguia simular de corrigir, então o painel de empatia, que se recorta por `kind:'filter'`, arrastava as
48
+ * três junto. Dois públicos opostos na mesma lista — quem quer sentir como é ser daltônico e quem é.
49
+ */
50
+ export declare const VIZ_CORRECTIONS: readonly VizMode[];
51
+ /** Este modo precisa de um CANVAS de mundo para existir? Só os `hcnew` precisam: eles repintam texturas. */
52
+ export declare function needsCanvas(key: string): boolean;
53
+ /**
54
+ * Os modos que funcionam em QUALQUER jogo — os que se aplicam como filtro de CSS sobre um elemento.
55
+ *
56
+ * Derivada, nunca listada à mão: um modo novo entra na pilha certa por causa do `kind` que ele declara, e não
57
+ * porque alguém lembrou de acrescentá-lo aqui. É a mesma regra de `VIZ_CORRECTIONS`.
58
+ */
59
+ export declare const VIZ_DOM_ONLY: readonly VizMode[];
60
+ /** Os modos que EXIGEM mundo. Complemento exato de `VIZ_DOM_ONLY` — juntos, os 16, sem sobra nem repetição. */
61
+ export declare const VIZ_CANVAS_ONLY: readonly VizMode[];