@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,103 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/set-cenario — TROCAR DE CENÁRIO: a única porta por onde o jogo muda de mundo visual.
3
+ //
4
+ // `setCenario(tema)` é chamado de quatro lugares muito diferentes — o splash (a criança escolhe antes de
5
+ // começar), a demonstração automática (game/attract.ts sorteia), o console de testes (`window.__incl`) e o
6
+ // BOOT (restauração do tema salvo em `incl_cenario`) — e sempre faz a mesma sequência de cinco passos. Esta
7
+ // função é ORQUESTRAÇÃO: ela não desenha nada, não gera textura nenhuma e não sabe fórmula alguma. Ela decide
8
+ // QUEM é avisado, EM QUE ORDEM, e o que fazer quando a resposta demora.
9
+ //
10
+ // ======================= POR QUE ISTO É UM MÓDULO, E NÃO FIAÇÃO NO game.js =======================
11
+ // A tentação era deixá-la no monólito como cola: quase toda linha dela chama alguém de fora, e o contexto
12
+ // injetado aqui é largo. Três coisas decidiram o contrário, e nenhuma delas é estética:
13
+ //
14
+ // 1. A GUARDA DE CORRIDA. Os tiles do tema (`tile_fill.png`/`tile_surface.png`) chegam quando chegarem, e
15
+ // entre o pedido e a chegada a pessoa pode ter trocado de cenário de novo — no splash isso é UM segundo
16
+ // de distância, e na demonstração automática o troca-troca é o comportamento normal. Por isso o `.then`
17
+ // abre com `if (getCenario() !== theme) return`: um tileset atrasado do Campo não pode repintar o chão da
18
+ // Floresta. É a espécie de defeito que aparece uma vez a cada cem trocas, some quando alguém vai olhar, e
19
+ // não tem como ser reproduzido de mão com confiança. Fora de um módulo, ele não tem como ter teste.
20
+ // 2. A ORDEM DOS AVISOS. `setCenarioValue` (core/state.js — valor, persistência e evento) vem ANTES de
21
+ // qualquer trabalho de textura, e é isso que faz a guarda de corrida do item 1 funcionar: quem compara
22
+ // `getCenario() !== theme` está comparando com o valor que ESTA chamada gravou. Inverter as duas linhas
23
+ // não quebra nada visível e desarma a guarda inteira, silenciosamente.
24
+ // 3. A VALIDAÇÃO. "Tema desconhecido → 'cidade'" é a rede que segura um `incl_cenario` de uma versão antiga
25
+ // (e o boot ainda migra a chave 'noite' → 'espaco', do lado do game.js). Ela virou
26
+ // `normalizarCenario` em render/cenario-data.ts, com o comportamento atual PREGADO por teste, defeito de
27
+ // herança de protótipo incluído — ver o cabeçalho de lá.
28
+ //
29
+ // ======================= OS CINCO PASSOS, E POR QUE NESTA ORDEM =======================
30
+ // 1. valida o tema → `normalizarCenario`
31
+ // 2. grava e persiste → `setCenarioValue` (core/state.js). ANTES do resto: arma as guardas de corrida.
32
+ // 3. veste o FUNDO → `aplicarTemaParallax` (render/parallax.ts) — síncrono nos temas v3, PNG na Cidade
33
+ // 4. pede os TILES do tema → assíncrono; ao chegar, refaz a textura do MUNDO e reaplica os modos de visão
34
+ // 5. avisa quem depende → vida ambiente da Cidade (síncrono) e alto contraste (síncrono)
35
+ // O passo 5 roda ANTES de o passo 4 terminar, e isso é de propósito: a vida ambiente e o recolor não esperam
36
+ // download nenhum. O passo 4 reaplica o recolor DE NOVO quando os tiles chegam, porque aí a textura do mundo
37
+ // mudou — os dois `reapplyVizAll` não são duplicação, são dois momentos.
38
+ //
39
+ // ======================= O QUE FICOU DE FORA, E POR QUÊ =======================
40
+ // · O DADO dos temas → render/cenario-data.ts (folha, sem dependência). Importado aqui, não injetado: é um
41
+ // módulo sem efeito nenhum, então importá-lo não cria risco de ordem de boot.
42
+ // · A geometria e a pintura das 3 camadas de fundo → render/parallax.ts. Este módulo só chama
43
+ // `aplicarTemaParallax` e não sabe o que é `tilePosition`.
44
+ // · As FÓRMULAS de céu/morros/placeholder → render/scene-parallax.ts (desde o Estágio 4).
45
+ // · O BUILDER da textura do mundo (`worldCanvas`) → render/world-tex.ts. Aqui só entra o resultado.
46
+ // · A migração da chave antiga ('noite' → 'espaco') e o `try/catch` do boot FICAM no game.js: são política de
47
+ // persistência e rede de segurança do arranque, não regra de cenário.
48
+ // · `worldCanvasNormal`/`worldTexNormal` continuam sendo `let` do game.js — são lidos por
49
+ // render/high-contrast (por getter) e pelo render estático. Este módulo os ESCREVE por um único
50
+ // `setWorldTextures(canvas, textura)`, para que a dupla nunca fique meio trocada.
51
+ //
52
+ // ======================= ⚠️ A ARMADILHA DE BOOT DESTA REGIÃO =======================
53
+ // O `setCenario` do boot é chamado MUITO cedo — logo depois de `initViewports`, e antes de metade do jogo
54
+ // existir. O monólito o embrulha num `try/catch` que cai para 'cidade' EM SILÊNCIO, e é por isso que uma
55
+ // dependência ausente aqui não vira erro: vira "o tema que a criança escolheu não voltou, e ninguém sabe por
56
+ // quê". Todas as dependências que ainda não existem naquele instante entram por GETTER ou por CALLBACK, nunca
57
+ // por valor, exatamente para que a montagem do ctx não toque em `const` em TDZ:
58
+ // · `isVizReady`/`reapplyVizAll` — `vizReady` só vira `true` lá no fim do boot; no arranque as duas voltas
59
+ // de `reapplyVizAll` são puladas, e quem pinta é o `else if (worldSprite)` do passo 4.
60
+ // · `getWorldSprite` — o sprite do mundo nasce DEPOIS deste ponto no game.js. Só é tocado dentro do `.then`,
61
+ // que é assíncrono, mas o getter garante que a montagem do ctx não o dereferencie.
62
+ // · `isVidaReady`/`applyCenarioVida` — `sceneCity` nasce centenas de linhas depois; `_vidaReady` é `false`
63
+ // no boot justamente para que esta chamada seja pulada, e o game.js chama `applyCenarioVida()` uma vez, à
64
+ // mão, quando a cena da cidade fica pronta.
65
+ // · `clearWorldTexCache` vem de render/high-contrast, que é um módulo de verdade e já existe no import.
66
+ //
67
+ // SEM I/O NO IMPORT, e agora sem I/O NENHUM: `carregarTilesDoTema` (a carga assíncrona dos PNG de tile, com a
68
+ // guarda de corrida que ela exigia) foi APAGADA no item 17 — os tiles da Cidade viraram dados em
69
+ // `render/city-tiles`. O corpo do módulo não pede nada, e a troca de cenário virou síncrona.
70
+ // Ver docs/5-Refactoring/plano-modularizacao-mapa.md (D2-b).
71
+ import { CENARIOS, normalizarCenario } from './cenario-data.js';
72
+ export function createSetCenario(ctx) {
73
+ function setCenario(theme) {
74
+ const tema = normalizarCenario(theme);
75
+ ctx.setCenarioValue(tema); // ANTES do trabalho de textura: é o valor com que as guardas de corrida comparam
76
+ const T = CENARIOS[tema];
77
+ ctx.aplicarTemaParallax(tema, T); // as 3 camadas de fundo (render/parallax.ts)
78
+ // TILES do tema: SÍNCRONOS. v3: blocos Clarity SEM recolor (o `worldCanvas` cai no desenho da v3 com null).
79
+ //
80
+ // A GUARDA DE CORRIDA MORREU JUNTO COM A CORRIDA. Havia um `if (getCenario() !== tema) return` aqui, e ele
81
+ // estava certo enquanto os tiles chegavam por `Promise`: trocar de cenário durante o download deixava a
82
+ // resposta antiga pintar por cima da nova. Sem download não há "durante", e guarda contra corrida que não
83
+ // existe mais é código que só pode confundir quem o ler depois.
84
+ const tiles = ctx.getTiles(tema);
85
+ const cv = ctx.worldCanvas(tiles), t = ctx.tex(cv);
86
+ ctx.setWorldTextures(cv, t);
87
+ ctx.clearWorldTexCache();
88
+ if (ctx.isVizReady())
89
+ ctx.reapplyVizAll();
90
+ else {
91
+ const sprite = ctx.getWorldSprite(); // no boot o recolor ainda não existe: pinta o sprite direto
92
+ if (sprite)
93
+ sprite.texture = t;
94
+ }
95
+ // (o Cenário saiu do menu de pausa — a escolha é do J1 no splash, antes de começar)
96
+ if (ctx.isVidaReady())
97
+ ctx.applyCenarioVida(); // liga/desliga carros/deco da cidade e semeia o tema
98
+ if (ctx.isVizReady())
99
+ ctx.reapplyVizAll(); // reaplica o cenário recolorido (só após o init montar tudo)
100
+ // incl_cenario é persistido por setCenarioValue (core/state.js)
101
+ }
102
+ return { setCenario };
103
+ }
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ export declare function spriteToCanvas(art: string[]): HTMLCanvasElement;
3
+ export declare function outlineCanvas(src: HTMLCanvasElement, thick: number): HTMLCanvasElement;
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/sprite-fx.ts — helpers de canvas p/ SPRITES/silhuetas, sobre render/canvas.ts. Módulo-folha (só makeCanvas):
3
+ // • spriteToCanvas: pinta arte ASCII 16×32 (1 char = 1 px) pela paleta unificada APP.
4
+ // • outlineCanvas: contorno ESCURO único atrás da arte (1º plano do alto contraste) — silhueta deslocada em anel
5
+ // + arte por cima (sem cortar vãos internos). APP / _silhouette / OUTLINE_DARK são privados. (Fase 2.21)
6
+ import { makeCanvas } from './canvas.js';
7
+ // Paleta UNIFICADA (E15) — luz de cima-esquerda, contorno escuro. H cabelo · S pele · D pele-sombra · K contorno/olho
8
+ // · W branco · R camisa · T camisa-sombra · B calça · P calça-sombra.
9
+ const APP = { H: '#403020', S: '#c08070', D: '#9a5f50', K: '#1a1420', W: '#e8eef0', R: '#3090d0', T: '#2566a0', B: '#303050', P: '#20203a' };
10
+ export function spriteToCanvas(art) {
11
+ const cv = makeCanvas(16, 32), c = cv.getContext('2d');
12
+ for (let y = 0; y < 32; y++) {
13
+ const row = art[y];
14
+ if (!row)
15
+ continue;
16
+ for (let x = 0; x < 16; x++) {
17
+ const ch = row[x];
18
+ if (ch === '.' || !ch)
19
+ continue;
20
+ c.fillStyle = APP[ch] || '#f0f';
21
+ c.fillRect(x, y, 1, 1);
22
+ }
23
+ }
24
+ return cv;
25
+ }
26
+ const OUTLINE_DARK = '#0a0a08'; // contorno escuro (José refará o gráfico visando 3:1 depois)
27
+ function _silhouette(src, color) { const cv = makeCanvas(src.width, src.height), c = cv.getContext('2d'); c.drawImage(src, 0, 0); c.globalCompositeOperation = 'source-in'; c.fillStyle = color; c.fillRect(0, 0, cv.width, cv.height); return cv; }
28
+ // Contorno ESCURO único: preserva a arte (contorno ATRÁS, arte por cima → só aparece nas bordas externas, sem cortar vãos).
29
+ export function outlineCanvas(src, thick) {
30
+ const cv = makeCanvas(src.width, src.height), c = cv.getContext('2d');
31
+ const dark = _silhouette(src, OUTLINE_DARK), r = thick >= 2 ? 2 : 1;
32
+ for (let dx = -r; dx <= r; dx++)
33
+ for (let dy = -r; dy <= r; dy++) {
34
+ if (!dx && !dy)
35
+ continue;
36
+ c.drawImage(dark, dx, dy);
37
+ } // anel escuro
38
+ c.drawImage(src, 0, 0);
39
+ return cv; // arte por cima → sem corte interno
40
+ }
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { tex } from './canvas.js';
3
+ type Tex = ReturnType<typeof tex>;
4
+ /** Alto-contraste plumbing this module needs but does not own — game.js keeps the mutable source of truth
5
+ * (world/coin/tree/parallax texture pipelines share the same DIRECT_CFG/directSpriteCanvas; `disp` also
6
+ * serves quiz rendering outside textures). Call once at boot, before the first letterTexture/pupTexFor. */
7
+ export interface TexturesCtx {
8
+ /**
9
+ * IDS DAS FORMAS a pré-desenhar. Eram importados de `game/activity-content` (as dez formas da atividade de
10
+ * soma e subtração) — uma aresta de importação da ENGINE para o JOGO por causa de UMA linha do `initTextures`.
11
+ *
12
+ * O ADR-0027 conta essas arestas porque elas decidem o que um pacote arrasta: um módulo de render que importa
13
+ * conteúdo de matemática não se empacota sem levar o currículo junto. E o custo escondido era maior que a
14
+ * aresta — esta era também a única razão de `render/viz-setters` arrastar `game/`, por transitividade.
15
+ *
16
+ * Só os IDS, e não a lista de formas: este módulo desenha por id (`shapeTexture(id)`) e nunca leu o nome em
17
+ * português. Pedir a estrutura inteira seria pedir mais do que se usa, que é como uma aresta volta.
18
+ */
19
+ shapes: readonly string[];
20
+ /**
21
+ * OS PODERES a pré-desenhar, com a arte pronta. Mesma razão de `shapes`, um passo adiante: além da lista,
22
+ * entra o CANVAS de cada um — porque o ícone de voo, o de ventosa e o da bengala de corrida são arte deste
23
+ * jogo, e não geometria que a engine saiba desenhar sozinha.
24
+ *
25
+ * Era `PUP_KINDS` cravado aqui + `import { powerupCanvas } from './props.js'`. A arte mudou de camada no
26
+ * item 19 (`game/props`), e esta era a única aresta que a mudança criaria — some por injeção, e não por
27
+ * mudança de endereço.
28
+ */
29
+ powerups: readonly {
30
+ readonly kind: string;
31
+ readonly canvas: HTMLCanvasElement;
32
+ }[];
33
+ disp: (s: string) => string;
34
+ directCfg: Record<string, {
35
+ off: number;
36
+ mul: number;
37
+ bgMul: number;
38
+ }>;
39
+ directSpriteCanvas: (src: HTMLCanvasElement, mode: string) => HTMLCanvasElement;
40
+ }
41
+ /** Vertex list for the polygon shapes (PURE — no canvas). null = drawn via an arc/ellipse/rect primitive instead. */
42
+ export declare function shapePoints(id: string, cx: number, cy: number, r: number): [number, number][] | null;
43
+ /** 16×16 icon for a soma/subtração shape id (cyan fill + dark outline). */
44
+ export declare function shapeTexture(id: string): Tex;
45
+ /** Cache keyed by shape id — filled by initTextures (the injected id list never changes at runtime). */
46
+ export declare const SHAPE_TEX: Record<string, Tex>;
47
+ /** 16×16 rounded-square tile with a single letter (uses the injected `disp`, which honors letterCase). */
48
+ export declare function letterTexture(ch: string): Tex;
49
+ /** Normal-mode cache — filled by initTextures. */
50
+ export declare const PUP_TEX: Record<string, Tex>;
51
+ /** Power-up texture for `mode` (normal or alto-contraste-direto). Cached per mode×kind. */
52
+ export declare function pupTexFor(kind: string, mode: string): Tex;
53
+ /** Drops the alto-contraste power-up cache — game.js's _rebakeDirect clears world/coin/pup/player caches
54
+ * together whenever outline thickness or role colors change. Call it from there instead of touching a private. */
55
+ export declare function resetPupTexCache(): void;
56
+ /** Fills SHAPE_TEX/PUP_TEX (+ wires the alto-contraste deps). Call ONCE at boot, before the first rebuildCoins/
57
+ * rebuildExtras — mirrors game.js's old eager `const SHAPE_TEX=...; SOMASUB_SHAPES.forEach(...)` / PUP_CANVAS
58
+ * forEach, just deferred past import time so this module stays side-effect-free to import (node tests included). */
59
+ export declare function initTextures(ctx: TexturesCtx): void;
60
+ export declare const PIP_W = 24, PIP_H = 32;
61
+ export declare const PIP_PAL: string[];
62
+ export declare const PIP_IDLE: string[];
63
+ export declare const PIP_WALK: string[][];
64
+ /** Valid palette digit (7 = transparent background, skipped); robust to short/ragged rows. */
65
+ export declare const isPix: (ch: string) => boolean;
66
+ export declare function indexedToCanvas(rows: string[]): HTMLCanvasElement;
67
+ export declare function silhouetteCanvasIdx(rows: string[]): HTMLCanvasElement;
68
+ export declare const PLAYER_IDLE: string[];
69
+ export declare const PLAYER_WALK: string[];
70
+ export declare const PLAYER_CLIMB: string[];
71
+ export declare const PLAYER_HURT: string[];
72
+ /** Verbatim reproduction of game.js's old eager `const TEX={idle:tex(spriteToCanvas(PLAYER_IDLE)),...}` — never
73
+ * auto-run (see header note above). Unused today; kept callable in case a future caller resurfaces. */
74
+ export declare function buildLegacyPlayerTex(): Record<string, Tex>;
75
+ export {};
@@ -0,0 +1,240 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/textures — procedural texture generators for in-world GLYPHS: soma/subtração SHAPES, sílabas LETTERS,
3
+ // and power-up ICONS (+ their alto-contraste variant). Verbatim from game.js (behavior-preserving). `disp`
4
+ // (letterCase-aware) and the DIRECT_CFG/directSpriteCanvas alto-contraste plumbing are SHARED with other texture
5
+ // pipelines (world/coin/tree/parallax) that stay in game.js — INJECTED via initTextures, never read from a
6
+ // global. Caches are filled BY initTextures (not at import time) so importing this module stays I/O-free, same
7
+ // discipline as render/sprites.ts (Fase 2.24). See docs/5-Refactoring/plano-modularizacao-mapa.md.
8
+ import { makeCanvas, tex } from './canvas.js';
9
+ import { spriteToCanvas } from './sprite-fx.js';
10
+ let disp = (s) => String(s).toLowerCase(); // safe pre-init default (matches letterCase='lower')
11
+ let directCfg = {};
12
+ let directSpriteCanvas = (src) => src; // safe pre-init default (fg=0 → sem contorno)
13
+ /* ===================== soma/subtração: formas ===================== */
14
+ /** Vertex list for the polygon shapes (PURE — no canvas). null = drawn via an arc/ellipse/rect primitive instead. */
15
+ export function shapePoints(id, cx, cy, r) {
16
+ switch (id) {
17
+ case 'triangulo': return [[cx, cy - r], [cx + r, cy + r], [cx - r, cy + r]];
18
+ case 'losango': return [[cx, cy - r], [cx + r, cy], [cx, cy + r], [cx - r, cy]];
19
+ case 'paralelogramo': return [[cx - r + 3, cy - r * 0.6], [cx + r, cy - r * 0.6], [cx + r - 3, cy + r * 0.6], [cx - r, cy + r * 0.6]];
20
+ case 'trapezio': return [[cx - r * 0.5, cy - r * 0.7], [cx + r * 0.5, cy - r * 0.7], [cx + r, cy + r * 0.7], [cx - r, cy + r * 0.7]];
21
+ case 'pentagono': return Array.from({ length: 5 }, (_, i) => { const a = -Math.PI / 2 + i * 2 * Math.PI / 5; return [cx + r * Math.cos(a), cy + r * Math.sin(a)]; });
22
+ case 'hexagono': return Array.from({ length: 6 }, (_, i) => { const a = i * 2 * Math.PI / 6; return [cx + r * Math.cos(a), cy + r * Math.sin(a)]; });
23
+ default: return null;
24
+ }
25
+ }
26
+ /** 16×16 icon for a soma/subtração shape id (cyan fill + dark outline). */
27
+ export function shapeTexture(id) {
28
+ const cv = makeCanvas(16, 16), c = cv.getContext('2d');
29
+ c.fillStyle = '#7fdcff';
30
+ c.strokeStyle = '#04121a';
31
+ c.lineWidth = 1.5;
32
+ const cx = 8, cy = 8, r = 6;
33
+ c.beginPath();
34
+ const pts = shapePoints(id, cx, cy, r);
35
+ if (pts) {
36
+ pts.forEach(([x, y], i) => c[i ? 'lineTo' : 'moveTo'](x, y));
37
+ c.closePath();
38
+ }
39
+ else
40
+ switch (id) {
41
+ case 'circulo':
42
+ c.arc(cx, cy, r, 0, 7);
43
+ break;
44
+ case 'oval':
45
+ c.ellipse(cx, cy, r, r * 0.66, 0, 0, 7);
46
+ break;
47
+ case 'quadrado':
48
+ c.rect(cx - r, cy - r, 2 * r, 2 * r);
49
+ break;
50
+ case 'retangulo':
51
+ c.rect(cx - r, cy - r * 0.6, 2 * r, r * 1.2);
52
+ break;
53
+ default: c.arc(cx, cy, r, 0, 7);
54
+ }
55
+ c.fill();
56
+ c.stroke();
57
+ return tex(cv);
58
+ }
59
+ /** Cache keyed by shape id — filled by initTextures (the injected id list never changes at runtime). */
60
+ export const SHAPE_TEX = {};
61
+ /* ===================== sílabas: letra ===================== */
62
+ /** 16×16 rounded-square tile with a single letter (uses the injected `disp`, which honors letterCase). */
63
+ export function letterTexture(ch) {
64
+ const cv = makeCanvas(16, 16), c = cv.getContext('2d');
65
+ c.fillStyle = '#ffd23f';
66
+ c.strokeStyle = '#1a1400';
67
+ c.lineWidth = 1.5;
68
+ c.beginPath();
69
+ if (c.roundRect)
70
+ c.roundRect(2, 2, 12, 12, 3);
71
+ else
72
+ c.rect(2, 2, 12, 12);
73
+ c.fill();
74
+ c.stroke();
75
+ c.fillStyle = '#1a1400';
76
+ c.font = 'bold 11px system-ui,sans-serif';
77
+ c.textAlign = 'center';
78
+ c.textBaseline = 'middle';
79
+ c.fillText(disp(ch), 8, 9);
80
+ return tex(cv);
81
+ }
82
+ /* ===================== power-ups: ícone (+ alto contraste) ===================== */
83
+ //
84
+ // A LISTA E A ARTE ENTRAM JUNTAS, e nenhuma das duas mora aqui (item 19). Era
85
+ // const PUP_KINDS = ['superjump', 'ultrajump', 'turbo', 'fly', 'wallcling', 'key', 'runcane'] as const;
86
+ // mais `import { powerupCanvas } from './props.js'` — os sete poderes DESTE jogo e o desenho deles, dentro de
87
+ // um módulo de render. É o mesmo defeito que `SHAPE_TEX` tinha e pelo qual `shapes` já entra por ctx: um
88
+ // segundo jogo herdava sete ícones que não são dele e não tinha como não herdar.
89
+ //
90
+ // A diferença em relação a `shapes` vale nota: das formas, a engine recebe só os IDS e DESENHA (`shapeTexture`
91
+ // é geometria — polígono, arco, retângulo — e serve a qualquer jogo). Dos power-ups ela recebe o CANVAS
92
+ // PRONTO, porque uma ventosa, uma asa e uma bengala de corrida não são geometria: são arte, e arte é do jogo.
93
+ const PUP_CANVAS = {};
94
+ /** Normal-mode cache — filled by initTextures. */
95
+ export const PUP_TEX = {};
96
+ const _pupTexHC = {}; // {mode:{kind:tex}} — outlined alto-contraste variant
97
+ /** Power-up texture for `mode` (normal or alto-contraste-direto). Cached per mode×kind. */
98
+ export function pupTexFor(kind, mode) {
99
+ const hc = (_pupTexHC[mode] = _pupTexHC[mode] || {});
100
+ if (directCfg[mode]) {
101
+ if (!hc[kind])
102
+ hc[kind] = tex(directSpriteCanvas(PUP_CANVAS[kind], mode));
103
+ return hc[kind];
104
+ }
105
+ return PUP_TEX[kind];
106
+ }
107
+ /** Drops the alto-contraste power-up cache — game.js's _rebakeDirect clears world/coin/pup/player caches
108
+ * together whenever outline thickness or role colors change. Call it from there instead of touching a private. */
109
+ export function resetPupTexCache() {
110
+ for (const k in _pupTexHC)
111
+ delete _pupTexHC[k];
112
+ }
113
+ /** Fills SHAPE_TEX/PUP_TEX (+ wires the alto-contraste deps). Call ONCE at boot, before the first rebuildCoins/
114
+ * rebuildExtras — mirrors game.js's old eager `const SHAPE_TEX=...; SOMASUB_SHAPES.forEach(...)` / PUP_CANVAS
115
+ * forEach, just deferred past import time so this module stays side-effect-free to import (node tests included). */
116
+ export function initTextures(ctx) {
117
+ disp = ctx.disp;
118
+ directCfg = ctx.directCfg;
119
+ directSpriteCanvas = ctx.directSpriteCanvas;
120
+ // LIMPA ANTES DE ENCHER. Enquanto as formas eram uma tabela FIXA importada, o cache só podia ser preenchido
121
+ // com o mesmo conteúdo e a diferença nunca apareceu — a função é chamada uma vez no boot. Com a lista
122
+ // INJETADA, "encher" tinha de deixar de significar "acrescentar": um segundo consumidor que inicializasse
123
+ // com as formas dele herdaria as nossas por cima, e o sintoma seria texturas de matemática num jogo que não
124
+ // tem matemática. Achado por um teste meu que afirmava a coisa errada e reprovou por isso.
125
+ for (const k of Object.keys(SHAPE_TEX))
126
+ delete SHAPE_TEX[k];
127
+ for (const id of ctx.shapes)
128
+ SHAPE_TEX[id] = shapeTexture(id);
129
+ // Mesma regra de limpar-antes-de-encher, pelo mesmo motivo do bloco acima: com a lista INJETADA, um segundo
130
+ // consumidor herdaria os poderes do primeiro por cima dos dele.
131
+ for (const k of Object.keys(PUP_CANVAS))
132
+ delete PUP_CANVAS[k];
133
+ for (const k of Object.keys(PUP_TEX))
134
+ delete PUP_TEX[k];
135
+ for (const p of ctx.powerups) {
136
+ PUP_CANVAS[p.kind] = p.canvas;
137
+ PUP_TEX[p.kind] = tex(p.canvas);
138
+ }
139
+ }
140
+ /* ===================== personagem: arte indexada (DEFERRED — não é código morto) =====================
141
+ FASE ATUAL: o jogo usa o PIXEL ART do PixelLab DIRETO (PNG nativo por quadro, render/sprites.ts — Fase 2.24).
142
+ A conversão deste personagem PROCEDURAL SEMÂNTICO/indexado (PIP_* abaixo) para a arte real do jogo "fica para
143
+ uma fase posterior" — comentário original de game.js, preservado aqui junto do código. ZERO chamadores hoje
144
+ (indexedToCanvas/silhouetteCanvasIdx não são invocadas por ninguém) — isso é trabalho ESTACIONADO de propósito,
145
+ não descartado. Ver docs/plano-arte-procedural.md. */
146
+ export const PIP_W = 24, PIP_H = 32;
147
+ export const PIP_PAL = ['#fcd7ab', '#e2aa86', '#9d6e62', '#5d2e22', '#472a1e', '#231c1c', '#1e1011', '#ff00ff', '#050809', '#010909'];
148
+ export const PIP_IDLE = ['.....99444446666888.....', '...9444444444444448.....', '..924444444444444488....', '.9422442444444444439....', '.94424222442424495338...', '894444444424224495348...', '964664444444444915548...', '966664455444449101658...', '966666595689998000859...', '966659986991891000859...', '96656919911011009819....', '.99969111890000910119...', '990099198999008998819...', '902281890990001920929...', '91133119889000089099....', '.911111121500003409.....', '....991100003330009.....', '.....9821000000009......', '.....999821111198.......', '....92223999998429......', '...9022222399823229.....', '...99888822222283829....', '...91118892222281008....', '..9910000182224910089...', '.939211001844449300829..', '.9335821139999999229329.', '92355589985335229999939.', '939955955255552589..99..', '99..99952555958129......', '.....928899.9925489.....', '....991999..99554339....', '........................'];
149
+ export const PIP_WALK = [
150
+ ['.....5566655...........', '...69344433365666......', '..533344434444336......', '.66224444444443466.....', '.54324444443444436.....', '6644442234424499346....', '66466433422323999444...', '66666344434434929445...', '66666448944939313935...', '66666659969856121945...', '66666889992191000945...', '6699681601102018929....', '6921981999900040026....', '9013891806960689926....', '903382190550009905.....', '.41231100220002306.....', '...592100000220006.....', '.....921100000006......', '.....56661111186.......', '....9622269999825......', '....9522222992525......', '....9968862222989......', '....8910062222929......', '.....810082222828......', '.....8100922229919.....', '.....9100996699914.....', '.....6200092559916.....', '.....5100094558988.....', '.....98112989689.......', '.....99889899119.......', '.....9558...95554......', '.....94444.............'],
151
+ ['.....6656655...........', '...68443344466665......', '..933444444444336......', '.98224444443344466.....', '.63424444443444435.....', '6544442244424499446....', '65366434422413695444...', '66566444434433618445...', '66666449944939213845...', '65656869969946122945...', '66666889992191000845...', '6588690911101019919....', '6900881998600080026....', '6012891509950499926....', '813362191860005805.....', '.51131100230002306.....', '...992100000220019.....', '.....921100000008......', '.....98951111189.......', '....8422299989924......', '...99411222992925......', '...89996992222999......', '...99910082222999......', '....981009222299.......', '.....81009222299.......', '.....89206198999.......', '......9200095499.......', '.....54622292499.......', '.....9556999659........', '.....999999.1198.......', '.....9349...64444......', '.....83455.............'],
152
+ ['.....6665656...........', '...96444444466665......', '..633444443444446......', '.55324444443444466.....', '.64424444333444435.....', '6644432244222369334....', '66466433422413699444...', '66666444444444929634...', '66566339944948813934...', '66666649969958121635...', '66666999992081000945...', '6699681922101016629....', '6921991989800081016....', '8002961505950499625....', '903361160550005506.....', '.50131110440003306.....', '...861100000330004.....', '.....961200000115......', '.....59891111189.......', '....9222266989926......', '...99222222992629......', '...99999992222999......', '...99920060022999......', '....99200610229........', '....992229222299.......', '.....992200999999......', '.....999200925999......', '.....55822292598.......', '....95599999969........', '....985589992289.......', '....9489...933223......', '....94469..............'],
153
+ ['.....5665566...........', '...98444443366666......', '..644444444444446......', '.96224444444444456.....', '.63313444443443436.....', '6543332243424469434....', '65466434422324499434...', '66666334444444939444...', '66665349944936312934...', '66666859958968122934...', '66666999992081000934...', '6686691611101009625....', '6922991989800060015....', '9013991508960699825....', '903381190650006506.....', '.51221110420002406.....', '...992200000220006.....', '.....931100000116......', '......9991111169.......', '.....522299899315......', '...89222222992225......', '...99888922222866......', '...99200942222929......', '....8100992222829......', '....810099222298166....', '....810899998899216....', '....810099925999216....', '....91006454459895.....', '....91216355928........', '.....99959992158.......', '......8689.943444......', '......94465............'],
154
+ ['.....6665655...........', '...56444444356665......', '..633333344443446......', '.86314444443444466.....', '.83423434433434445.....', '6633432243423399434....', '66456434422413899434...', '66566434434434828636...', '66566349944949213935...', '66666659969968111636...', '66666989982091000645...', '6699691922201018829....', '6811991999900081016....', '6002961505960499625....', '603361160560005506.....', '.51131100440002206.....', '...981100000230015.....', '.....961100000116......', '.....99991111189.......', '....9222296966825......', '...89222222992625......', '...99998922222996......', '...99200932222826......', '....62009922229138.....', '....810099222299169....', '....910699898899206....', '....810099525599219....', '....91008545559988.....', '....82119946529........', '.....99899991169.......', '......9555.833323......', '......95555............'],
155
+ ['.....6556656...........', '...56443344456566......', '..833444444443336......', '.56224444444443466.....', '.64324434343444435.....', '6644442244424499349....', '65466444422313695444...', '66566344444333619446...', '66666439944939223945...', '65665669969949121945...', '66666899982190000845...', '6699690911101019919....', '6911991996600081026....', '8113891609960499925....', '913381191980005606.....', '.51131100430002306.....', '...992100000220019.....', '.....821100000006......', '.....99961111196.......', '....9222269899924......', '...99222222992625......', '...99998922222995......', '...89100922222929......', '....92008822229215.....', '....920089222299139....', '....620099988999108....', '....610009523399129....', '....91000855249998.....', '....9922299989998......', '.....899999.81198......', '.....99559..943445.....', '.....995548............'],
156
+ ];
157
+ /** Valid palette digit (7 = transparent background, skipped); robust to short/ragged rows. */
158
+ export const isPix = (ch) => ch >= '0' && ch <= '9' && ch !== '7';
159
+ export function indexedToCanvas(rows) {
160
+ const cv = makeCanvas(PIP_W, PIP_H), c = cv.getContext('2d');
161
+ for (let y = 0; y < PIP_H; y++) {
162
+ const r = rows[y];
163
+ if (!r)
164
+ continue;
165
+ for (let x = 0; x < PIP_W; x++) {
166
+ const ch = r[x];
167
+ if (!isPix(ch))
168
+ continue;
169
+ c.fillStyle = PIP_PAL[+ch];
170
+ c.fillRect(x, y, 1, 1);
171
+ }
172
+ }
173
+ return cv;
174
+ }
175
+ export function silhouetteCanvasIdx(rows) {
176
+ const cv = makeCanvas(PIP_W, PIP_H), c = cv.getContext('2d');
177
+ c.fillStyle = '#ffe600';
178
+ for (let y = 0; y < PIP_H; y++) {
179
+ const r = rows[y];
180
+ if (!r)
181
+ continue;
182
+ for (let x = 0; x < PIP_W; x++) {
183
+ if (isPix(r[x]))
184
+ c.fillRect(x, y, 1, 1);
185
+ }
186
+ }
187
+ return cv;
188
+ }
189
+ /* ===================== personagem: sprite indexado ANTIGO (achado, não é o mesmo caso do PIP_*) =====================
190
+ ACHADO — relatado, não consertado: TEX + PLAYER_IDLE/WALK/CLIMB/HURT abaixo já tinham ZERO chamadores no
191
+ game.js original (nenhuma referência além da própria definição de TEX) — diferente do bloco PIP_* acima, este
192
+ não carrega comentário de "fase posterior"; é vestígio pré-PixelLab, plenamente substituído pelo pipeline PNG
193
+ (render/sprites.ts: TEX_IDLE/TEX_WALK/TEX_CLIMB/…, Fase 2.24). Preservado verbatim (a tarefa pede não apagar);
194
+ TEX passou de constante eager para função sob demanda (buildLegacyPlayerTex) só para este módulo poder ser
195
+ importado sem `document` (disciplina de render/sprites.ts) — comportamento IDÊNTICO se algum dia for chamada. */
196
+ export const PLAYER_IDLE = [
197
+ '................', '................', '.....HHHHHH.....', '....HHHHHHHH....',
198
+ '....HHSSSSHH....', '....HSSSSSSH....', '....HSKSSKSH....', '....HSSSSSSH....',
199
+ '....HSSWWSSH....', '....HSSSSSSH....', '.....HSSSSH.....', '......SSSS......',
200
+ '.....RRRRRR.....', '....RRRRRRRR....', '...RRRRRRRRRR...', '..SRRRRRRRRRRS..',
201
+ '..SRRRRRRRRRRS..', '..SRRRRRRRRRRS..', '..SRRRRRRRRRRS..', '...RRRRRRRRRR...',
202
+ '...RRRRRRRRRR...', '....RRRRRRRR....', '....BBBBBBBB....', '....BBBBBBBB....',
203
+ '....BBB..BBB....', '....BBB..BBB....', '....BBB..BBB....', '....BBB..BBB....',
204
+ '....BBB..BBB....', '....BBB..BBB....', '...KKKK..KKKK...', '...KKKK..KKKK...',
205
+ ];
206
+ export const PLAYER_WALK = [
207
+ '................', '................', '.....HHHHHH.....', '....HHHHHHHH....',
208
+ '....HHSSSSHH....', '....HSSSSSSH....', '....HSKSSKSH....', '....HSSSSSSH....',
209
+ '....HSSWWSSH....', '....HSSSSSSH....', '.....HSSSSH.....', '......SSSS......',
210
+ '.....RRRRRR.....', '....RRRRRRRR....', '...RRRRRRRRRR...', '..SRRRRRRRRRRS..',
211
+ '..SRRRRRRRRRRS..', '..SRRRRRRRRRRS..', '..SRRRRRRRRRRS..', '...RRRRRRRRRR...',
212
+ '...RRRRRRRRRR...', '....RRRRRRRR....', '....BBBBBBBB....', '...BBBB..BBB....',
213
+ '..BBB....BBB....', '.BBB.....BBB....', 'KKKK.....KKKK...', '................',
214
+ '................', '................', '................', '................',
215
+ ];
216
+ export const PLAYER_CLIMB = [
217
+ '................', '................', '.....HHHHHH.....', '....HHHHHHHH....',
218
+ '....HHHHHHHH....', '....HHHHHHHH....', '....HHHHHHHH....', '....HHHHHHHH....',
219
+ '....HHHHHHHH....', '....HHHHHHHH....', '.....HHHHHH.....', '......SSSS......',
220
+ '.....RRRRRR.....', '....RRRRRRRR....', '...RRRRRRRRRR...', '..SRRRRRRRRRRS..',
221
+ '..SRRRRRRRRRRS..', '..SRRRRRRRRRRS..', '..SRRRRRRRRRRS..', '...RRRRRRRRRR...',
222
+ '...RRRRRRRRRR...', '....RRRRRRRR....', '....BBBBBBBB....', '....BBBBBBBB....',
223
+ '....BBB..BBB....', '....BBB..BBB....', '....BBB..BBB....', '....BBB..BBB....',
224
+ '....BBB..BBB....', '....BBB..BBB....', '...KKKK..KKKK...', '...KKKK..KKKK...',
225
+ ];
226
+ export const PLAYER_HURT = [
227
+ '................', '................', '.....HHHHHH.....', '....HHHHHHHH....',
228
+ '....HHSSSSHH....', '....HSSSSSSH....', '....HSWWWWSH....', '....HSWKKWSH....',
229
+ '....HSSSSSSH....', '....HSKKKKSH....', '.....HSSSSH.....', '......SSSS......',
230
+ '.....RRRRRR.....', '....RRRRRRRR....', '...RRRRRRRRRR...', '..SRRRRRRRRRRS..',
231
+ '..SRRRRRRRRR.S..', '..SRRRRRRRR.SS..', '...RRRRRRRRSS...', '...RRRRRRRRRR...',
232
+ '...RRRRRRRRRR...', '....RRRRRRRR....', '....BBBBBBBB....', '....BBBBBBBB....',
233
+ '....BBB..BBB....', '....BBB..BBB....', '....BBB..BBB....', '....BBB..BBB....',
234
+ '....BBB..BBB....', '....BBB..BBB....', '...KKKK..KKKK...', '...KKKK..KKKK...',
235
+ ];
236
+ /** Verbatim reproduction of game.js's old eager `const TEX={idle:tex(spriteToCanvas(PLAYER_IDLE)),...}` — never
237
+ * auto-run (see header note above). Unused today; kept callable in case a future caller resurfaces. */
238
+ export function buildLegacyPlayerTex() {
239
+ return { idle: tex(spriteToCanvas(PLAYER_IDLE)), walk: tex(spriteToCanvas(PLAYER_WALK)), climb: tex(spriteToCanvas(PLAYER_CLIMB)), hurt: tex(spriteToCanvas(PLAYER_HURT)) };
240
+ }
@@ -0,0 +1,46 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Desenho } from './port.js';
3
+ type Gfx = Desenho;
4
+ export interface TitleSceneCtx {
5
+ /** The PIXI.Graphics layer this scene draws into (created + z-ordered in game.js). */
6
+ titleG: Gfx;
7
+ /** Live screen size (pass `app.screen` — width/height read fresh on every draw()). */
8
+ screen: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ /** Reduced-motion flags; only `.parallax` (reduced parallax/animation) matters here. */
13
+ getRm: () => {
14
+ parallax?: boolean;
15
+ };
16
+ }
17
+ export interface TitleScene {
18
+ /** Redraws the title backdrop for the current frame (clears `titleG` first). */
19
+ draw: () => void;
20
+ }
21
+ /** The 4 title-screen clouds' logical (unscaled) positions — v3 exact. */
22
+ export interface TitleCloudDef {
23
+ cx: number;
24
+ cy: number;
25
+ }
26
+ export declare const TITLE_CLOUDS: readonly TitleCloudDef[];
27
+ /** Horizon row (sky/grass split), in screen pixels. */
28
+ export declare function titleHorizonY(H: number): number;
29
+ /** Sky gradient color for scanline `y` (0-indexed from the top), given the horizon row count. RGB
30
+ * interpolation rgb(26→58, 26→76, 58→180) — exact v3 formula, packed as 0xRRGGBB. */
31
+ export declare function titleSkyRowColor(y: number, horizon: number): number;
32
+ /** This frame's cloud-drift offset: frozen at 0 under reduced motion, else sub-pixel accumulation of the
33
+ * per-draw frame counter (titleT/6) — corrects #21a (no more Math.floor → no "jumps 1px every 6 frames"). */
34
+ export declare function titleCloudDriftOffset(titleT: number, reducedMotion: boolean): number;
35
+ /** Screen-space x of one title cloud, with whole-body wrap (#21b: the 28px-wide cloud only re-enters once it
36
+ * has left the screen entirely). */
37
+ export declare function titleCloudX(cx: number, k: number, off: number, W: number): number;
38
+ export interface TitleGrassRow {
39
+ xStart: number;
40
+ xStep: number;
41
+ y: number;
42
+ }
43
+ /** The 3 staggered dotted-grass rows below the horizon — v3 exact. */
44
+ export declare function titleGrassRows(k: number, horizon: number): TitleGrassRow[];
45
+ export declare function createTitleScene(ctx: TitleSceneCtx): TitleScene;
46
+ export {};
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/title-scene — title-screen BACKDROP: sky gradient + 4 drifting clouds + dotted grass, drawn into
3
+ // `titleG` (v3.1.100 formulas, verbatim). Extracted from game.js's drawTitleScene(). The `titleG` PIXI.Graphics
4
+ // layer is CREATED in game.js (`app.stage.addChildAt(titleG, app.stage.getChildIndex(weatherLayer))`) — its
5
+ // z-order is soldered into the render-graph assembly there, same precedent as render/scene-sky.ts — and is
6
+ // INJECTED here; only the drawing logic moves. cloudWrapX (sub-pixel, whole-body wrap, bug #21) is imported
7
+ // straight from render/scene-sky.ts rather than re-implemented — it is already a leaf pure function there.
8
+ import { LOGICAL_H } from '../core/constants.js';
9
+ import { cloudWrapX } from './scene-sky.js';
10
+ export const TITLE_CLOUDS = [
11
+ { cx: 40, cy: 30 }, { cx: 180, cy: 52 }, { cx: 265, cy: 22 }, { cx: 110, cy: 72 },
12
+ ];
13
+ /** Horizon row (sky/grass split), in screen pixels. */
14
+ export function titleHorizonY(H) {
15
+ return Math.round(H * 0.735);
16
+ }
17
+ /** Sky gradient color for scanline `y` (0-indexed from the top), given the horizon row count. RGB
18
+ * interpolation rgb(26→58, 26→76, 58→180) — exact v3 formula, packed as 0xRRGGBB. */
19
+ export function titleSkyRowColor(y, horizon) {
20
+ const f = y / horizon;
21
+ const r = Math.round(26 + 32 * f), g = Math.round(26 + 50 * f), b = Math.round(58 + 122 * f);
22
+ return (r << 16) | (g << 8) | b;
23
+ }
24
+ /** This frame's cloud-drift offset: frozen at 0 under reduced motion, else sub-pixel accumulation of the
25
+ * per-draw frame counter (titleT/6) — corrects #21a (no more Math.floor → no "jumps 1px every 6 frames"). */
26
+ export function titleCloudDriftOffset(titleT, reducedMotion) {
27
+ return reducedMotion ? 0 : titleT / 6;
28
+ }
29
+ /** Screen-space x of one title cloud, with whole-body wrap (#21b: the 28px-wide cloud only re-enters once it
30
+ * has left the screen entirely). */
31
+ export function titleCloudX(cx, k, off, W) {
32
+ return cloudWrapX(cx * k - off, -28 * k, W + 28 * k);
33
+ }
34
+ /** The 3 staggered dotted-grass rows below the horizon — v3 exact. */
35
+ export function titleGrassRows(k, horizon) {
36
+ return [
37
+ { xStart: 0, xStep: 3 * k, y: horizon },
38
+ { xStart: 1 * k, xStep: 4 * k, y: horizon + 3 * k },
39
+ { xStart: 2 * k, xStep: 5 * k, y: horizon + 6 * k },
40
+ ];
41
+ }
42
+ // ---------------------------------------------------------------------------------------------
43
+ // PIXI-facing (thin) — requires the injected `titleG` layer
44
+ // ---------------------------------------------------------------------------------------------
45
+ export function createTitleScene(ctx) {
46
+ let titleT = 0; // frame counter (module-local state, mirrors the old closure-level `let titleT=0`)
47
+ function drawCloud(g, cx, cy, k, off, W) {
48
+ const x = titleCloudX(cx, k, off, W), s = k;
49
+ g.beginFill(0xf6f5f0)
50
+ .drawRect(x, cy * s, 28 * s, 6 * s)
51
+ .drawRect(x + 6 * s, (cy - 4) * s, 16 * s, 6 * s)
52
+ .drawRect(x + 2 * s, (cy + 6) * s, 24 * s, 4 * s)
53
+ .endFill();
54
+ }
55
+ function draw() {
56
+ const g = ctx.titleG;
57
+ g.clear();
58
+ const W = ctx.screen.width, H = ctx.screen.height, k = H / LOGICAL_H;
59
+ const HOR = titleHorizonY(H);
60
+ for (let y = 0; y < HOR; y++)
61
+ g.beginFill(titleSkyRowColor(y, HOR)).drawRect(0, y, W, 1).endFill();
62
+ titleT++;
63
+ const off = titleCloudDriftOffset(titleT, !!ctx.getRm().parallax);
64
+ for (const c of TITLE_CLOUDS)
65
+ drawCloud(g, c.cx, c.cy, k, off, W);
66
+ g.beginFill(0x3f7d20).drawRect(0, HOR, W, H - HOR).endFill();
67
+ g.beginFill(0x2d5b16);
68
+ for (const row of titleGrassRows(k, HOR)) {
69
+ for (let x = row.xStart; x < W; x += row.xStep)
70
+ g.drawRect(x, row.y, k, k);
71
+ }
72
+ g.endFill();
73
+ }
74
+ return { draw };
75
+ }