@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,228 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/city-tex — a ARTE PROCEDURAL DA RUA: os bichos, os pedestres e os carros do cenário Cidade.
3
+ //
4
+ // Três blocos que viviam soltos no main.js (`LIFE_TEX`, `ADULT_TEX`, `CAR_TEX`) e que são a MESMA coisa vista
5
+ // de três ângulos: retângulos coloridos carimbados num canvas offscreen que vira PIXI.Texture. Por isso caem
6
+ // juntos aqui, e não em três módulos: o que os une não é o assunto (bicho ≠ carro), é a TÉCNICA e o CONSUMIDOR
7
+ // — quem os pede é a Cidade, e só ela. `render/props` (moeda, árvore) e `render/world-tex` (o nível inteiro)
8
+ // ficam onde estão: props é decoração de QUALQUER cenário, world-tex é o mapa, não a rua.
9
+ //
10
+ // A DUPLICAÇÃO QUE ESTA EXTRAÇÃO CUROU: cada um dos três blocos declarava o SEU `mk` e o SEU `px`, com
11
+ // assinaturas que não conversavam — `mk(w,h,paint)` com `px(x,y,w,h,cor)`; `mk(paint)` de 16×32 fixo com
12
+ // `px(x,y,w,h)` e a cor presa numa `const` de fora; `mk(body,dark,top)` de 78×36 fixo com `px(x,y,w,h,cl)`.
13
+ // Três grafias para "pinte um retângulo". Agora existe uma só, `pixelCanvas`/`pixelTexture` em render/canvas.
14
+ // EFEITO COLATERAL ACEITO: as silhuetas de adulto passaram a receber a cor em CADA chamada (`ADULT_COL`) em vez
15
+ // de um `fillStyle` único antes do laço. O bitmap resultante é idêntico ao byte — só o número de atribuições de
16
+ // `fillStyle` muda —, e em troca some a última assinatura divergente.
17
+ //
18
+ // SEM I/O NO IMPORT — este é o ponto mais importante do arquivo. As texturas de origem nasciam em IIFE no corpo
19
+ // do main.js, o que funciona lá porque o main.js só roda no navegador; um módulo em `render/` é importado pelo
20
+ // project `node` do Vitest, onde `makeCanvas` toca `document` e explode. Por isso NÃO há constante pronta aqui:
21
+ // há `createCityTextures()`, chamada uma vez pelo main.js no boot. Mesmo precedente de `render/textures`
22
+ // (caches preenchidos por `initTextures`) e `render/viewports` (`initViewports`).
23
+ //
24
+ // A FRONTEIRA COM O JOGO NÃO SE MEXE: `game/life` e `game/traffic` continuam recebendo as texturas por INJEÇÃO
25
+ // (`initLife({ lifeTex, adultTex, … })`, `initTraffic({ CAR_TEX, … })`) — eles nunca importaram este módulo e
26
+ // continuam sem importar, para seguirem testáveis em `node` com texturas de mentira. O main.js é que passou a
27
+ // buscar aqui o que antes fabricava em casa. Quem cria as CAMADAS (`lifeLayer`, `carLayer`) e solda o z-order
28
+ // segue sendo o main.js: z-order é composição de cena, não arte.
29
+ //
30
+ // ARMADILHAS AO MEXER NA ARTE:
31
+ // · Os comentários de arte (`// cabeça alta / bicando`, `// M2: magro, de boné`, `// coluna B + retrovisor`)
32
+ // são a ÚNICA documentação de qual retângulo é qual parte da figura. Não os apague ao editar coordenadas.
33
+ // · A silhueta F2 NÃO chama `arms` — é verbatim do original, não esquecimento: os dois retângulos altos do
34
+ // cabelo longo (`2,4,3,13` e `11,4,3,13`) ocupam exatamente onde os braços iriam. Se um dia isso for
35
+ // "consertado", a F2 muda de forma no jogo.
36
+ // · Os carros são 3× NATIVOS (78×36 desenhados, sem upscale) porque a rua fica na camada da frente, perto da
37
+ // câmera; os bichos são 1× (7×6 … 13×9). Não normalize as escalas: `game/traffic` conta com os 78px.
38
+ // · Dois quadros por figura, sempre na ordem `[f0, f1]`: `game/life` alterna por índice (`f = f ? 0 : 1`).
39
+ // Inverter o par não quebra nenhum tipo — quebra a animação, em silêncio.
40
+ import { pixelTexture } from './canvas.js';
41
+ /* ===================== bichos: pombo, pombo voando, gato, cão ===================== */
42
+ /** Pombo no chão 7×6 — corpo cinza, bico laranja, pés. f alterna cabeça alta ↔ bicando. */
43
+ export const paintPombo = (f) => (px) => {
44
+ px(1, 2, 4, 2, '#9aa3b2');
45
+ px(0, 3, 2, 1, '#7d8695');
46
+ if (f === 0) {
47
+ px(4, 1, 2, 2, '#b9c2d0');
48
+ px(6, 2, 1, 1, '#e0a23c');
49
+ }
50
+ else {
51
+ px(4, 3, 2, 2, '#b9c2d0');
52
+ px(6, 4, 1, 1, '#e0a23c');
53
+ } // cabeça alta / bicando
54
+ px(2, 5, 1, 1, '#c96a2e');
55
+ px(4, 5, 1, 1, '#c96a2e');
56
+ };
57
+ /** Pombo em revoada 8×7 — mesmo corpo, asa batendo. */
58
+ export const paintPomboFly = (f) => (px) => {
59
+ px(2, 3, 4, 2, '#9aa3b2');
60
+ px(6, 2, 2, 2, '#b9c2d0');
61
+ px(7, 3, 1, 1, '#e0a23c');
62
+ if (f === 0)
63
+ px(1, 0, 4, 2, '#c8d0dc');
64
+ else
65
+ px(1, 5, 4, 2, '#c8d0dc'); // asa cima/baixo
66
+ };
67
+ /** Gato 12×8 — corpo/cabeça/orelhas/rabo escuros, olho verde; f alterna as patas. */
68
+ export const paintGato = (f) => (px) => {
69
+ px(1, 3, 8, 3, '#454b58');
70
+ px(8, 1, 3, 3, '#454b58');
71
+ px(8, 0, 1, 1, '#454b58');
72
+ px(10, 0, 1, 1, '#454b58');
73
+ px(0, 2, 1, 3, '#454b58');
74
+ px(9, 2, 1, 1, '#9fe07a');
75
+ if (f === 0) {
76
+ px(2, 6, 1, 2, '#454b58');
77
+ px(7, 6, 1, 2, '#454b58');
78
+ }
79
+ else {
80
+ px(3, 6, 1, 2, '#454b58');
81
+ px(6, 6, 1, 2, '#454b58');
82
+ }
83
+ };
84
+ /** Cão 13×9 — corpo marrom, focinho escuro, orelha; f alterna as patas. */
85
+ export const paintCao = (f) => (px) => {
86
+ px(1, 3, 9, 4, '#8a6a44');
87
+ px(9, 1, 4, 4, '#8a6a44');
88
+ px(12, 2, 1, 2, '#3a2d1c');
89
+ px(9, 0, 2, 2, '#6d5334');
90
+ px(0, 2, 1, 3, '#8a6a44');
91
+ if (f === 0) {
92
+ px(2, 7, 1, 2, '#6d5334');
93
+ px(8, 7, 1, 2, '#6d5334');
94
+ }
95
+ else {
96
+ px(3, 7, 1, 2, '#6d5334');
97
+ px(7, 7, 1, 2, '#6d5334');
98
+ }
99
+ };
100
+ /** Catálogo dos bichos: tamanho do canvas + painter por quadro. Fonte única que `createCityTextures` percorre
101
+ * e que o teste de regressão de arte itera — as chaves são as MESMAS que `game/life` lê em `LifeTexAtlas`. */
102
+ export const CREATURE_ART = {
103
+ pombo: { w: 7, h: 6, paint: paintPombo },
104
+ pomboFly: { w: 8, h: 7, paint: paintPomboFly },
105
+ gato: { w: 12, h: 8, paint: paintGato },
106
+ cao: { w: 13, h: 9, paint: paintCao },
107
+ };
108
+ /* ===================== adultos: 6 silhuetas 16×32 ===================== */
109
+ // Adultos = SILHUETAS 16×32 (mesma proporção/tamanho do personagem), formatos distintos M/F (pedido do José)
110
+ /** Tamanho da silhueta — casado com o do personagem jogável (render/sprites). */
111
+ export const ADULT_W = 16, ADULT_H = 32;
112
+ /** Cor única de toda silhueta (era o `const col` fora do `mk` no main.js). */
113
+ export const ADULT_COL = '#262b38';
114
+ /** Pernas: calça (duas colunas alternando o passo) ou saia + pernas curtas. */
115
+ const legs = (px, f, skirt) => {
116
+ if (skirt) {
117
+ px(4, 20, 8, 6, ADULT_COL);
118
+ if (f === 0) {
119
+ px(5, 26, 2, 6, ADULT_COL);
120
+ px(9, 26, 2, 6, ADULT_COL);
121
+ }
122
+ else {
123
+ px(4, 26, 2, 6, ADULT_COL);
124
+ px(10, 26, 2, 6, ADULT_COL);
125
+ }
126
+ }
127
+ else {
128
+ if (f === 0) {
129
+ px(5, 20, 3, 12, ADULT_COL);
130
+ px(9, 20, 3, 11, ADULT_COL);
131
+ }
132
+ else {
133
+ px(4, 20, 3, 11, ADULT_COL);
134
+ px(10, 20, 3, 12, ADULT_COL);
135
+ }
136
+ }
137
+ };
138
+ /** Braços pendentes, defasados entre os quadros (dá o balanço da caminhada). */
139
+ const arms = (px, f) => {
140
+ if (f === 0) {
141
+ px(2, 10, 2, 8, ADULT_COL);
142
+ px(12, 10, 2, 8, ADULT_COL);
143
+ }
144
+ else {
145
+ px(2, 11, 2, 7, ADULT_COL);
146
+ px(12, 9, 2, 8, ADULT_COL);
147
+ }
148
+ };
149
+ /** As 6 silhuetas — 3 masculinas + 3 femininas, todas 16×32. A ordem é a do original (M1, M2, M3, F1, F2, F3);
150
+ * `game/life` sorteia por índice, então reordenar troca qual silhueta cada sorteio produz. */
151
+ export const ADULT_SHAPES = [
152
+ (f) => (px) => { px(4, 0, 8, 6, ADULT_COL); px(3, 6, 10, 14, ADULT_COL); arms(px, f); legs(px, f, false); }, // M1: ombros largos
153
+ (f) => (px) => { px(5, 0, 6, 5, ADULT_COL); px(3, 1, 10, 2, ADULT_COL); px(5, 5, 6, 15, ADULT_COL); arms(px, f); legs(px, f, false); }, // M2: magro, de boné
154
+ (f) => (px) => { px(4, 1, 8, 5, ADULT_COL); px(2, 6, 12, 14, ADULT_COL); arms(px, f); legs(px, f, false); }, // M3: troncudo
155
+ (f) => (px) => { px(4, 0, 8, 6, ADULT_COL); px(11, 3, 3, 10, ADULT_COL); px(4, 6, 8, 10, ADULT_COL); px(3, 16, 10, 5, ADULT_COL); arms(px, f); legs(px, f, true); }, // F1: rabo de cavalo + saia
156
+ (f) => (px) => { px(3, 0, 10, 6, ADULT_COL); px(2, 4, 3, 13, ADULT_COL); px(11, 4, 3, 13, ADULT_COL); px(5, 6, 6, 10, ADULT_COL); px(4, 16, 8, 5, ADULT_COL); legs(px, f, true); }, // F2: cabelo longo + vestido (sem `arms` — ver o cabeçalho)
157
+ (f) => (px) => { px(3, 0, 10, 7, ADULT_COL); px(4, 7, 8, 9, ADULT_COL); px(3, 16, 10, 5, ADULT_COL); arms(px, f); legs(px, f, true); }, // F3: chanel + saia
158
+ ];
159
+ /* ===================== carros: 4 cores, 78×36 (3× nativo) ===================== */
160
+ /** Tamanho do carro — 3× NATIVO (detalhado, sem upscale); `game/traffic` conta com os 78px de largura. */
161
+ export const CAR_W = 78, CAR_H = 36;
162
+ /** As 4 pinturas de fábrica: `[lataria, sombra, teto]`. */
163
+ export const CAR_PALETTES = [
164
+ ['#c8452e', '#7d2717', '#a03a24'], // vermelho
165
+ ['#2e6fc8', '#193f7d', '#2757a0'], // azul
166
+ ['#3aa15b', '#1f6336', '#2f8a4c'], // verde
167
+ ['#c8a12e', '#7d641a', '#a8862a'], // amarelo
168
+ ];
169
+ /* ===================== A RODA É REDONDA, E ISSO PRECISOU SER DITO =====================
170
+ * A primeira versão do carro (portada verbatim do monólito) fazia o pneu com UM retângulo de 14×11. Numa
171
+ * arte de retângulos carimbados, a roda é o único elemento que o olho sabe de cor que é redondo — e é o
172
+ * primeiro que denuncia o desenho inteiro. O Dev, olhando a rua: "melhore os carros, estão muito feios com
173
+ * as rodas quadradas."
174
+ *
175
+ * O jeito de ter círculo aqui é o de sempre em pixel art: uma TABELA de linhas, cada uma com o recuo e a
176
+ * largura daquela altura. Não é aproximação de matemática em tempo de execução — é o desenho, escrito. */
177
+ /** Pneu Ø14: `[recuo, largura]` por linha, de cima para baixo. */
178
+ const PNEU = [
179
+ [5, 4], [3, 8], [2, 10], [1, 12], [1, 12], [0, 14], [0, 14],
180
+ [0, 14], [0, 14], [1, 12], [1, 12], [2, 10], [3, 8], [5, 4],
181
+ ];
182
+ /** Aro Ø8, no mesmo formato — desenhado dentro do pneu. */
183
+ const ARO = [
184
+ [2, 4], [1, 6], [0, 8], [0, 8], [0, 8], [0, 8], [1, 6], [2, 4],
185
+ ];
186
+ /** Um carro 78×36 na paleta dada. Vidros, brilhos, farol e lanterna são fixos (não seguem a lataria). */
187
+ export const paintCar = (body, dark, top) => (px) => {
188
+ px(3, 14, 72, 13, body);
189
+ px(3, 25, 72, 2, dark); // corpo + saia escura
190
+ px(1, 16, 2, 8, dark);
191
+ px(75, 16, 2, 8, dark); // para-choques
192
+ // Cabine com o teto AFUNILADO: eram 40px de bloco reto, e teto reto num carro de perfil lê como caixa.
193
+ px(17, 4, 36, 2, top);
194
+ px(15, 6, 40, 9, top);
195
+ px(17, 6, 36, 9, body);
196
+ px(19, 7, 14, 7, '#bcd6ee');
197
+ px(37, 7, 14, 7, '#bcd6ee'); // vidros
198
+ px(20, 8, 4, 2, '#eef6ff');
199
+ px(38, 8, 4, 2, '#eef6ff'); // brilho dos vidros
200
+ px(34, 7, 3, 7, top);
201
+ px(53, 10, 4, 4, dark); // coluna B + retrovisor
202
+ px(3, 14, 72, 1, 'rgba(255,255,255,.28)'); // realce superior da lataria
203
+ px(35, 15, 1, 10, dark); // frisa entre as duas portas
204
+ px(0, 17, 3, 5, '#ffd9a0');
205
+ px(75, 17, 3, 5, '#ff6a5a'); // farol / lanterna
206
+ const wheel = (wx) => {
207
+ px(wx + 1, 20, 12, 2, dark);
208
+ px(wx - 1, 21, 16, 1, dark); // caixa de roda, em arco
209
+ PNEU.forEach(([dx, w], i) => px(wx + dx, 22 + i, w, 1, '#10131a')); // pneu
210
+ ARO.forEach(([dx, w], i) => px(wx + 3 + dx, 25 + i, w, 1, '#2b3140')); // aro
211
+ px(wx + 6, 28, 2, 2, '#8a93a8'); // cubo
212
+ };
213
+ wheel(11);
214
+ wheel(53);
215
+ };
216
+ /** Assa TODA a arte da rua de uma vez. TOCA `document` — chame só no boot do navegador, nunca no import.
217
+ * Não memoiza de propósito: é chamada uma vez pelo main.js; um cache aqui só esconderia uma segunda chamada. */
218
+ export function createCityTextures() {
219
+ const pair = (w, h, paint) => [pixelTexture(w, h, paint(0)), pixelTexture(w, h, paint(1))]; // ordem [f0, f1] — game/life alterna por índice
220
+ const lifeTex = {};
221
+ for (const k of Object.keys(CREATURE_ART)) {
222
+ const a = CREATURE_ART[k];
223
+ lifeTex[k] = pair(a.w, a.h, a.paint);
224
+ }
225
+ const adultTex = ADULT_SHAPES.map((v) => pair(ADULT_W, ADULT_H, v));
226
+ const carTex = CAR_PALETTES.map(([body, dark, top]) => pixelTexture(CAR_W, CAR_H, paintCar(body, dark, top)));
227
+ return { lifeTex, adultTex, carTex };
228
+ }
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { type PixelPainter } from './canvas.js';
3
+ /** O TILE do miolo da parede: alvenaria em fiada alternada. 51 retângulos, 9 cores, fiel ao pixel. */
4
+ export declare const paintTileFill: PixelPainter;
5
+ /** O TILE do TOPO da plataforma: mesma alvenaria com a borda superior iluminada. 62 retângulos, 23 cores. */
6
+ export declare const paintTileSurface: PixelPainter;
7
+ /** Os dois tiles como canvas 16×16 — a forma que `worldCanvas` consumia das imagens carregadas. */
8
+ export declare function cityTiles(): {
9
+ fill: HTMLCanvasElement;
10
+ surface: HTMLCanvasElement;
11
+ };
@@ -0,0 +1,140 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/city-tiles.ts — OS DOIS TILES DA CIDADE, como DADOS e não como PNG (pilar "arte = dados", ADR-0010).
3
+ //
4
+ // `tile_fill` e `tile_surface` eram dois PNG de 16×16 baixados por `carregarTilesDoTema`. Aqui eles voltam como
5
+ // `PixelPainter` — a mesma forma que `render/city-tex` já usa para os pombos, os gatos e as silhuetas: uma
6
+ // lista de retângulos `px(x, y, w, h, cor)`.
7
+ //
8
+ // POR QUE RETÂNGULOS E NÃO UMA REGRA DE ALVENARIA. A primeira tentativa foi inferir a regra (tijolo 7×6, junta
9
+ // de 1 px, fiada alternada, bisel) e gerá-la. Deu 66% de acerto exato: a arte tem variação FEITA À MÃO — o
10
+ // bisel muda de tijolo para tijolo, e nenhuma regra curta a alcança. A decomposição em retângulos custa 51 e
11
+ // 62 chamadas e acerta 100%. Entre uma regra elegante que erra um quinto dos pixels e uma lista que acerta
12
+ // todos, num tile de 16×16 desenhado à mão, a lista é a resposta honesta.
13
+ //
14
+ // O QUE ISSO COMPRA, além de tirar dois PNG do pacote: o desenho passa a ser recolorível (o alto contraste e
15
+ // as paletas seguras para daltonismo mexem em cores, não em bitmaps), some o carregamento assíncrono — e com
16
+ // ele a guarda de corrida de `set-cenario` e os 404 de boot dos temas que não têm arte própria.
17
+ import { pixelCanvas } from './canvas.js';
18
+ /** O TILE do miolo da parede: alvenaria em fiada alternada. 51 retângulos, 9 cores, fiel ao pixel. */
19
+ export const paintTileFill = (px) => {
20
+ px(0, 0, 1, 1, '#403b48');
21
+ px(1, 0, 3, 1, '#b8b6c1');
22
+ px(4, 0, 2, 1, '#bcbdc9');
23
+ px(6, 0, 2, 1, '#828291');
24
+ px(8, 0, 1, 1, '#403b48');
25
+ px(9, 0, 1, 1, '#b1b0bc');
26
+ px(10, 0, 4, 1, '#b8b6c1');
27
+ px(14, 0, 2, 1, '#403b48');
28
+ px(0, 1, 1, 1, '#b1b0bc');
29
+ px(1, 1, 6, 5, '#828291');
30
+ px(7, 1, 1, 4, '#403b48');
31
+ px(8, 1, 1, 5, '#b8b6c1');
32
+ px(9, 1, 6, 5, '#828291');
33
+ px(15, 1, 1, 5, '#393543');
34
+ px(0, 2, 1, 3, '#b8b6c1');
35
+ px(0, 5, 1, 1, '#b1b0bc');
36
+ px(7, 5, 1, 1, '#3e2e3f');
37
+ px(0, 6, 1, 3, '#403b48');
38
+ px(1, 6, 1, 1, '#777485');
39
+ px(2, 6, 4, 1, '#828291');
40
+ px(6, 6, 3, 3, '#403b48');
41
+ px(9, 6, 5, 1, '#828291');
42
+ px(14, 6, 2, 2, '#403b48');
43
+ px(1, 7, 5, 1, '#403b48');
44
+ px(9, 7, 5, 1, '#403b48');
45
+ px(1, 8, 1, 1, '#b1b0bc');
46
+ px(2, 8, 2, 1, '#b8b6c1');
47
+ px(4, 8, 1, 1, '#bcbdc9');
48
+ px(5, 8, 1, 1, '#b1b0bc');
49
+ px(9, 8, 1, 1, '#b1b0bc');
50
+ px(10, 8, 2, 1, '#b8b6c1');
51
+ px(12, 8, 1, 1, '#bcbdc9');
52
+ px(13, 8, 1, 1, '#b1b0bc');
53
+ px(14, 8, 1, 1, '#403b48');
54
+ px(15, 8, 1, 1, '#393543');
55
+ px(0, 9, 1, 5, '#b1b0bc');
56
+ px(1, 9, 6, 5, '#828291');
57
+ px(7, 9, 1, 3, '#403b48');
58
+ px(8, 9, 1, 5, '#b8b6c1');
59
+ px(9, 9, 6, 5, '#828291');
60
+ px(15, 9, 1, 2, '#403b48');
61
+ px(15, 11, 1, 3, '#393543');
62
+ px(7, 12, 1, 2, '#393543');
63
+ px(0, 14, 1, 2, '#403b48');
64
+ px(1, 14, 5, 1, '#828291');
65
+ px(6, 14, 3, 2, '#403b48');
66
+ px(9, 14, 5, 1, '#828291');
67
+ px(14, 14, 2, 2, '#403b48');
68
+ px(1, 15, 1, 1, '#444653');
69
+ px(2, 15, 4, 1, '#403b48');
70
+ px(9, 15, 5, 1, '#403b48');
71
+ };
72
+ /** O TILE do TOPO da plataforma: mesma alvenaria com a borda superior iluminada. 62 retângulos, 23 cores. */
73
+ export const paintTileSurface = (px) => {
74
+ px(0, 0, 1, 1, '#8b8994');
75
+ px(1, 0, 3, 1, '#bbbac5');
76
+ px(4, 0, 2, 1, '#bdbdc8');
77
+ px(6, 0, 2, 1, '#a6a5b2');
78
+ px(8, 0, 1, 1, '#8b8994');
79
+ px(9, 0, 1, 1, '#b8b7c3');
80
+ px(10, 0, 4, 1, '#bbbac5');
81
+ px(14, 0, 2, 1, '#8b8994');
82
+ px(0, 1, 1, 1, '#b6b5c1');
83
+ px(1, 1, 6, 1, '#9b9aa8');
84
+ px(7, 1, 1, 1, '#74717d');
85
+ px(8, 1, 1, 1, '#bab8c3');
86
+ px(9, 1, 6, 1, '#9b9aa8');
87
+ px(15, 1, 1, 1, '#706e7a');
88
+ px(0, 2, 1, 1, '#b9b7c2');
89
+ px(1, 2, 6, 1, '#90909e');
90
+ px(7, 2, 1, 1, '#5e5a66');
91
+ px(8, 2, 1, 1, '#b9b7c2');
92
+ px(9, 2, 6, 1, '#90909e');
93
+ px(15, 2, 1, 1, '#585562');
94
+ px(0, 3, 1, 2, '#b8b6c1');
95
+ px(1, 3, 6, 3, '#828291');
96
+ px(7, 3, 1, 2, '#403b48');
97
+ px(8, 3, 1, 3, '#b8b6c1');
98
+ px(9, 3, 6, 3, '#828291');
99
+ px(15, 3, 1, 3, '#393543');
100
+ px(0, 5, 1, 1, '#b1b0bc');
101
+ px(7, 5, 1, 1, '#3e2e3f');
102
+ px(0, 6, 1, 3, '#403b48');
103
+ px(1, 6, 1, 1, '#777485');
104
+ px(2, 6, 4, 1, '#828291');
105
+ px(6, 6, 3, 3, '#403b48');
106
+ px(9, 6, 5, 1, '#828291');
107
+ px(14, 6, 2, 2, '#403b48');
108
+ px(1, 7, 5, 1, '#403b48');
109
+ px(9, 7, 5, 1, '#403b48');
110
+ px(1, 8, 1, 1, '#b1b0bc');
111
+ px(2, 8, 2, 1, '#b8b6c1');
112
+ px(4, 8, 1, 1, '#bcbdc9');
113
+ px(5, 8, 1, 1, '#b1b0bc');
114
+ px(9, 8, 1, 1, '#b1b0bc');
115
+ px(10, 8, 2, 1, '#b8b6c1');
116
+ px(12, 8, 1, 1, '#bcbdc9');
117
+ px(13, 8, 1, 1, '#b1b0bc');
118
+ px(14, 8, 1, 1, '#403b48');
119
+ px(15, 8, 1, 1, '#393543');
120
+ px(0, 9, 1, 5, '#b1b0bc');
121
+ px(1, 9, 6, 5, '#828291');
122
+ px(7, 9, 1, 3, '#403b48');
123
+ px(8, 9, 1, 5, '#b8b6c1');
124
+ px(9, 9, 6, 5, '#828291');
125
+ px(15, 9, 1, 2, '#403b48');
126
+ px(15, 11, 1, 3, '#393543');
127
+ px(7, 12, 1, 2, '#393543');
128
+ px(0, 14, 1, 2, '#403b48');
129
+ px(1, 14, 5, 1, '#828291');
130
+ px(6, 14, 3, 2, '#403b48');
131
+ px(9, 14, 5, 1, '#828291');
132
+ px(14, 14, 2, 2, '#403b48');
133
+ px(1, 15, 1, 1, '#444653');
134
+ px(2, 15, 4, 1, '#403b48');
135
+ px(9, 15, 5, 1, '#403b48');
136
+ };
137
+ /** Os dois tiles como canvas 16×16 — a forma que `worldCanvas` consumia das imagens carregadas. */
138
+ export function cityTiles() {
139
+ return { fill: pixelCanvas(16, 16, paintTileFill), surface: pixelCanvas(16, 16, paintTileSurface) };
140
+ }
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ type CrtCfg = {
3
+ scan: number;
4
+ vig: number;
5
+ round: number;
6
+ };
7
+ /** O CRT de fábrica. Ganhou nome porque o "restaurar padrões" do menu (ADR-0028) precisa do MESMO valor que
8
+ * a carga do boot usa quando nada foi salvo — duas cópias seriam duas chances de o reset devolver um CRT que
9
+ * o jogo nunca mostrou. Congelado: um padrão que alguém consiga escrever em tempo de execução não é padrão. */
10
+ export declare const CRT_DEFAULT: Readonly<CrtCfg>;
11
+ /** Liga a contagem de jogadores e a pergunta de a11y. Chamado uma vez pela raiz, antes do 1º `applyCrt()`. */
12
+ export declare function initCrt(deps: {
13
+ numJogadores: () => number;
14
+ a11yVisualAtiva: () => boolean;
15
+ }): void;
16
+ export declare const CRT: CrtCfg;
17
+ export declare function crtScanVars(): void;
18
+ export declare function applyCrt(): void;
19
+ export {};
@@ -0,0 +1,92 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/crt.ts — estética CRT (menu Sensibilidade visual): scanlines/vinheta/cantos, só CSS (classes em
3
+ // #game-region). Extraído do game.js (Estágio 4, Tier 1). CRT = config {scan,vig,round} (0=off,1,2; scan/vig são
4
+ // on/off) carregada do localStorage com migração do formato antigo booleano. crtScanVars ancora a scanline em
5
+ // PIXELS REAIS (recomputa da altura real do #game-region + dpr → 1 linha por pixel de arte, espaçamento regular).
6
+ // Auto-contido: depende de ui/dom ($). A contagem de jogadores entra por `initCrt` (ver abaixo).
7
+ import { $ } from '../ui/dom.js';
8
+ import { screenGrid } from '../core/screens.js';
9
+ import * as store from '../platform/storage.js';
10
+ // scanline LIGADA por padrão (decisão do José 2026-07-03). Migra incl_crt (booleano) → incl_crt2 (níveis 0..2).
11
+ /** O CRT de fábrica. Ganhou nome porque o "restaurar padrões" do menu (ADR-0028) precisa do MESMO valor que
12
+ * a carga do boot usa quando nada foi salvo — duas cópias seriam duas chances de o reset devolver um CRT que
13
+ * o jogo nunca mostrou. Congelado: um padrão que alguém consiga escrever em tempo de execução não é padrão. */
14
+ export const CRT_DEFAULT = Object.freeze({ scan: 1, vig: 0, round: 1 });
15
+ // A CONTAGEM DE JOGADORES entra por injeção desde 2026-08-26. Era `numPlayers`, um `let` de `core/state`
16
+ // importado como binding vivo — e um `let` de módulo é compartilhado por qualquer segundo jogo que a
17
+ // mesma página carregue (D13 do `demos`, ADR-0038). O que entra aqui é o GETTER da rodada que a raiz
18
+ // possui; o `let` que sobra guarda a função, não o número.
19
+ let _numJogadores = () => 1;
20
+ /**
21
+ * ALGUM jogador está num modo de acessibilidade visual? (qualquer coisa que não seja `normal`.)
22
+ *
23
+ * O ADR-0020 decide: "modos de a11y SUPRIMEM o CRT/efeitos decorativos — precedência a11y > estética". Isso
24
+ * nunca tinha sido implementado, e a emenda de 2026-08-26 mediu: com a vinheta ligada, `crt-vig-1` sobrevivia
25
+ * em `hc-direto`, `fix-deuter`, `lv-blur` e `blind`. Uma vinheta escurecendo as bordas trabalha contra o modo
26
+ * que existe para AUMENTAR contraste.
27
+ *
28
+ * `ALGUM` e não "o jogador 1": o CRT é decoração GLOBAL, uma só para a tela inteira. Não há como escurecer as
29
+ * bordas de meia tela. Se a decoração e a acessibilidade de qualquer criança se contradizem, quem cede é a
30
+ * decoração — que é literalmente o que "precedência a11y > estética" quer dizer.
31
+ */
32
+ let _a11yVisualAtiva = () => false;
33
+ /** Liga a contagem de jogadores e a pergunta de a11y. Chamado uma vez pela raiz, antes do 1º `applyCrt()`. */
34
+ export function initCrt(deps) {
35
+ _numJogadores = deps.numJogadores;
36
+ _a11yVisualAtiva = deps.a11yVisualAtiva;
37
+ }
38
+ export const CRT = (() => {
39
+ const d = { ...CRT_DEFAULT };
40
+ try {
41
+ const s = JSON.parse(store.get(store.KEYS.crt, null) || store.get(store.KEYS.crtLegacy, null) || 'null');
42
+ const fresh = !store.get(store.KEYS.crt, null); // migração p/ crt2: herda vig/round; scan volta ao padrão ON uma vez
43
+ if (s && typeof s === 'object')
44
+ for (const k in d)
45
+ if (k in s) {
46
+ const v = s[k];
47
+ if (fresh && k === 'scan')
48
+ continue;
49
+ d[k] = v === true ? (k === 'round' ? 2 : 1) : v === false ? (k === 'round' ? 1 : 0) : Math.max(0, Math.min(2, v | 0));
50
+ }
51
+ }
52
+ catch (e) { /* noop: file:// / modo privado */ }
53
+ d.scan = d.scan ? 1 : 0;
54
+ d.vig = d.vig ? 1 : 0; // scanlines/vinheta são ON/OFF (só cantos têm 3 níveis)
55
+ return d;
56
+ })();
57
+ // Ancora a scanline em px REAIS: 1 linha por pixel de ARTE (kDev inteiro) → espaçamento SEMPRE regular em qualquer dpr.
58
+ export function crtScanVars() {
59
+ const g = $('#game-region');
60
+ if (!g || !CRT.scan)
61
+ return;
62
+ const { rows } = screenGrid(_numJogadores()), dpr = window.devicePixelRatio || 1;
63
+ const perDev = Math.max(2, Math.round((g.clientHeight || 360) * dpr / (180 * rows))); // kDev = px REAIS por linha de arte (INTEIRO)
64
+ g.style.setProperty('--scan-per', (perDev / dpr) + 'px'); // período = kDev px reais (1 linha de arte)
65
+ g.style.setProperty('--scan-line', (Math.max(1, Math.round(dpr)) / dpr) + 'px'); // linha = 1 px REAL
66
+ }
67
+ // Aplica as classes CSS de CRT ao #game-region e persiste. Chamado no boot e ao mexer no menu.
68
+ export function applyCrt() {
69
+ const g = $('#game-region');
70
+ if (!g)
71
+ return;
72
+ ['crt-scan-1', 'crt-vig-1', 'crt-round-0', 'crt-round-2'].forEach((c) => g.classList.remove(c));
73
+ // OS DOIS EFEITOS CEDEM À ACESSIBILIDADE, SEM EXCEÇÃO (ADR-0020 + ADR-0047).
74
+ //
75
+ // Houve uma versão com uma chave de escape por efeito, a pedido do Dev. Ele a removeu depois de VER o
76
+ // resultado na tela: "Ceder fez muito bem ao jogo nos modos de acessibilidade". Fica anotado porque a
77
+ // ausência da chave é decisão, não esquecimento — e porque o pilar 2 volta a não ter exceção nenhuma.
78
+ //
79
+ // O valor gravado NÃO é alterado: a preferência continua lá e volta a valer sozinha ao sair do modo de
80
+ // acessibilidade. Suprimir não é desligar — é a distinção que impede a criança de perder o que escolheu
81
+ // toda vez que liga o alto contraste.
82
+ const a11y = _a11yVisualAtiva();
83
+ if (CRT.scan && !a11y) {
84
+ g.classList.add('crt-scan-' + CRT.scan);
85
+ crtScanVars();
86
+ }
87
+ if (CRT.vig && !a11y)
88
+ g.classList.add('crt-vig-' + CRT.vig);
89
+ if (CRT.round !== 1)
90
+ g.classList.add('crt-round-' + CRT.round); // 1 = visual padrão (8px), sem classe
91
+ store.setJSON(store.KEYS.crt, CRT);
92
+ }
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** As seis chaves de modo de daltonismo — as MESMAS chaves de `VIZ_MODES` em render/viz-modes. */
3
+ export type CvdKey = 'sim-protan' | 'sim-deuter' | 'sim-tritan' | 'fix-protan' | 'fix-deuter' | 'fix-tritan';
4
+ /** Ordem canônica: as três simulações (o que a pessoa vê) antes das três correções (o que a ajuda a ver). */
5
+ export declare const CVD_KEYS: readonly CvdKey[];
6
+ /**
7
+ * As matrizes, 4×5 em ordem de linha. Linha A = `0 0 0 1 0` em todas: nenhum dos seis modos mexe em alfa.
8
+ *
9
+ * SIMULAÇÃO (Machado, Oliveira & Fernandes 2009, severidade 1.0 — valores conferidos na página dos autores,
10
+ * UFRGS). CORREÇÃO (daltonização canônica): a linha R é identidade — não adianta modular o canal que a pessoa
11
+ * não distingue — e o erro é reinjetado em G e B, onde há discriminação; a soma de cada linha é 1, então
12
+ * branco e cinzas ficam preservados.
13
+ */
14
+ export declare const CVD_MATRIX: Record<CvdKey, readonly number[]>;
15
+ /**
16
+ * Modo → id do `<filter>` SVG. Os ids são API PÚBLICA deste módulo: `VIZ_FILTER` (render/viz-modes) pede o
17
+ * filtro pelo `url(#…)`, e é este mapa que promete que o filtro com aquele id vai existir no documento.
18
+ * Renomear um id aqui sem renomear lá apaga a canvas (referência de filtro inexistente não renderiza).
19
+ */
20
+ export declare const CVD_SVG_ID: Record<CvdKey, string>;
21
+ /** Atributo `values` de um `<feColorMatrix>`: as 4 linhas separadas por espaço duplo (legibilidade). PURA. */
22
+ export declare function cvdMatrixValues(k: CvdKey): string;
23
+ /**
24
+ * Gera os seis `<filter>` DENTRO do `<defs id="cvd-defs">` do index.html, a partir de `CVD_MATRIX`.
25
+ *
26
+ * Por que no boot e não escrito à mão no HTML: era exatamente a segunda cópia dos números. Construído com
27
+ * `createElementNS` (e não `innerHTML`) porque `<filter>`/`<feColorMatrix>` só funcionam no namespace SVG —
28
+ * com innerHTML o resultado depende do algoritmo de fragmento do navegador.
29
+ *
30
+ * Idempotente: esvazia o host antes de preencher, então chamar duas vezes não duplica ids (id duplicado faria
31
+ * o navegador escolher o primeiro — falha silenciosa outra vez).
32
+ *
33
+ * @returns quantos filtros foram instalados (0 = host ausente; o chamador decide se isso é fatal).
34
+ */
35
+ export declare function installCvdFilters(host: Element | null | undefined): number;
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/cvd-matrices — as SEIS matrizes de daltonismo (3 SIMULAÇÕES Machado 2009 sev. 1.0 + 3 CORREÇÕES
3
+ // C = I + M_err·(I − Sim), M_err de Fidaner et al.), num só lugar. Módulo-folha: ZERO dependências.
4
+ //
5
+ // A DUPLICAÇÃO QUE ESTE MÓDULO CURA (a sétima do refactor). Os mesmos 120 números existiam escritos DUAS
6
+ // vezes, em linguagens diferentes:
7
+ // · `<feColorMatrix values="…">` em app/index.html — caminho de TELA ÚNICA (`filter: url(#cvd-…)` na
8
+ // <canvas>); render/viz-modes só guarda o `url(#cvd-deuter)`, nunca os números.
9
+ // · `PIXI.ColorMatrixFilter` dentro de `pixiFilterFor` no game.js — caminho MULTI-TELA (filtro GPU por
10
+ // viewport, um modo por jogador).
11
+ // Batiam por sorte: nada ligava as duas cópias. Se divergissem, o sintoma seria SILENCIOSO e de
12
+ // ACESSIBILIDADE — a MESMA pessoa daltônica veria cores diferentes em tela única e em multi-tela, sem erro,
13
+ // sem log, sem teste vermelho. Agora os dois caminhos leem daqui: `render/viewports` monta o
14
+ // ColorMatrixFilter com estes arrays, e `installCvdFilters()` GERA os seis `<filter>` do index.html no boot
15
+ // a partir dos mesmos arrays (o HTML só guarda o `<defs id="cvd-defs">` vazio que os recebe).
16
+ //
17
+ // Este é um módulo-folha de propósito (mesmo formato de render/hc-role-data): um arquivo de dados sem
18
+ // dependência nenhuma pode ser lido tanto pelo pipeline de render quanto pelo boot do documento sem arrastar
19
+ // PixiJS para dentro do HTML nem o DOM para dentro do render.
20
+ //
21
+ // LAYOUT: 20 números = 4 linhas de 5 (R, G, B, A), em ordem de linha. É EXATAMENTE o mesmo layout nos dois
22
+ // destinos — `feColorMatrix type="matrix"` e `PIXI.ColorMatrixFilter#matrix` — por isso o array serve aos
23
+ // dois sem conversão. Aplicados em sRGB nos dois caminhos (aproximação padrão da web; ver a decisão 2 da
24
+ // pesquisa). Fonte primária e a conferência valor a valor: docs/research/PESQUISA-DALTONIZACAO.md.
25
+ /** Ordem canônica: as três simulações (o que a pessoa vê) antes das três correções (o que a ajuda a ver). */
26
+ export const CVD_KEYS = ['sim-protan', 'sim-deuter', 'sim-tritan', 'fix-protan', 'fix-deuter', 'fix-tritan'];
27
+ /**
28
+ * As matrizes, 4×5 em ordem de linha. Linha A = `0 0 0 1 0` em todas: nenhum dos seis modos mexe em alfa.
29
+ *
30
+ * SIMULAÇÃO (Machado, Oliveira & Fernandes 2009, severidade 1.0 — valores conferidos na página dos autores,
31
+ * UFRGS). CORREÇÃO (daltonização canônica): a linha R é identidade — não adianta modular o canal que a pessoa
32
+ * não distingue — e o erro é reinjetado em G e B, onde há discriminação; a soma de cada linha é 1, então
33
+ * branco e cinzas ficam preservados.
34
+ */
35
+ export const CVD_MATRIX = {
36
+ 'sim-protan': [0.152286, 1.052583, -0.204868, 0, 0, 0.114503, 0.786281, 0.099216, 0, 0, -0.003882, -0.048116, 1.051998, 0, 0, 0, 0, 0, 1, 0],
37
+ 'sim-deuter': [0.367322, 0.860646, -0.227968, 0, 0, 0.280085, 0.672501, 0.047413, 0, 0, -0.011820, 0.042940, 0.968881, 0, 0, 0, 0, 0, 1, 0],
38
+ 'sim-tritan': [1.255528, -0.076749, -0.178779, 0, 0, -0.078411, 0.930809, 0.147602, 0, 0, 0.004733, 0.691367, 0.303900, 0, 0, 0, 0, 0, 1, 0],
39
+ 'fix-protan': [1, 0, 0, 0, 0, 0.478897, 0.476911, 0.044192, 0, 0, 0.597282, -0.688692, 1.091410, 0, 0, 0, 0, 0, 1, 0],
40
+ 'fix-deuter': [1, 0, 0, 0, 0, 0.162790, 0.725047, 0.112165, 0, 0, 0.454695, -0.645392, 1.190697, 0, 0, 0, 0, 0, 1, 0],
41
+ 'fix-tritan': [1, 0, 0, 0, 0, -0.100459, 1.122915, -0.022457, 0, 0, -0.183603, -0.637643, 1.821245, 0, 0, 0, 0, 0, 1, 0],
42
+ };
43
+ /**
44
+ * Modo → id do `<filter>` SVG. Os ids são API PÚBLICA deste módulo: `VIZ_FILTER` (render/viz-modes) pede o
45
+ * filtro pelo `url(#…)`, e é este mapa que promete que o filtro com aquele id vai existir no documento.
46
+ * Renomear um id aqui sem renomear lá apaga a canvas (referência de filtro inexistente não renderiza).
47
+ */
48
+ export const CVD_SVG_ID = {
49
+ 'sim-protan': 'cvd-protan', 'sim-deuter': 'cvd-deuter', 'sim-tritan': 'cvd-tritan',
50
+ 'fix-protan': 'cvd-fix-protan', 'fix-deuter': 'cvd-fix-deuter', 'fix-tritan': 'cvd-fix-tritan',
51
+ };
52
+ /** Atributo `values` de um `<feColorMatrix>`: as 4 linhas separadas por espaço duplo (legibilidade). PURA. */
53
+ export function cvdMatrixValues(k) {
54
+ const m = CVD_MATRIX[k];
55
+ return [0, 5, 10, 15].map((i) => m.slice(i, i + 5).join(' ')).join(' ');
56
+ }
57
+ const SVG_NS = 'http://www.w3.org/2000/svg';
58
+ /**
59
+ * Gera os seis `<filter>` DENTRO do `<defs id="cvd-defs">` do index.html, a partir de `CVD_MATRIX`.
60
+ *
61
+ * Por que no boot e não escrito à mão no HTML: era exatamente a segunda cópia dos números. Construído com
62
+ * `createElementNS` (e não `innerHTML`) porque `<filter>`/`<feColorMatrix>` só funcionam no namespace SVG —
63
+ * com innerHTML o resultado depende do algoritmo de fragmento do navegador.
64
+ *
65
+ * Idempotente: esvazia o host antes de preencher, então chamar duas vezes não duplica ids (id duplicado faria
66
+ * o navegador escolher o primeiro — falha silenciosa outra vez).
67
+ *
68
+ * @returns quantos filtros foram instalados (0 = host ausente; o chamador decide se isso é fatal).
69
+ */
70
+ export function installCvdFilters(host) {
71
+ if (!host)
72
+ return 0;
73
+ const doc = host.ownerDocument;
74
+ if (!doc)
75
+ return 0;
76
+ while (host.firstChild)
77
+ host.removeChild(host.firstChild);
78
+ let n = 0;
79
+ for (const k of CVD_KEYS) {
80
+ const f = doc.createElementNS(SVG_NS, 'filter');
81
+ f.setAttribute('id', CVD_SVG_ID[k]);
82
+ f.setAttribute('color-interpolation-filters', 'sRGB'); // idem PIXI: os dois caminhos operam em sRGB
83
+ const fe = doc.createElementNS(SVG_NS, 'feColorMatrix');
84
+ fe.setAttribute('type', 'matrix');
85
+ fe.setAttribute('values', cvdMatrixValues(k));
86
+ f.appendChild(fe);
87
+ host.appendChild(f);
88
+ n++;
89
+ }
90
+ return n;
91
+ }