@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,149 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { PlayerView } from '../core/entity.js';
3
+ import { type AnimPlayer, type Frame, type PlayerTextures } from './player-anim.js';
4
+ import type { RenderizarEm } from './port.js';
5
+ /** O que este módulo toca de um `PIXI.Graphics`. Superconjunto do `DrawGraphics` de wheelchair-sprites. */
6
+ export interface GraphicsLike {
7
+ clear(): unknown;
8
+ lineStyle(width: number, color?: number, alpha?: number): unknown;
9
+ beginFill(color: number, alpha?: number): unknown;
10
+ endFill(): unknown;
11
+ drawRect(x: number, y: number, w: number, h: number): unknown;
12
+ moveTo(x: number, y: number): unknown;
13
+ lineTo(x: number, y: number): unknown;
14
+ drawCircle(x: number, y: number, r: number): unknown;
15
+ }
16
+ /** O sprite do personagem — só posição, escala, alpha, visibilidade e textura. */
17
+ export interface PlayerSprite {
18
+ x: number;
19
+ y: number;
20
+ alpha: number;
21
+ visible: boolean;
22
+ scale: {
23
+ set(x: number, y: number): void;
24
+ };
25
+ texture: unknown;
26
+ }
27
+ /** `camera` (PIXI.Container): daqui só a posição importa; ela é passada opaca ao renderer. */
28
+ export interface CameraLike {
29
+ x: number;
30
+ y: number;
31
+ }
32
+ /** `app.renderer` — só a passada em render-texture do caminho multi-tela. */
33
+ /** O sprite de um item declarado: o mínimo que o desenho toca. */
34
+ export interface ItemSprite {
35
+ visible: boolean;
36
+ alpha: number;
37
+ }
38
+ /**
39
+ * Um power-up, do ponto de vista do DESENHO: um sprite, e nada mais.
40
+ *
41
+ * Era `extends Powerup`, importado de `game/powerups` — e esse `import type` era a ÚLTIMA aresta deste módulo
42
+ * para o jogo. Tipo apagado em tempo de compilação não entra no pacote, é verdade; mas ele obriga o arquivo a
43
+ * existir para o `tsc`, e obriga QUALQUER jogo a ter um power-up com aquela forma. O desenho toca `sprite` e
44
+ * mais nada — declarar só isso é a fatia mínima, a mesma regra de `core/entity`.
45
+ */
46
+ interface PowerupWithSprite {
47
+ sprite?: {
48
+ visible: boolean;
49
+ } | null;
50
+ /**
51
+ * ⚠️ `kind` NÃO é lido aqui — e entrou mesmo assim, em 2026-08-26. O desenho ENTREGA o power-up ao
52
+ * `powerupVisibleTo` injetado, e é ele que lê o `kind` (a chave do portão é global; o resto é por
53
+ * jogador). Uma fatia mínima descreve o que o módulo CONSOME; em posição de parâmetro ela precisa
54
+ * descrever também o que ele ENTREGA, senão o callback real não cabe no ctx.
55
+ *
56
+ * É a quinta vez que esta forma aparece na árvore — `held` em audio-nav, `setSquash` na física, o
57
+ * menu de pausa no gamepad, o `navPause` — e a lição é a mesma: repassar é uma exigência, não uma
58
+ * leitura. A fatia continua mínima: dois campos, e nenhum tipo de `game/` importado.
59
+ */
60
+ kind: string;
61
+ }
62
+ /** O jogador, do ponto de vista do DESENHO (a animação usa `AnimPlayer`, do qual este é superconjunto). */
63
+ /**
64
+ * O jogador visto pelo DESENHO: tudo o que a animação lê, mais posição, dano e apresentação.
65
+ * `sq`/`sqT` são o squash&stretch (amplitude e relógio, escritos por render/fx.setSquash); `easy` desenha a
66
+ * hitbox de coleta tolerante translúcida; `viz` é o modo de visão, que recolore por viewport.
67
+ */
68
+ export type DrawPlayer = AnimPlayer & PlayerView<'i' | 'x' | 'y' | 'facing' | 'hurtTimer' | 'sq' | 'sqT' | 'easy' | 'viz' | 'sprite'>;
69
+ /** Flags de Movimento Reduzido que o DESENHO consulta (o objeto `rm` do game.js tem mais chaves). */
70
+ export interface ReducedMotion {
71
+ items?: boolean;
72
+ }
73
+ export interface DrawCtx {
74
+ /** Quantos jogadores/telas. Estado de RODADA (ADR-0038): vem da instância que a raiz possui.
75
+ * Era `numPlayers`, um `let` de `core/state` importado como binding vivo — e um `let` de módulo
76
+ * é compartilhado por qualquer segundo jogo que a mesma página carregue (D13 do `demos`). */
77
+ getNumPlayers: () => number;
78
+ /** Os jogadores. Estado de RODADA, pelo mesmo motivo. `readonly unknown[]` porque cada consumidor
79
+ * estreita para a SUA fatia — o tipo real é do jogo, não da engine (ADR-0033). */
80
+ getPlayers: () => readonly unknown[];
81
+ camera: CameraLike;
82
+ renderizarEm: RenderizarEm;
83
+ caneLayer: GraphicsLike;
84
+ chairLayer: GraphicsLike;
85
+ /**
86
+ * CAIXA DE COLISÃO do jogador (largura × altura, em px). ENTRA em vez de ser importada de `game/player`, e
87
+ * isso não é política nova: `render/scene-city`, `render/scene-sky` e `platform/audio-nav` já a recebem
88
+ * assim — este módulo é que tinha ficado para trás, sozinho na camada de render importando do jogo.
89
+ *
90
+ * Ela aparece em três lugares e sempre pela mesma razão: `pl.y` é o PÉ do jogador, e meia caixa acima é o
91
+ * meio do corpo. É conhecimento de gênero — um top-down não ancora no pé, um quiz não tem corpo — e por
92
+ * isso pertence a quem tem corpo, não a quem desenha.
93
+ */
94
+ BOX: {
95
+ w: number;
96
+ h: number;
97
+ };
98
+ easyHitbox: GraphicsLike;
99
+ getVpTex(): unknown[];
100
+ isWheelchair(): boolean;
101
+ getFxClock(): number;
102
+ getPowerups(): readonly PowerupWithSprite[];
103
+ /**
104
+ * Os sprites dos ITENS declarados, na ordem em que o jogo os declara. Era `getCoinSprites()`, importado de
105
+ * `game/coin-spawning` — o desenho sabia que os itens são moedas.
106
+ */
107
+ getItemSprites(): readonly (ItemSprite | null | undefined)[];
108
+ /** O item `j` aparece para o jogador `i`? Era `!cn.taken` — a regra "coletado some" é do jogo. */
109
+ itemVisibleTo(j: number, playerIndex: number): boolean;
110
+ /**
111
+ * O item `j` é DESTE jogador? Era `cn.owner === i`, e o que o desenho faz com a resposta é esmaecer o item
112
+ * alheio (lote C). A ideia de POSSE é do jogo: um cooperativo responde `true` sempre e ninguém esmaece nada.
113
+ */
114
+ itemOwnedBy(j: number, playerIndex: number): boolean;
115
+ /**
116
+ * O power-up `pu` aparece para o jogador `i`? Era `!puTaken(pu, i)`, que trazia a regra "chave é global,
117
+ * o resto é por jogador" — uma frase sobre ESTE jogo, dentro do desenho.
118
+ */
119
+ powerupVisibleTo(pu: PowerupWithSprite, playerIndex: number): boolean;
120
+ rm: ReducedMotion;
121
+ WORLD_PX_W(): number;
122
+ WORLD_PX_H(): number;
123
+ caneOn(pl: DrawPlayer): boolean;
124
+ updateParallax(camX: number, camY: number): void;
125
+ drawElevators(): void;
126
+ markSeen(camX: number, camY: number): void;
127
+ redrawMinimapIfDirty(): void;
128
+ drawMinimapPlayer(worldX: number, worldY: number): void;
129
+ applySharedTextures(viz: string): void;
130
+ renderVpOverlay(i: number, viz: string): void;
131
+ playerVizTex(base: Frame, viz: string): unknown;
132
+ updateGameHud(): void;
133
+ playerTextures(): PlayerTextures;
134
+ held(pl: AnimPlayer, act: string): boolean;
135
+ }
136
+ export interface DrawApi {
137
+ /** Enquadra `pl`, aplica clamp+tremor+arredondamento e reposiciona o parallax.
138
+ * `i` é o índice do jogador, e existe porque CADA JOGADOR TEM A SUA CÂMERA (ver `camDe`). */
139
+ placeCam(pl: DrawPlayer, i?: number): {
140
+ camX: number;
141
+ camY: number;
142
+ };
143
+ /** Desenha o quadro inteiro (era `draw()` no game.js). */
144
+ drawFrame(): void;
145
+ /** Escolhe o quadro de animação de `pl` e o aplica no sprite (era a cauda do `stepPlayer`). */
146
+ animatePlayer(pl: DrawPlayer, dt: number, dir: number): Frame;
147
+ }
148
+ export declare function initDraw(ctx: DrawCtx): DrawApi;
149
+ export {};
@@ -0,0 +1,221 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/draw.ts — O QUADRO. Onde a câmera para e o que é carimbado nela, uma vez por tick, depois que
3
+ // `update()` já simulou o mundo. Extraído verbatim do game.js (`placeCam` + `draw`), mais a aplicação da
4
+ // textura escolhida no sprite do jogador (a última linha do antigo `stepPlayer`).
5
+ //
6
+ // O QUE ENTROU
7
+ // · `placeCam(pl)` — a câmera: enquadra o jogador, CLAMPA nas bordas do mundo, aplica o tremor de tela
8
+ // (render/fx.shakeAmp) RE-CLAMPANDO (senão o tremor mostraria o vazio fora do mapa),
9
+ // arredonda para pixel inteiro (o jogo é 320×180: meio pixel é tremulação) e
10
+ // reposiciona o parallax. Devolve `{camX,camY}` PRÉ-arredondamento, que é o que o
11
+ // minimapa usa para marcar o que já foi visto.
12
+ // · `drawFrame()` — o quadro inteiro: transform dos sprites de jogador (squash&stretch com easing,
13
+ // ancorado nos pés + piscada de dano), elevadores, partículas, bengala, cadeira de
14
+ // rodas, hitbox tolerante do modo Fácil, e ENTÃO um dos DOIS caminhos de câmera
15
+ // (tela única direta vs. multi-tela com uma render-texture por viewport), clima e HUD.
16
+ // · `animatePlayer()` — chama a decisão de quadro (render/player-anim) e a aplica no sprite, passando pelo
17
+ // recolor do modo de visão (`playerVizTex`).
18
+ //
19
+ // POR QUE A FRONTEIRA CAIU AQUI
20
+ // `placeCam` e `draw` são inseparáveis: os DOIS caminhos de câmera do `draw` chamam `placeCam`, e o de
21
+ // multi-tela depende do efeito colateral dela (mover `camera` antes de cada `renderer.render`). Separá-los
22
+ // só produziria dois módulos que se importam. Já `animatePlayer` veio junto por outro motivo: a escolha do
23
+ // quadro é pura e mora em render/player-anim.ts, mas APLICÁ-LA toca um `PIXI.Sprite` e o cache de recolor de
24
+ // render/viewports — exatamente o tipo de coisa que este módulo já faz. Deixá-la no game.js manteria lá uma
25
+ // linha órfã de render dentro de uma função de lógica de jogo.
26
+ //
27
+ // O QUE FICOU DE FORA, E POR QUÊ
28
+ // · `configureRender()` — a MONTAGEM do multi-tela (criar/destruir as render-textures, os sprites de
29
+ // viewport, as molduras e as bolinhas). É ciclo de vida do render-graph, não desenho de quadro; e vive
30
+ // colada ao `app.renderer.resize` e ao HUD. Fica no game.js para uma rodada própria.
31
+ // · `updateParallax()` — mexe nos `TilingSprite` criados no game.js e na decor de tela da v3; entra
32
+ // injetada.
33
+ // · `applySharedTextures()` (render/viz-setters) e `renderVpOverlay()` (render/viewports) — já são módulos;
34
+ // entram injetados porque suas instâncias são criadas no game.js.
35
+ // · A coleta de moedas, os power-ups e o portão que precediam a animação no `stepPlayer`: são LÓGICA DE
36
+ // JOGO (dependem de `coins`, `powerups`, `gate`, quiz e HUD) e continuam no game.js.
37
+ //
38
+ // INJEÇÃO — a regra e as EXCEÇÕES conscientes
39
+ // Vale a regra da casa: o que o game.js REATRIBUI entra por getter; o que é estável entra por valor. Daí
40
+ // `getVpTex` (o `let vpTex` que `configureRender` troca a cada mudança de nº de telas), `isWheelchair`
41
+ // (`let wheelchair`), `getFxClock` (`let fxClock`, o relógio do cintilar) e `getPowerups` (`let powerups`)
42
+ // serem GETTERS. `rm` (o objeto de Movimento Reduzido) é `const` e mutado IN PLACE → entra por valor.
43
+ //
44
+ // EXCEÇÃO (desvio consciente da instrução de recorte, que pedia getters também para `numPlayers`, `coins` e
45
+ // `players`): esses três são `export let` de core/state.ts, ou seja, BINDINGS VIVOS de ES Modules. Importá-los
46
+ // aqui já entrega o valor atual a cada leitura — `setNumPlayersValue`/`setCoins` são vistos na hora, sem
47
+ // getter. Getter aqui não acrescentaria correção, só uma indireção a mais e um ctx maior; e o precedente do
48
+ // repositório é este (game/coin-spawning.ts importa `coins`+`setCoins` direto; game/physics.ts importa
49
+ // `players`). A regra do getter vale para o que mora no ESCOPO PRIVADO do game.js, que módulo nenhum enxerga.
50
+ //
51
+ // PIXI POR INTERFACE ESTRUTURAL, não por `import * as PIXI` — mesmo precedente de render/viewports.ts e
52
+ // render/scene-sky.ts. `camera`, `renderer`, as camadas de `Graphics` e os sprites entram como interfaces com
53
+ // só os membros que este módulo toca, para o arquivo carregar no project `node` do Vitest.
54
+ // Pelo mesmo motivo, os colaboradores que importam PIXI (game/level-geometry.drawElevators,
55
+ // render/minimap.*) entram por closure em vez de import: o game.js já tem as camadas deles em mão.
56
+ // São importados direto só os módulos-folha node-safe: constantes, RNG, render/fx, render/wheelchair-sprites,
57
+ // render/viz-modes, game/powerups, game/coin-spawning e core/state.
58
+ //
59
+ // ORDEM DE BOOT: `initDraw` não faz I/O nem toca o render-graph — só fecha closures. Pode ser chamada em
60
+ // qualquer ponto do game.js posterior às camadas de `Graphics`, à `camera` e ao `app`; o resto do ctx é
61
+ // função e resolve tarde. Ver docs/5-Refactoring/plano-modularizacao-mapa.md (C1).
62
+ import { LOGICAL_W, LOGICAL_H, EASY } from '../core/constants.js';
63
+ // A corrente da DECORACAO, nao a do jogo: uma particula sorteada aqui nao pode mover o sorteio
64
+ // das moedas. Ver o cabecalho de core/rng.ts (issue #107).
65
+ import { rngDecoracao } from '../core/rng.js';
66
+ const rnd = rngDecoracao.rnd;
67
+ import { JUICE, easeOut3, shakeAmp, drawFx } from './fx.js';
68
+ import { criarCamera } from './camera.js';
69
+ import { drawCane, drawRunCane, drawChair } from './wheelchair-sprites.js';
70
+ import { VIZ_BY_KEY } from './viz-modes.js';
71
+ // (`game/powerups` e `game/coin-spawning` SAÍRAM daqui no item 19 — ver o bloco "ITENS DECLARADOS" abaixo.)
72
+ import { drawWeather } from './weather.js';
73
+ import { choosePlayerFrame } from './player-anim.js';
74
+ export function initDraw(ctx) {
75
+ /* ===================== câmera ===================== */
76
+ // A CONTA saiu para `render/camera` (item 22: M1 extraiu as fórmulas, M2 as embrulhou num OBJETO). Aqui
77
+ // ficou o CARIMBO dela no render-graph: o sorteio do tremor, a escrita no container e o aviso ao parallax.
78
+ //
79
+ // ========================= UMA CÂMERA POR JOGADOR, E POR QUÊ AGORA =========================
80
+ // O caminho multi-tela chama `placeCam(PLS[i])` para CADA jogador dentro do MESMO quadro. Com uma câmera
81
+ // só, a posição do jogador i entraria como ponto de partida da do jogador i+1. Hoje isso não daria em
82
+ // nada — com zona-morta 0×0 a câmera é sem memória e `seguir` equivale a `enquadrar`, letra por letra
83
+ // (`tests/camera.node.test.js` prende essa igualdade). No dia em que a zona deixar de ser zero, daria: as
84
+ // câmeras dos jogadores se puxariam. É mais barato separar antes de o defeito existir do que depois, e
85
+ // separar agora não muda um pixel do que a criança vê.
86
+ //
87
+ // ⚠️ `pular()` AINDA NÃO TEM CHAMADOR. Ele é para nascer/renascer/trocar de fase, e enquanto a zona é zero
88
+ // não faz falta nenhuma — `seguir` já centra. Quando o Dev escolher uma zona, o nascimento sem `pular`
89
+ // aparece como um deslize da câmera até o jogador no primeiro quadro da fase. Fica anotado aqui, e não
90
+ // fingido de resolvido.
91
+ const cams = new Map();
92
+ const camDe = (i) => {
93
+ let c = cams.get(i);
94
+ if (!c) {
95
+ c = criarCamera({ w: ctx.WORLD_PX_W(), h: ctx.WORLD_PX_H() }, { w: LOGICAL_W, h: LOGICAL_H });
96
+ cams.set(i, c);
97
+ }
98
+ // O mundo muda de tamanho ao trocar de fase, e a câmera não é avisada por ninguém — reprender todo quadro
99
+ // custa duas contas e dispensa um evento que hoje não existe.
100
+ else
101
+ c.redimensionar({ w: ctx.WORLD_PX_W(), h: ctx.WORLD_PX_H() }, { w: LOGICAL_W, h: LOGICAL_H });
102
+ return c;
103
+ };
104
+ function placeCam(pl, i = 0) {
105
+ const c = camDe(i);
106
+ // `pl.y` é o PÉ do jogador; o meio do corpo fica meia caixa acima. Esta conversão é a única coisa de
107
+ // PLATAFORMA que havia no enquadramento, e agora ela mora aqui, onde o corpo existe — e não na câmera.
108
+ c.seguir(pl.x, pl.y - ctx.BOX.h / 2);
109
+ const k = shakeAmp(); // JUICE: tremor decai linearmente (render/fx)
110
+ // Os dois sorteios ficam DENTRO do `if`, como no original: com `k === 0` ele não chamava `rnd()`, e
111
+ // chamá-lo duas vezes por quadro deslocaria o fluxo do gerador COMPARTILHADO — mesma semente, outro jogo.
112
+ const cam = k > 0 ? c.quadro(k, rnd() * 2 - 1, rnd() * 2 - 1) : c.base;
113
+ ctx.camera.x = -Math.round(cam.camX);
114
+ ctx.camera.y = -Math.round(cam.camY);
115
+ ctx.updateParallax(cam.camX, cam.camY);
116
+ return cam;
117
+ }
118
+ /* ===================== animação do personagem ===================== */
119
+ function animatePlayer(pl, dt, dir) {
120
+ const tx = choosePlayerFrame(pl, {
121
+ dt, dir, wheelchair: ctx.isWheelchair(), held: ctx.held, rnd, tex: ctx.playerTextures(),
122
+ });
123
+ // solo/default; no MP o drawFrame troca a textura por viewport (applySharedTextures)
124
+ if (pl.sprite)
125
+ pl.sprite.texture = ctx.playerVizTex(tx, pl.viz);
126
+ return tx;
127
+ }
128
+ /* ===================== o quadro ===================== */
129
+ function drawFrame() {
130
+ const PLS = ctx.getPlayers();
131
+ for (const pl of PLS) {
132
+ if (!pl.sprite)
133
+ continue;
134
+ pl.sprite.x = pl.x;
135
+ pl.sprite.y = pl.y + 1;
136
+ // JUICE: squash&stretch com easing, ancorado nos pés
137
+ const q = (JUICE.squash && !pl.rmWalk && (pl.sqT || 0) > 0) ? (pl.sq || 0) * easeOut3((pl.sqT || 0) / 8) : 0;
138
+ // sem escala procedural de respiração (parecia mastigar) — respiração é por FRAMES
139
+ pl.sprite.scale.set((pl.facing < 0 ? -1 : 1) * (1 - q * 0.7), 1 + q);
140
+ pl.sprite.alpha = pl.hurtTimer > 0 ? (Math.floor(pl.hurtTimer / 4) % 2 ? 0.4 : 1) : 1;
141
+ }
142
+ ctx.drawElevators(); // cadeirante: plataforma do elevador sob os pés (largo/fino)
143
+ drawFx(); // JUICE: partículas (poeira/brilhos) na camada acima dos players
144
+ const shimOn = JUICE.shimmer && !ctx.rm.items; // JUICE: cintilar dos itens (respeita Movimento Reduzido)
145
+ const fxClock = ctx.getFxClock();
146
+ // modo cego: bengala SÓ andando (corrida = bengala de roda); nada parado/nadando/voando/escada
147
+ ctx.caneLayer.clear();
148
+ for (const pl of PLS) {
149
+ if (!ctx.caneOn(pl) || !pl.sprite || !pl.sprite.visible)
150
+ continue;
151
+ if (pl.running)
152
+ drawRunCane(ctx.caneLayer, pl);
153
+ else if (pl.walking)
154
+ drawCane(ctx.caneLayer, pl);
155
+ }
156
+ // cadeirante: desenha a cadeira nos jogadores (exceto nadando/voando)
157
+ ctx.chairLayer.clear();
158
+ if (ctx.isWheelchair()) {
159
+ for (const pl of PLS) {
160
+ if (pl.sprite && pl.sprite.visible && !pl.inWater && !pl.flying)
161
+ drawChair(ctx.chairLayer, pl);
162
+ }
163
+ }
164
+ // Fácil: hitbox de coleta tolerante (retângulo translúcido) — só para os jogadores em Fácil
165
+ ctx.easyHitbox.clear();
166
+ const pad = EASY.pad;
167
+ for (const pl of PLS) {
168
+ if (!pl.easy)
169
+ continue;
170
+ ctx.easyHitbox.lineStyle(1, 0xffffff, 0.45);
171
+ ctx.easyHitbox.beginFill(0xffffff, 0.10);
172
+ ctx.easyHitbox.drawRect(pl.x - ctx.BOX.w / 2 - pad, pl.y - ctx.BOX.h - pad, ctx.BOX.w + 2 * pad, ctx.BOX.h + 2 * pad);
173
+ ctx.easyHitbox.endFill();
174
+ }
175
+ if (ctx.getNumPlayers() <= 1) {
176
+ // O CINTILAR fica: é juice da engine (render/fx), e vale para moeda, estrela ou peça de tabuleiro.
177
+ const itens = ctx.getItemSprites();
178
+ for (let j = 0; j < itens.length; j++) {
179
+ const s = itens[j];
180
+ if (s)
181
+ s.alpha = shimOn ? 0.8 + 0.2 * Math.sin(fxClock * 0.12 + j * 1.7) : 1;
182
+ }
183
+ const { camX, camY } = placeCam(PLS[0]);
184
+ ctx.markSeen(camX, camY);
185
+ ctx.redrawMinimapIfDirty();
186
+ ctx.drawMinimapPlayer(PLS[0].x, PLS[0].y - ctx.BOX.h / 2);
187
+ }
188
+ else {
189
+ // Otimização: se TODOS estão no mesmo modo (caso comum), troca as texturas UMA vez; senão, por viewport.
190
+ const v0 = PLS[0].viz, allSame = PLS.every((p) => p.viz === v0);
191
+ const anyOverlay = PLS.some((p) => { const m = VIZ_BY_KEY[p.viz]; return !!m && m.kind === 'lowvision'; });
192
+ if (allSame)
193
+ ctx.applySharedTextures(v0);
194
+ for (let i = 0, n = ctx.getNumPlayers(); i < n; i++) {
195
+ const viz = PLS[i].viz;
196
+ if (!allSame)
197
+ ctx.applySharedTextures(viz); // só troca por viewport quando os modos diferem
198
+ const itens2 = ctx.getItemSprites();
199
+ for (let j = 0; j < itens2.length; j++) {
200
+ const s = itens2[j];
201
+ if (!s)
202
+ continue;
203
+ s.visible = ctx.itemVisibleTo(j, i);
204
+ // Lote C: item alheio esmaecido — quem é "alheio" é o jogo que diz; JUICE: cintilar multiplicativo
205
+ s.alpha = (ctx.itemOwnedBy(j, i) ? 1 : 0.4) * (shimOn ? 0.8 + 0.2 * Math.sin(fxClock * 0.12 + j * 1.7) : 1);
206
+ }
207
+ for (const pu of ctx.getPowerups()) {
208
+ if (pu.sprite)
209
+ pu.sprite.visible = ctx.powerupVisibleTo(pu, i);
210
+ }
211
+ placeCam(PLS[i], i);
212
+ ctx.renderizarEm(ctx.camera, ctx.getVpTex()[i], true); // LIMPA: cada quadro é um quadro, não um acúmulo
213
+ if (anyOverlay)
214
+ ctx.renderVpOverlay(i, viz); // passada extra só se algum jogador está em baixa visão
215
+ }
216
+ }
217
+ drawWeather(); // chuva/clarão em tela-espaço, sobre tudo
218
+ ctx.updateGameHud(); // HUD por jogador (moedas + poder) em DOM sobreposto (alta definição)
219
+ }
220
+ return { placeCam, drawFrame, animatePlayer };
221
+ }
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ export interface Particle {
3
+ x: number;
4
+ y: number;
5
+ vx: number;
6
+ vy: number;
7
+ life: number;
8
+ max: number;
9
+ color: number;
10
+ size: number;
11
+ g: number;
12
+ }
13
+ export interface JuiceFlags {
14
+ dust: boolean;
15
+ sparkle: boolean;
16
+ squash: boolean;
17
+ hitstop: boolean;
18
+ shake: boolean;
19
+ shimmer: boolean;
20
+ }
21
+ /** Juice toggles (persisted in localStorage; edited in the ?debug panel + Sensibilidade → Movimento). */
22
+ export declare const JUICE: JuiceFlags;
23
+ export declare function saveJuice(): void;
24
+ /** Standard easing (squash recovery, particle fade). Shared → exported (game.js render reads it too). */
25
+ export declare const easeOut3: (t: number) => number;
26
+ /** Minimal PIXI.Graphics surface drawFx needs (kept structural so the module stays PIXI-free for node tests). */
27
+ interface FxGraphics {
28
+ clear(): void;
29
+ beginFill(color: number, alpha?: number): unknown;
30
+ drawRect(x: number, y: number, w: number, h: number): unknown;
31
+ endFill(): unknown;
32
+ }
33
+ interface ReducedMotion {
34
+ particles?: boolean;
35
+ parallax?: boolean;
36
+ }
37
+ interface Squashable {
38
+ rmWalk?: boolean;
39
+ sq?: number;
40
+ sqT?: number;
41
+ }
42
+ export declare function initFx(deps: {
43
+ fxG: FxGraphics;
44
+ rm: ReducedMotion;
45
+ getPlayers: () => readonly unknown[];
46
+ }): void;
47
+ export declare function spawnParticle(x: number, y: number, vx: number, vy: number, life: number, color: number, size: number, grav?: number): void;
48
+ export declare function puffDust(x: number, y: number, n: number): void;
49
+ export declare function burstSparkle(x: number, y: number, color?: number, n?: number): void;
50
+ export declare function addShake(mag: number, dur: number): void;
51
+ export declare function addHitstop(t: number): void;
52
+ export declare function setSquash(pl: Squashable, amt: number): void;
53
+ /** Advance particles + decay shake/squash. (fxClock is stepped by game.js — see header.) */
54
+ export declare function stepFx(dt: number): void;
55
+ export declare function drawFx(): void;
56
+ /** Decrement hit-stop and report whether the world is still frozen (game.js early-returns on true). */
57
+ export declare function tickHitstop(dt: number): boolean;
58
+ /** Current screen-shake amplitude (0 = none); the camera applies the random jitter. */
59
+ export declare function shakeAmp(): number;
60
+ export declare const getParticles: () => Particle[];
61
+ export declare const getHitstopT: () => number;
62
+ export declare const getShakeT: () => number;
63
+ export {};
@@ -0,0 +1,123 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/fx — Juice / micro-feedback (Estágio 4, Tier 2): particles (dust/sparkle), screen shake, hit-stop,
3
+ // squash&stretch, + the JUICE toggles. Formulas are verbatim from game.js (behavior-preserving). The reduce-
4
+ // motion flags (`rm`), the PIXI graphics layer (`fxG`) and the round's player list are INJECTED via initFx.
5
+ // `fxClock` is NOT here — it is a general animation clock (coin shimmer reads it) and stays in game.js.
6
+ // Shake/hit-stop state is read by the camera/update/__incl via getters. See docs/5-Refactoring/plano-modularizacao-mapa.md.
7
+ // A corrente da DECORACAO, nao a do jogo: uma particula sorteada aqui nao pode mover o sorteio
8
+ // das moedas. Ver o cabecalho de core/rng.ts (issue #107).
9
+ import { rngDecoracao } from '../core/rng.js';
10
+ const rnd = rngDecoracao.rnd;
11
+ import * as store from '../platform/storage.js';
12
+ /** Juice toggles (persisted in localStorage; edited in the ?debug panel + Sensibilidade → Movimento). */
13
+ export const JUICE = (() => {
14
+ const d = { dust: true, sparkle: true, squash: true, hitstop: true, shake: true, shimmer: true };
15
+ const s = store.getJSON(store.KEYS.juice, null);
16
+ if (s && typeof s === 'object')
17
+ for (const k of Object.keys(d))
18
+ if (k in s)
19
+ d[k] = !!s[k];
20
+ return d;
21
+ })();
22
+ export function saveJuice() {
23
+ store.setJSON(store.KEYS.juice, JUICE);
24
+ }
25
+ /** Standard easing (squash recovery, particle fade). Shared → exported (game.js render reads it too). */
26
+ export const easeOut3 = (t) => 1 - Math.pow(1 - t, 3);
27
+ let particles = [];
28
+ let hitstopT = 0, shakeT = 0, shakeDur = 1, shakeMag = 0;
29
+ let fxG = null;
30
+ let rm = {};
31
+ // A LISTA DE JOGADORES entra por injeção desde 2026-08-26. Era `numPlayers`, um `let` de `core/state`
32
+ // importado como binding vivo — e um `let` de módulo é compartilhado por qualquer segundo jogo que a
33
+ // mesma página carregue (D13 do `demos`, ADR-0038). O que entra aqui é o GETTER da rodada que a raiz
34
+ // possui; o `let` que sobra guarda a função, não a lista.
35
+ let getPlayers = () => [];
36
+ export function initFx(deps) {
37
+ fxG = deps.fxG;
38
+ rm = deps.rm;
39
+ getPlayers = deps.getPlayers;
40
+ }
41
+ export function spawnParticle(x, y, vx, vy, life, color, size, grav) {
42
+ if (particles.length >= 160)
43
+ particles.shift();
44
+ particles.push({ x, y, vx, vy, life, max: life, color, size, g: grav || 0 });
45
+ }
46
+ export function puffDust(x, y, n) {
47
+ if (!JUICE.dust || rm.particles)
48
+ return;
49
+ for (let i = 0; i < n; i++)
50
+ spawnParticle(x + (rnd() - 0.5) * 8, y - 1 - rnd() * 2, (rnd() - 0.5) * 0.9, -0.2 - rnd() * 0.4, 14 + rnd() * 10, 0xcfc6b8, rnd() < 0.4 ? 2 : 1, 0.02);
51
+ }
52
+ export function burstSparkle(x, y, color, n) {
53
+ if (!JUICE.sparkle || rm.particles)
54
+ return;
55
+ const N = n || 8;
56
+ for (let i = 0; i < N; i++) {
57
+ const a = (i / N) * Math.PI * 2 + rnd() * 0.5, sp = 0.5 + rnd() * 0.9;
58
+ spawnParticle(x, y, Math.cos(a) * sp, Math.sin(a) * sp - 0.3, 18 + rnd() * 12, color || 0xffd23f, rnd() < 0.5 ? 2 : 1, 0.015);
59
+ }
60
+ }
61
+ export function addShake(mag, dur) {
62
+ if (!JUICE.shake || rm.parallax)
63
+ return;
64
+ shakeMag = Math.max(shakeMag, mag);
65
+ shakeDur = dur;
66
+ shakeT = Math.max(shakeT, dur);
67
+ }
68
+ export function addHitstop(t) {
69
+ if (!JUICE.hitstop)
70
+ return;
71
+ hitstopT = Math.max(hitstopT, t);
72
+ }
73
+ export function setSquash(pl, amt) {
74
+ if (!JUICE.squash || pl.rmWalk)
75
+ return;
76
+ pl.sq = Math.max(-0.28, Math.min(0.2, amt));
77
+ pl.sqT = 8;
78
+ }
79
+ /** Advance particles + decay shake/squash. (fxClock is stepped by game.js — see header.) */
80
+ export function stepFx(dt) {
81
+ if (shakeT > 0)
82
+ shakeT = Math.max(0, shakeT - dt);
83
+ for (const pl of getPlayers())
84
+ if (pl.sqT && pl.sqT > 0)
85
+ pl.sqT = Math.max(0, pl.sqT - dt);
86
+ for (let i = particles.length - 1; i >= 0; i--) {
87
+ const p = particles[i];
88
+ p.life -= dt;
89
+ if (p.life <= 0) {
90
+ particles.splice(i, 1);
91
+ continue;
92
+ }
93
+ p.vy += p.g * dt;
94
+ p.x += p.vx * dt;
95
+ p.y += p.vy * dt;
96
+ }
97
+ }
98
+ export function drawFx() {
99
+ if (!fxG)
100
+ return;
101
+ fxG.clear();
102
+ for (const p of particles) {
103
+ const f = p.life / p.max;
104
+ fxG.beginFill(p.color, 0.9 * easeOut3(f));
105
+ fxG.drawRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);
106
+ fxG.endFill();
107
+ }
108
+ }
109
+ /** Decrement hit-stop and report whether the world is still frozen (game.js early-returns on true). */
110
+ export function tickHitstop(dt) {
111
+ if (hitstopT > 0) {
112
+ hitstopT = Math.max(0, hitstopT - dt);
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+ /** Current screen-shake amplitude (0 = none); the camera applies the random jitter. */
118
+ export function shakeAmp() {
119
+ return shakeT > 0 && shakeMag > 0 ? shakeMag * (shakeT / shakeDur) : 0;
120
+ }
121
+ export const getParticles = () => particles;
122
+ export const getHitstopT = () => hitstopT;
123
+ export const getShakeT = () => shakeT;
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** Papéis que `roleOf(tile)` sabe devolver — os que o repinte do alto contraste aplica direto no tile. */
3
+ export type PaintableRole = 'hazard' | 'climb' | 'water';
4
+ /** Todos os papéis customizáveis. `gate` não vem de `roleOf()`: é usado à parte pelo desenho do portão
5
+ * trancado (game/level-geometry), mas o jogador escolhe a cor dele no mesmo painel. */
6
+ export type HcRoleKey = PaintableRole | 'gate';
7
+ /** Ordem canônica dos papéis — a ordem em que o painel desenha os seletores de cor. */
8
+ export declare const HC_ROLE_KEYS: readonly HcRoleKey[];
9
+ /**
10
+ * Cores padrão por papel (RGB 0-255). Color-blocking: perigo = laranja-quente, escalável/interativo
11
+ * (escada/trampolim) = ciano, água = azul, portão = magenta. A estrutura (pedra/parede) fica no cinza-azulado
12
+ * do nível e por isso não é um papel. Customizável e persistido — ver `HC_ROLE` em render/high-contrast.
13
+ */
14
+ export declare const HC_ROLE_DEF: Record<HcRoleKey, [number, number, number]>;
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // render/hc-role-data — os PAPÉIS SEMÂNTICOS do color-blocking, num só lugar.
3
+ //
4
+ // Existiam duas listas dos mesmos quatro papéis: `RoleKey`/`ROLE_KEYS` em ui/settings-visual (que desenha um
5
+ // seletor de cor por papel) e `PaintableRole`/`HcRoleKey` em render/high-contrast (que repinta os tiles). Nada
6
+ // ligava as duas, e a falha era silenciosa: um quinto papel adicionado ao render ganharia cor e não ganharia
7
+ // seletor, sem erro de tipo em lugar nenhum — o painel simplesmente não ofereceria como customizar.
8
+ //
9
+ // Este módulo é FOLHA de propósito: zero dependências. Ele é importado tanto pelo render quanto pela UI, e um
10
+ // arquivo de dados sem dependência nenhuma pode ser importado dos dois lados sem arrastar o pipeline de
11
+ // renderização para dentro de um painel de configuração (nem o contrário).
12
+ //
13
+ // O que NÃO mora aqui: os rótulos em pt-BR dos papéis, que são apresentação e ficam em ui/settings-visual.
14
+ // Eles são digitados por `HcRoleKey`, então continuam obrigados a cobrir exatamente estes quatro papéis.
15
+ /** Ordem canônica dos papéis — a ordem em que o painel desenha os seletores de cor. */
16
+ export const HC_ROLE_KEYS = ['hazard', 'climb', 'water', 'gate'];
17
+ /**
18
+ * Cores padrão por papel (RGB 0-255). Color-blocking: perigo = laranja-quente, escalável/interativo
19
+ * (escada/trampolim) = ciano, água = azul, portão = magenta. A estrutura (pedra/parede) fica no cinza-azulado
20
+ * do nível e por isso não é um papel. Customizável e persistido — ver `HC_ROLE` em render/high-contrast.
21
+ */
22
+ export const HC_ROLE_DEF = {
23
+ hazard: [255, 110, 45], climb: [55, 225, 205], water: [70, 140, 255], gate: [194, 58, 212],
24
+ };