@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.
- package/LICENSE +661 -0
- package/README.md +107 -0
- package/app/css/style.css +622 -0
- package/app/public/vendor/fonts/andika-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/andika-400.woff2 +0 -0
- package/app/public/vendor/fonts/andika-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/andika-700.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-400.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-700.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-mono-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-mono-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-400.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +88 -0
- package/dist-pkg/boot/create-game.d.ts +159 -0
- package/dist-pkg/boot/create-game.js +266 -0
- package/dist-pkg/core/a11y-sr.d.ts +4 -0
- package/dist-pkg/core/a11y-sr.js +20 -0
- package/dist-pkg/core/actions.d.ts +100 -0
- package/dist-pkg/core/actions.js +149 -0
- package/dist-pkg/core/anel.d.ts +17 -0
- package/dist-pkg/core/anel.js +31 -0
- package/dist-pkg/core/collision.d.ts +21 -0
- package/dist-pkg/core/collision.js +62 -0
- package/dist-pkg/core/constants.d.ts +84 -0
- package/dist-pkg/core/constants.js +64 -0
- package/dist-pkg/core/contract.d.ts +227 -0
- package/dist-pkg/core/contract.js +184 -0
- package/dist-pkg/core/dom-query.d.ts +9 -0
- package/dist-pkg/core/dom-query.js +25 -0
- package/dist-pkg/core/entity.d.ts +150 -0
- package/dist-pkg/core/entity.js +38 -0
- package/dist-pkg/core/escape-html.d.ts +16 -0
- package/dist-pkg/core/escape-html.js +32 -0
- package/dist-pkg/core/i18n.d.ts +71 -0
- package/dist-pkg/core/i18n.js +236 -0
- package/dist-pkg/core/layers.d.ts +39 -0
- package/dist-pkg/core/layers.js +71 -0
- package/dist-pkg/core/letter-grid.d.ts +55 -0
- package/dist-pkg/core/letter-grid.js +116 -0
- package/dist-pkg/core/loop.d.ts +17 -0
- package/dist-pkg/core/loop.js +36 -0
- package/dist-pkg/core/password.d.ts +34 -0
- package/dist-pkg/core/password.js +205 -0
- package/dist-pkg/core/rng.d.ts +20 -0
- package/dist-pkg/core/rng.js +61 -0
- package/dist-pkg/core/rotulo-acessivel.d.ts +17 -0
- package/dist-pkg/core/rotulo-acessivel.js +41 -0
- package/dist-pkg/core/run-state.d.ts +115 -0
- package/dist-pkg/core/run-state.js +40 -0
- package/dist-pkg/core/scenes.d.ts +68 -0
- package/dist-pkg/core/scenes.js +64 -0
- package/dist-pkg/core/screens.d.ts +16 -0
- package/dist-pkg/core/screens.js +28 -0
- package/dist-pkg/core/state.d.ts +138 -0
- package/dist-pkg/core/state.js +290 -0
- package/dist-pkg/core/tiles.d.ts +12 -0
- package/dist-pkg/core/tiles.js +61 -0
- package/dist-pkg/core/world.d.ts +4 -0
- package/dist-pkg/core/world.js +58 -0
- package/dist-pkg/educational/activities-registry.d.ts +47 -0
- package/dist-pkg/educational/activities-registry.js +95 -0
- package/dist-pkg/i18n/en.d.ts +3 -0
- package/dist-pkg/i18n/en.js +560 -0
- package/dist-pkg/i18n/es.d.ts +3 -0
- package/dist-pkg/i18n/es.js +558 -0
- package/dist-pkg/i18n/pt.d.ts +3 -0
- package/dist-pkg/i18n/pt.js +618 -0
- package/dist-pkg/input/default-bindings.d.ts +24 -0
- package/dist-pkg/input/default-bindings.js +122 -0
- package/dist-pkg/input/devices.d.ts +15 -0
- package/dist-pkg/input/devices.js +31 -0
- package/dist-pkg/input/edges.d.ts +58 -0
- package/dist-pkg/input/edges.js +49 -0
- package/dist-pkg/input/gamepad.d.ts +203 -0
- package/dist-pkg/input/gamepad.js +531 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +61 -0
- package/dist-pkg/input/keyboard-runtime.js +83 -0
- package/dist-pkg/input/keyboard.d.ts +45 -0
- package/dist-pkg/input/keyboard.js +80 -0
- package/dist-pkg/input/keydown.d.ts +270 -0
- package/dist-pkg/input/keydown.js +381 -0
- package/dist-pkg/input/latch.d.ts +30 -0
- package/dist-pkg/input/latch.js +63 -0
- package/dist-pkg/input/pointer-space.d.ts +40 -0
- package/dist-pkg/input/pointer-space.js +51 -0
- package/dist-pkg/input/state.d.ts +11 -0
- package/dist-pkg/input/state.js +12 -0
- package/dist-pkg/input/touch-bindings.d.ts +210 -0
- package/dist-pkg/input/touch-bindings.js +365 -0
- package/dist-pkg/input/touch.d.ts +173 -0
- package/dist-pkg/input/touch.js +314 -0
- package/dist-pkg/input/transports.d.ts +90 -0
- package/dist-pkg/input/transports.js +96 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +58 -0
- package/dist-pkg/input/vocabulary-migration.js +125 -0
- package/dist-pkg/platform/audio-ambient.d.ts +27 -0
- package/dist-pkg/platform/audio-ambient.js +89 -0
- package/dist-pkg/platform/audio-earcons.d.ts +24 -0
- package/dist-pkg/platform/audio-earcons.js +62 -0
- package/dist-pkg/platform/audio-jingles.d.ts +17 -0
- package/dist-pkg/platform/audio-jingles.js +54 -0
- package/dist-pkg/platform/audio-mixer.d.ts +14 -0
- package/dist-pkg/platform/audio-mixer.js +62 -0
- package/dist-pkg/platform/audio-nav.d.ts +51 -0
- package/dist-pkg/platform/audio-nav.js +99 -0
- package/dist-pkg/platform/audio-sonar.d.ts +71 -0
- package/dist-pkg/platform/audio-sonar.js +173 -0
- package/dist-pkg/platform/audio.d.ts +32 -0
- package/dist-pkg/platform/audio.js +191 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +19 -0
- package/dist-pkg/platform/interruptible-speech.js +79 -0
- package/dist-pkg/platform/speech.d.ts +2 -0
- package/dist-pkg/platform/speech.js +35 -0
- package/dist-pkg/platform/storage.d.ts +87 -0
- package/dist-pkg/platform/storage.js +148 -0
- package/dist-pkg/platform/tts.d.ts +33 -0
- package/dist-pkg/platform/tts.js +142 -0
- package/dist-pkg/render/camera.d.ts +58 -0
- package/dist-pkg/render/camera.js +105 -0
- package/dist-pkg/render/canvas.d.ts +13 -0
- package/dist-pkg/render/canvas.js +26 -0
- package/dist-pkg/render/cenario-data.d.ts +127 -0
- package/dist-pkg/render/cenario-data.js +145 -0
- package/dist-pkg/render/city-tex.d.ts +63 -0
- package/dist-pkg/render/city-tex.js +228 -0
- package/dist-pkg/render/city-tiles.d.ts +11 -0
- package/dist-pkg/render/city-tiles.js +140 -0
- package/dist-pkg/render/crt.d.ts +19 -0
- package/dist-pkg/render/crt.js +92 -0
- package/dist-pkg/render/cvd-matrices.d.ts +35 -0
- package/dist-pkg/render/cvd-matrices.js +91 -0
- package/dist-pkg/render/draw.d.ts +149 -0
- package/dist-pkg/render/draw.js +221 -0
- package/dist-pkg/render/fx.d.ts +63 -0
- package/dist-pkg/render/fx.js +123 -0
- package/dist-pkg/render/hc-role-data.d.ts +14 -0
- package/dist-pkg/render/hc-role-data.js +24 -0
- package/dist-pkg/render/high-contrast.d.ts +109 -0
- package/dist-pkg/render/high-contrast.js +254 -0
- package/dist-pkg/render/lq-filter.d.ts +37 -0
- package/dist-pkg/render/lq-filter.js +94 -0
- package/dist-pkg/render/minimap.d.ts +11 -0
- package/dist-pkg/render/minimap.js +94 -0
- package/dist-pkg/render/parallax.d.ts +85 -0
- package/dist-pkg/render/parallax.js +158 -0
- package/dist-pkg/render/player-anim.d.ts +71 -0
- package/dist-pkg/render/player-anim.js +152 -0
- package/dist-pkg/render/port.d.ts +143 -0
- package/dist-pkg/render/port.js +29 -0
- package/dist-pkg/render/recycling-tex.d.ts +48 -0
- package/dist-pkg/render/recycling-tex.js +164 -0
- package/dist-pkg/render/scene-city.d.ts +77 -0
- package/dist-pkg/render/scene-city.js +181 -0
- package/dist-pkg/render/scene-parallax.d.ts +72 -0
- package/dist-pkg/render/scene-parallax.js +426 -0
- package/dist-pkg/render/scene-sky.d.ts +126 -0
- package/dist-pkg/render/scene-sky.js +295 -0
- package/dist-pkg/render/screen-pipeline.d.ts +134 -0
- package/dist-pkg/render/screen-pipeline.js +177 -0
- package/dist-pkg/render/set-cenario.d.ts +54 -0
- package/dist-pkg/render/set-cenario.js +103 -0
- package/dist-pkg/render/sprite-fx.d.ts +3 -0
- package/dist-pkg/render/sprite-fx.js +40 -0
- package/dist-pkg/render/textures.d.ts +75 -0
- package/dist-pkg/render/textures.js +240 -0
- package/dist-pkg/render/title-scene.d.ts +46 -0
- package/dist-pkg/render/title-scene.js +75 -0
- package/dist-pkg/render/viewports.d.ts +52 -0
- package/dist-pkg/render/viewports.js +185 -0
- package/dist-pkg/render/viz-axes.d.ts +98 -0
- package/dist-pkg/render/viz-axes.js +184 -0
- package/dist-pkg/render/viz-modes.d.ts +61 -0
- package/dist-pkg/render/viz-modes.js +74 -0
- package/dist-pkg/render/viz-setters.d.ts +130 -0
- package/dist-pkg/render/viz-setters.js +244 -0
- package/dist-pkg/render/weather.d.ts +96 -0
- package/dist-pkg/render/weather.js +171 -0
- package/dist-pkg/render/wheelchair-sprites.d.ts +24 -0
- package/dist-pkg/render/wheelchair-sprites.js +63 -0
- package/dist-pkg/render/world-tex.d.ts +18 -0
- package/dist-pkg/render/world-tex.js +87 -0
- package/dist-pkg/ui/activities-menu.d.ts +258 -0
- package/dist-pkg/ui/activities-menu.js +648 -0
- package/dist-pkg/ui/caa-sets.d.ts +39 -0
- package/dist-pkg/ui/caa-sets.js +65 -0
- package/dist-pkg/ui/changed-mark.d.ts +15 -0
- package/dist-pkg/ui/changed-mark.js +96 -0
- package/dist-pkg/ui/debug-panel.d.ts +67 -0
- package/dist-pkg/ui/debug-panel.js +173 -0
- package/dist-pkg/ui/dom.d.ts +24 -0
- package/dist-pkg/ui/dom.js +47 -0
- package/dist-pkg/ui/focus-trap.d.ts +71 -0
- package/dist-pkg/ui/focus-trap.js +99 -0
- package/dist-pkg/ui/fonts.d.ts +41 -0
- package/dist-pkg/ui/fonts.js +59 -0
- package/dist-pkg/ui/hud.d.ts +137 -0
- package/dist-pkg/ui/hud.js +229 -0
- package/dist-pkg/ui/item-announcement.d.ts +19 -0
- package/dist-pkg/ui/item-announcement.js +36 -0
- package/dist-pkg/ui/layout.d.ts +6 -0
- package/dist-pkg/ui/layout.js +54 -0
- package/dist-pkg/ui/loop-crash.d.ts +19 -0
- package/dist-pkg/ui/loop-crash.js +60 -0
- package/dist-pkg/ui/map-hub.d.ts +56 -0
- package/dist-pkg/ui/map-hub.js +138 -0
- package/dist-pkg/ui/menu-nav.d.ts +141 -0
- package/dist-pkg/ui/menu-nav.js +390 -0
- package/dist-pkg/ui/pause-icons.d.ts +327 -0
- package/dist-pkg/ui/pause-icons.js +620 -0
- package/dist-pkg/ui/reach-notice.d.ts +34 -0
- package/dist-pkg/ui/reach-notice.js +71 -0
- package/dist-pkg/ui/settings-audio.d.ts +139 -0
- package/dist-pkg/ui/settings-audio.js +544 -0
- package/dist-pkg/ui/settings-caa.d.ts +45 -0
- package/dist-pkg/ui/settings-caa.js +137 -0
- package/dist-pkg/ui/settings-controls.d.ts +100 -0
- package/dist-pkg/ui/settings-controls.js +152 -0
- package/dist-pkg/ui/settings-empathy.d.ts +62 -0
- package/dist-pkg/ui/settings-empathy.js +137 -0
- package/dist-pkg/ui/settings-motion.d.ts +95 -0
- package/dist-pkg/ui/settings-motion.js +282 -0
- package/dist-pkg/ui/settings-motor.d.ts +77 -0
- package/dist-pkg/ui/settings-motor.js +215 -0
- package/dist-pkg/ui/settings-panel.d.ts +76 -0
- package/dist-pkg/ui/settings-panel.js +187 -0
- package/dist-pkg/ui/settings-typo.d.ts +74 -0
- package/dist-pkg/ui/settings-typo.js +150 -0
- package/dist-pkg/ui/settings-visual.d.ts +111 -0
- package/dist-pkg/ui/settings-visual.js +252 -0
- package/dist-pkg/ui/shell.d.ts +239 -0
- package/dist-pkg/ui/shell.js +430 -0
- package/dist-pkg/ui/title.d.ts +36 -0
- package/dist-pkg/ui/title.js +60 -0
- package/dist-pkg/ui/vlibras.d.ts +11 -0
- package/dist-pkg/ui/vlibras.js +87 -0
- package/dist-pkg/ui/webcam.d.ts +7 -0
- package/dist-pkg/ui/webcam.js +88 -0
- package/docs/CREDITS.md +64 -0
- package/docs/LICENSES.md +132 -0
- package/package.json +115 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { CenarioTema, TemaMorros, TemaPredios, FaixaDePredios } from './cenario-data.js';
|
|
3
|
+
import type { CriarAzulejo } from './port.js';
|
|
4
|
+
/** Uma camada de fundo: `factor` = quanto do movimento horizontal da câmera ela reproduz; `fy`, o vertical. */
|
|
5
|
+
export interface CamadaParallax {
|
|
6
|
+
key: string;
|
|
7
|
+
factor: number;
|
|
8
|
+
fy: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const PARALLAX: readonly CamadaParallax[];
|
|
11
|
+
/** Índice da camada → número do arquivo PNG da Cidade (`c4.png`, `c3.png`, `c2.png`). */
|
|
12
|
+
export declare const ARQUIVO_POR_CAMADA: readonly number[];
|
|
13
|
+
/** Onde uma camada fica neste quadro. `x`/`y` = posição DENTRO do camera; `tileX`/`tileY` = rolagem da textura. */
|
|
14
|
+
export interface PosicaoParallax {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
tileX: number;
|
|
18
|
+
tileY: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A matemática do parallax, sem sprite nenhum.
|
|
22
|
+
*
|
|
23
|
+
* `x`/`y` = `camX`/`camY` porque o container `camera` está em `-camX`/`-camY`: a soma dá ZERO, e a camada
|
|
24
|
+
* fica parada na tela. É esse cancelamento que faz o fundo parecer fundo.
|
|
25
|
+
* `tileX`/`tileY` = `-cam * fator`: quanto MENOR o fator, menos a textura rola, mais distante a camada parece.
|
|
26
|
+
* Com `reduzido` (movimento reduzido — `rm.parallax`), a rolagem trava em zero e o fundo vira papel de
|
|
27
|
+
* parede; a contra-posição CONTINUA valendo, senão o fundo passaria a deslizar junto com o mundo — o oposto
|
|
28
|
+
* exato do que a opção existe para dar.
|
|
29
|
+
*/
|
|
30
|
+
export declare function posicoesParallax(camX: number, camY: number, camadas?: readonly CamadaParallax[], reduzido?: boolean): PosicaoParallax[];
|
|
31
|
+
/** `tilePosition` de um `TilingSprite` — só o `set`, que é como o original o escreve. */
|
|
32
|
+
interface PontoObservavel {
|
|
33
|
+
set(x: number, y: number): void;
|
|
34
|
+
}
|
|
35
|
+
/** O que tocamos de um `PIXI.TilingSprite`, e só isso. */
|
|
36
|
+
export interface TilingSpriteLike {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
texture: unknown;
|
|
40
|
+
tilePosition: PontoObservavel;
|
|
41
|
+
/**
|
|
42
|
+
* A ordem-z. Este módulo NÃO a lê — quem a escreve é a raiz de composição, ao encaixar as três camadas
|
|
43
|
+
* no registro canônico do ADR-0020. Está aqui porque a interface é EXPORTADA: ela não descreve só o que
|
|
44
|
+
* o parallax toca, descreve o que ele DEVOLVE, e o consumidor precisa deste campo para posicionar.
|
|
45
|
+
*/
|
|
46
|
+
zIndex: number;
|
|
47
|
+
}
|
|
48
|
+
/** `camera` — só a inserção em posição de z fixa. */
|
|
49
|
+
interface ContainerLike {
|
|
50
|
+
addChildAt(child: unknown, index: number): unknown;
|
|
51
|
+
}
|
|
52
|
+
/** `starsG`/`skyDecoG`/`fogG`: contra-posicionados junto com o parallax. */
|
|
53
|
+
interface PosicionavelLike {
|
|
54
|
+
position: {
|
|
55
|
+
set(x: number, y: number): void;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export interface ParallaxCtx {
|
|
59
|
+
camera: ContainerLike;
|
|
60
|
+
criarAzulejo: CriarAzulejo<TilingSpriteLike>;
|
|
61
|
+
placeholderTex: (i: number) => unknown;
|
|
62
|
+
skyTex: (T: CenarioTema) => unknown;
|
|
63
|
+
/** themeHillsTexture — banda de morros. O `tema` escolhe a SILHUETA plantada em cima (árvores, cerca). */
|
|
64
|
+
hillsTex: (T: TemaMorros, near: boolean, tema: string) => unknown;
|
|
65
|
+
/** themeCitySkyTexture — céu + prédios distantes da Cidade, a única camada opaca dela. */
|
|
66
|
+
citySkyTex: (T: TemaPredios) => unknown;
|
|
67
|
+
/** themeSkylineTexture — uma faixa de prédios sobre transparência. */
|
|
68
|
+
skylineTex: (faixa: FaixaDePredios, semente: number) => unknown;
|
|
69
|
+
rm: {
|
|
70
|
+
parallax?: boolean;
|
|
71
|
+
};
|
|
72
|
+
getCenario: () => string;
|
|
73
|
+
getVizMode: () => string;
|
|
74
|
+
clearParallaxTexCache: () => void;
|
|
75
|
+
getDecorDeTela: () => ReadonlyArray<PosicionavelLike | null | undefined>;
|
|
76
|
+
}
|
|
77
|
+
export interface ParallaxApi {
|
|
78
|
+
layers: TilingSpriteLike[];
|
|
79
|
+
texNormal: unknown[];
|
|
80
|
+
updateParallax(camX: number, camY: number): void;
|
|
81
|
+
aplicarTemaParallax(theme: string, T: CenarioTema): void;
|
|
82
|
+
}
|
|
83
|
+
/** Cria as 3 camadas dentro do `camera` e devolve a API que as move e as veste. */
|
|
84
|
+
export declare function createParallax(ctx: ParallaxCtx): ParallaxApi;
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// render/parallax — AS TRÊS CAMADAS DE FUNDO: onde cada uma fica, e de que tema ela é feita.
|
|
3
|
+
//
|
|
4
|
+
// Camada 1 do jogo é o tileset + o personagem. Atrás dele vêm três `TilingSprite`, e é isso que este módulo
|
|
5
|
+
// governa: quanto cada uma se desloca quando a câmera anda (`updateParallax`), e qual textura cada uma veste
|
|
6
|
+
// quando o cenário muda (`aplicarTemaParallax`). Nada mais — o tileset do mundo, o céu vivo da v3, o clima e a
|
|
7
|
+
// Cidade têm cada um o seu módulo, e nenhum deles passa por aqui.
|
|
8
|
+
//
|
|
9
|
+
// ======================= A ENTREGA: A CONTA SAIU DE DENTRO DO EFEITO =======================
|
|
10
|
+
// `updateParallax` era MATEMÁTICA PURA DISFARÇADA DE EFEITO. Dado (camX, camY) e os fatores de profundidade,
|
|
11
|
+
// existe UMA posição determinada para cada camada — mas no monólito essa posição só existia como o lado
|
|
12
|
+
// esquerdo de quatro atribuições dentro de um `for`, e a única forma de conferi-la era subir o jogo inteiro e
|
|
13
|
+
// olhar. Aqui a conta é `posicoesParallax(camX, camY, camadas, reduzido)`, uma função pura que devolve
|
|
14
|
+
// `{x, y, tileX, tileY}` por camada, e o `updateParallax` virou o carimbo dela nos sprites.
|
|
15
|
+
//
|
|
16
|
+
// O que essa separação torna verificável — e o que quebraria em SILÊNCIO sem ela:
|
|
17
|
+
// 1. A CONTRA-POSIÇÃO. Os três sprites vivem DENTRO do container `camera` (têm de viver: é assim que também
|
|
18
|
+
// aparecem nas render-textures do multi-tela). O `camera` anda `-camX` a cada quadro; cada camada é posta
|
|
19
|
+
// em `+camX` para cancelar exatamente esse movimento e ficar PARADA na tela. É o cancelamento que faz o
|
|
20
|
+
// fundo parecer fundo. Inverta o sinal e o céu passa a correr com o dobro da velocidade do mundo, na
|
|
21
|
+
// direção errada — o jogo continua rodando, os testes de física continuam verdes, e só quem olhar percebe.
|
|
22
|
+
// 2. A PROPORCIONALIDADE. O `tilePosition` de cada camada rola por `-camX * fator`. Camada mais distante
|
|
23
|
+
// (fator menor) se mexe MENOS. Ignorar o fator (ou trocá-lo entre duas camadas) achata a profundidade sem
|
|
24
|
+
// erro nenhum em lugar nenhum.
|
|
25
|
+
// 3. O MOVIMENTO REDUZIDO. Com `rm.parallax` ligado (acessibilidade: gatilho vestibular), o `tilePosition`
|
|
26
|
+
// trava em zero — o fundo vira papel de parede — MAS a contra-posição continua valendo. Trocar essa ordem
|
|
27
|
+
// (travar `x`/`y` em vez do `tilePosition`) grudaria o fundo no mundo e produziria exatamente o
|
|
28
|
+
// deslizamento que a opção existe para eliminar, para a pessoa que pediu para não ver deslizamento.
|
|
29
|
+
//
|
|
30
|
+
// ======================= POR QUE A FRONTEIRA CAIU AQUI, E NÃO ANTES NEM DEPOIS =======================
|
|
31
|
+
// ANTES (o dado dos temas) saiu para render/cenario-data.ts: as cores não são geometria de câmera, e são
|
|
32
|
+
// lidas por scene-sky e pelo attract, que não têm nada a ver com rolagem. `PARALLAX` FICOU aqui, e não lá,
|
|
33
|
+
// porque os fatores não variam por tema — são a definição de "o que é fundo" — e porque a conta que os
|
|
34
|
+
// consome é a deste arquivo.
|
|
35
|
+
// DEPOIS (a orquestração da troca de cenário) ficou em render/set-cenario.ts. `setCenario` faz TRÊS coisas:
|
|
36
|
+
// repinta o fundo, RECONSTRÓI A TEXTURA DO MUNDO a partir dos PNG de tile do tema, e avisa a vida ambiente e
|
|
37
|
+
// o alto contraste. Só a primeira é parallax. Trazer as outras duas para cá daria a este módulo a textura do
|
|
38
|
+
// tileset — que não é fundo, é a Camada 1 — e o ciclo de vida do alto contraste. Por isso `aplicarTemaParallax`
|
|
39
|
+
// mora aqui e é CHAMADO de lá: este módulo sabe vestir as três camadas e não sabe por quê.
|
|
40
|
+
//
|
|
41
|
+
// ======================= A PARTE IMPURA, E O QUE ELA GUARDA =======================
|
|
42
|
+
// `aplicarTemaParallax` tem duas metades assimétricas, e a assimetria é o ponto:
|
|
43
|
+
// · Tema `v3` (Campo/Amanhecer/Noite/Floresta): as texturas são GERADAS na hora (render/scene-parallax), de
|
|
44
|
+
// forma síncrona. Não há espera, logo não há corrida.
|
|
45
|
+
// · Cidade (`v3:false`): as três texturas são PNG baixados, e chegam quando chegarem. Entre o pedido e a
|
|
46
|
+
// chegada a pessoa pode ter trocado de cenário de novo — por isso cada callback abre com
|
|
47
|
+
// `if (getCenario() !== theme) return`. Sem essa guarda, um PNG atrasado da Cidade sobrescreveria o céu do
|
|
48
|
+
// tema recém-escolhido. É a corrida mais fácil de introduzir e a mais difícil de reproduzir de mão, e é
|
|
49
|
+
// por ela, mais do que por qualquer outra coisa, que esta metade também entrou num módulo testável.
|
|
50
|
+
// Nos dois caminhos a ordem é a mesma e importa: grava em `texNormal` → invalida o cache de recolor
|
|
51
|
+
// (`clearParallaxTexCache`) → e SÓ pinta o sprite se o modo de visão for 'normal'. Nos modos acessíveis quem
|
|
52
|
+
// pinta é render/viz-setters, lendo a textura crua que acabamos de gravar; pintar aqui atropelaria o recolor.
|
|
53
|
+
//
|
|
54
|
+
// ======================= INJEÇÃO, INTERFACE ESTRUTURAL E ORDEM DE BOOT =======================
|
|
55
|
+
// Nada de `import * as PIXI`: declaramos só o que tocamos — um `TilingSprite` é, aqui, quatro campos
|
|
56
|
+
// (`x`, `y`, `texture`, `tilePosition`) e um construtor de três argumentos. É o precedente de
|
|
57
|
+
// render/scene-sky e render/viewports, e é o que deixa o módulo (e a factory INTEIRA, não só a parte pura)
|
|
58
|
+
// rodar no project `node`, com sprites de mentira e um `Image` falso.
|
|
59
|
+
// Pela mesma razão os três geradores de textura (`placeholderTex`/`skyTex`/`hillsTex`) entram INJETADOS em vez
|
|
60
|
+
// de importados de render/scene-parallax: eles chamam `makeCanvas`, e importá-los aqui obrigaria todo teste da
|
|
61
|
+
// factory a ter um `document`.
|
|
62
|
+
// · `rm` entra por VALOR: é `const` no game.js, mutado in place pelo painel de movimento reduzido (mesma
|
|
63
|
+
// decisão de render/draw.ts). Ler `rm.parallax` a cada quadro já vê o valor novo.
|
|
64
|
+
// · `getCenario`/`getVizMode` entram por GETTER: são as mega-variáveis de core/state.js, REATRIBUÍDAS.
|
|
65
|
+
// · `getDecorDeTela` é GETTER porque `starsG`/`skyDecoG`/`fogG` são declarados MUITO depois no game.js — o
|
|
66
|
+
// monólito protegia isso com `typeof starsG!=='undefined'`. Aqui o game.js passa `() => [starsG, skyDecoG,
|
|
67
|
+
// fogG]` (são `var`, logo içados) e a guarda por elemento fica deste lado.
|
|
68
|
+
// · `createParallax` DEVOLVE `layers` e `texNormal` em vez de recebê-los: o game.js os mantém como `const`
|
|
69
|
+
// desestruturados do retorno, e `texNormal` continua sendo o MESMO array que `initViewports` recebe por
|
|
70
|
+
// VALOR — `aplicarTemaParallax` troca os ELEMENTOS in place, nunca o array.
|
|
71
|
+
//
|
|
72
|
+
// ⚠️ ORDEM DE BOOT: `createParallax` tem de rodar logo depois de `camera` existir e ANTES de qualquer coisa que
|
|
73
|
+
// dependa de `parallaxLayers` — o `camera.addChildAt(ts, i)` dá a ordem-z do fundo, e `starsG` é inserido
|
|
74
|
+
// relativamente a `parallaxLayers[1]`. E tem de rodar ANTES do `setCenario` do boot, que é quem veste as
|
|
75
|
+
// camadas com o tema salvo.
|
|
76
|
+
//
|
|
77
|
+
// SEM I/O NO IMPORT: o corpo do módulo declara `PARALLAX` e funções. Nenhum sprite é criado, nenhuma textura é
|
|
78
|
+
// gerada e nenhuma imagem é pedida até `createParallax`/`aplicarTemaParallax` serem chamados.
|
|
79
|
+
// Ver docs/5-Refactoring/plano-modularizacao-mapa.md (D2-b).
|
|
80
|
+
import { LOGICAL_W, LOGICAL_H } from '../core/constants.js';
|
|
81
|
+
/* Parallax: 3 camadas de FUNDO atrás do tileset (Camada 1 = tileset+personagem).
|
|
82
|
+
Camada 4 (fator 0.10) é a mais distante e "quase não se mexe" — receberá a maior imagem possível do
|
|
83
|
+
PixelLab. Vivem DENTRO do camera (contra-posicionadas p/ ficarem fixas na tela) para também aparecerem nas
|
|
84
|
+
render-textures do multiplayer. tilePosition faz o scroll fracionado → ilusão de profundidade.
|
|
85
|
+
A ORDEM do array é a ordem-z: índice 0 no fundo. */
|
|
86
|
+
export const PARALLAX = [
|
|
87
|
+
{ key: 'sky', factor: 0.10, fy: 0 }, // Camada 4 — mais distante (céu/horizonte), maior imagem
|
|
88
|
+
{ key: 'far', factor: 0.28, fy: 0 }, // Camada 3
|
|
89
|
+
{ key: 'near', factor: 0.52, fy: 0 }, // Camada 2 — mais próxima do tileset
|
|
90
|
+
];
|
|
91
|
+
/** Índice da camada → número do arquivo PNG da Cidade (`c4.png`, `c3.png`, `c2.png`). */
|
|
92
|
+
export const ARQUIVO_POR_CAMADA = [4, 3, 2];
|
|
93
|
+
/**
|
|
94
|
+
* A matemática do parallax, sem sprite nenhum.
|
|
95
|
+
*
|
|
96
|
+
* `x`/`y` = `camX`/`camY` porque o container `camera` está em `-camX`/`-camY`: a soma dá ZERO, e a camada
|
|
97
|
+
* fica parada na tela. É esse cancelamento que faz o fundo parecer fundo.
|
|
98
|
+
* `tileX`/`tileY` = `-cam * fator`: quanto MENOR o fator, menos a textura rola, mais distante a camada parece.
|
|
99
|
+
* Com `reduzido` (movimento reduzido — `rm.parallax`), a rolagem trava em zero e o fundo vira papel de
|
|
100
|
+
* parede; a contra-posição CONTINUA valendo, senão o fundo passaria a deslizar junto com o mundo — o oposto
|
|
101
|
+
* exato do que a opção existe para dar.
|
|
102
|
+
*/
|
|
103
|
+
export function posicoesParallax(camX, camY, camadas = PARALLAX, reduzido = false) {
|
|
104
|
+
return camadas.map((p) => ({
|
|
105
|
+
x: camX,
|
|
106
|
+
y: camY,
|
|
107
|
+
tileX: reduzido ? 0 : -camX * p.factor,
|
|
108
|
+
tileY: reduzido ? 0 : -camY * p.fy,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
/* ===================== a montagem + o carimbo (impuro) ===================== */
|
|
112
|
+
/** Cria as 3 camadas dentro do `camera` e devolve a API que as move e as veste. */
|
|
113
|
+
export function createParallax(ctx) {
|
|
114
|
+
const layers = PARALLAX.map((_p, i) => {
|
|
115
|
+
const ts = ctx.criarAzulejo(ctx.placeholderTex(i), LOGICAL_W, LOGICAL_H);
|
|
116
|
+
ctx.camera.addChildAt(ts, i); // i=0 (sky) fica no fundo; depois far, near; tileset entra por cima
|
|
117
|
+
return ts;
|
|
118
|
+
});
|
|
119
|
+
// texturas normais (recoloridas p/ o fundo no alto contraste). Array `const` no game.js: os ELEMENTOS mudam.
|
|
120
|
+
const texNormal = layers.map((ts) => ts.texture);
|
|
121
|
+
function updateParallax(camX, camY) {
|
|
122
|
+
const pos = posicoesParallax(camX, camY, PARALLAX, !!ctx.rm.parallax);
|
|
123
|
+
for (let i = 0; i < layers.length; i++) {
|
|
124
|
+
const ts = layers[i], q = pos[i];
|
|
125
|
+
ts.x = q.x;
|
|
126
|
+
ts.y = q.y; // anula o camera → fixa na tela
|
|
127
|
+
ts.tilePosition.set(q.tileX, q.tileY); // rolagem fracionada → profundidade (0,0 no movimento reduzido)
|
|
128
|
+
}
|
|
129
|
+
// L6: decor de TELA da v3 (estrelas atrás dos morros · nuvens/pássaros à frente · névoa na frente de tudo).
|
|
130
|
+
// Guarda por elemento: no boot elas ainda não existem (eram o `typeof starsG!=='undefined'` do monólito).
|
|
131
|
+
for (const g of ctx.getDecorDeTela()) {
|
|
132
|
+
if (g)
|
|
133
|
+
g.position.set(camX, camY);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Grava a textura crua da camada `i`, invalida o recolor e — só no modo 'normal' — pinta o sprite. */
|
|
137
|
+
function vestir(i, t) {
|
|
138
|
+
texNormal[i] = t;
|
|
139
|
+
ctx.clearParallaxTexCache();
|
|
140
|
+
if (ctx.getVizMode() === 'normal')
|
|
141
|
+
layers[i].texture = t; // nos modos acessíveis quem pinta é viz-setters
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* TODA TROCA DE TEMA É SÍNCRONA AGORA (ADR-0042), e isso é mais que uma simplificação.
|
|
145
|
+
*
|
|
146
|
+
* Enquanto a Cidade vinha de PNG, este caminho era assíncrono e carregava a corrida junto: três `Image`
|
|
147
|
+
* baixando, e cada `onload` tendo de perguntar `getCenario() !== theme` antes de pintar, porque a criança
|
|
148
|
+
* podia ter trocado de cenário no meio. Isso sumiu com os arquivos — não há mais o que baixar, e portanto
|
|
149
|
+
* não há mais corrida que possa perder.
|
|
150
|
+
*/
|
|
151
|
+
function aplicarTemaParallax(theme, T) {
|
|
152
|
+
const texs = T.fundo === 'predios'
|
|
153
|
+
? [ctx.citySkyTex(T), ctx.skylineTex(T.predios[1], 977), ctx.skylineTex(T.predios[2], 131)]
|
|
154
|
+
: [ctx.skyTex(T), ctx.hillsTex(T, false, theme), ctx.hillsTex(T, true, theme)];
|
|
155
|
+
layers.forEach((_ts, i) => vestir(i, texs[i]));
|
|
156
|
+
}
|
|
157
|
+
return { layers, texNormal, updateParallax, aplicarTemaParallax };
|
|
158
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { ControlledPlayer } from '../core/entity.js';
|
|
3
|
+
/** Uma textura de quadro. Opaca de propósito: este módulo ESCOLHE quadros, nunca os desenha. */
|
|
4
|
+
export type Frame = unknown;
|
|
5
|
+
/** Uma "gracinha" de descanso (render/sprites.FLAVORS): `seq` indexa `tex`, `hold` é o nº de ticks por passo. */
|
|
6
|
+
export interface FlavorLike {
|
|
7
|
+
seq: readonly number[];
|
|
8
|
+
hold: number;
|
|
9
|
+
tex: readonly Frame[];
|
|
10
|
+
}
|
|
11
|
+
/** O conjunto COMPLETO de quadros do personagem. Espelha os `TEX_*` de render/sprites, com nomes curtos. */
|
|
12
|
+
export interface PlayerTextures {
|
|
13
|
+
idle: readonly Frame[];
|
|
14
|
+
walk: readonly Frame[];
|
|
15
|
+
run: readonly Frame[];
|
|
16
|
+
jumpUp: Frame;
|
|
17
|
+
jumpDown: Frame;
|
|
18
|
+
climb: readonly Frame[];
|
|
19
|
+
fly: Frame;
|
|
20
|
+
clingWall: readonly Frame[];
|
|
21
|
+
clingCeil: readonly Frame[];
|
|
22
|
+
swim: readonly Frame[];
|
|
23
|
+
swimIdle: readonly Frame[];
|
|
24
|
+
flavors: readonly FlavorLike[];
|
|
25
|
+
}
|
|
26
|
+
/** Os campos do jogador que a ANIMAÇÃO lê ou escreve. O objeto real (game/player.makePlayer) é superconjunto. */
|
|
27
|
+
/**
|
|
28
|
+
* O jogador visto pela ANIMAÇÃO — derivado de core/entity. O que cada campo significa está documentado lá,
|
|
29
|
+
* uma vez; o que importa registrar AQUI é o que este módulo faz com eles:
|
|
30
|
+
*
|
|
31
|
+
* · LÊ o que a física produziu: `vx`/`vy`, os três contatos, `clinging`/`clingN`/`flying` e `airTime`
|
|
32
|
+
* (quadros desde que saiu do chão — a base do coyote-time). `runCane` decide se `running` pode ligar.
|
|
33
|
+
* · LÊ as preferências de Movimento Reduzido: `rmWalk` congela TODA a locomoção num quadro único,
|
|
34
|
+
* `rmBreath` congela a respiração do idle, `rmFlavor` desliga as gracinhas.
|
|
35
|
+
* · ESCREVE os relógios: `anim` (respiração), `walkAnim` (o passo — NUNCA reseta, senão o ciclo de 8
|
|
36
|
+
* reinicia), `climbFrame` (quadro CONGELADO da ventosa), `idleNow`/`idleTime`, `flavor`/`flavorT`,
|
|
37
|
+
* `walking`/`running` (lidos pelo desenho da bengala em render/draw) e `_tx`, o quadro-base EM COR a
|
|
38
|
+
* partir do qual o multi-tela recolore, por viewport.
|
|
39
|
+
* · `ctrl`/`pad` entram só porque `held` precisa deles. `ControlledPlayer`: a animação roda em partida.
|
|
40
|
+
*
|
|
41
|
+
* `clingN` era `string | null` aqui e `ClingSide | null` na física — a mesma face da ventosa, uma tipada e
|
|
42
|
+
* a outra não. Derivar fecha isso sem discussão.
|
|
43
|
+
*/
|
|
44
|
+
export type AnimPlayer = Pick<ControlledPlayer, 'vx' | 'vy' | 'onGround' | 'onLadder' | 'inWater' | 'clinging' | 'clingN' | 'flying' | 'airTime' | 'runCane' | 'rmWalk' | 'rmBreath' | 'rmFlavor' | 'anim' | 'walkAnim' | 'climbFrame' | 'idleNow' | 'idleTime' | 'flavor' | 'flavorT' | 'walking' | 'running' | '_tx' | 'ctrl' | 'pad'>;
|
|
45
|
+
/** Tudo que a escolha do quadro precisa e que não mora no jogador. */
|
|
46
|
+
export interface PlayerAnimEnv {
|
|
47
|
+
dt: number;
|
|
48
|
+
dir: number;
|
|
49
|
+
wheelchair: boolean;
|
|
50
|
+
held(pl: AnimPlayer, act: string): boolean;
|
|
51
|
+
rnd(): number;
|
|
52
|
+
tex: PlayerTextures;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Coyote-time da ANIMAÇÃO (E16), em quadros. Existe para matar o flicker walk↔jump no pouso: `onGround`
|
|
56
|
+
* pisca por 1 quadro ao repousar, e sem essa folga o personagem alternava entre andar e pular ao aterrissar.
|
|
57
|
+
* É o coyote do RENDER e é independente do coyote da física (game/physics) — mudar um não muda o outro.
|
|
58
|
+
*/
|
|
59
|
+
export declare const COYOTE = 5;
|
|
60
|
+
/**
|
|
61
|
+
* Escolhe o quadro do jogador para ESTE tick e adianta os relógios de animação.
|
|
62
|
+
*
|
|
63
|
+
* Cadeia de prioridade E17, do mais específico ao mais genérico — a ORDEM é a regra, não um detalhe:
|
|
64
|
+
* ventosa → escada → água → voo → aéreo(pulo) → andando → idle
|
|
65
|
+
* Estar na água DENTRO de uma escada mostra escada; grudado na parede dentro d'água mostra ventosa.
|
|
66
|
+
*
|
|
67
|
+
* Também escreve em `pl` (ver AnimPlayer): `walking`/`running` (bengala), `idleNow`, os relógios e `_tx`.
|
|
68
|
+
*
|
|
69
|
+
* @returns o quadro escolhido (o mesmo valor que fica em `pl._tx`).
|
|
70
|
+
*/
|
|
71
|
+
export declare function choosePlayerFrame(pl: AnimPlayer, env: PlayerAnimEnv): Frame;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// render/player-anim.ts — A ESCOLHA DO QUADRO do personagem: dado o estado do jogador, QUAL textura desenhar.
|
|
3
|
+
// Extraída verbatim da segunda metade do `stepPlayer` do game.js (o trecho E15/E16/E17/E19/E20, de
|
|
4
|
+
// `const COYOTE=5` até `pl._tx=tx`). Ela morava dentro da função de física por acidente histórico — mas não é
|
|
5
|
+
// física: não move ninguém, não colide, não lê o mundo. É uma DECISÃO PURA a partir do estado já simulado,
|
|
6
|
+
// e é por isso que ela é render e mora aqui, ao lado de render/sprites (quem cria as texturas) e de
|
|
7
|
+
// render/viewports (quem as recolore por modo de visão).
|
|
8
|
+
//
|
|
9
|
+
// POR QUE A FRONTEIRA CAIU EXATAMENTE AQUI
|
|
10
|
+
// O `stepPlayer` do monólito fazia quatro coisas em sequência: (1) física, (2) coleta de moeda/quiz,
|
|
11
|
+
// (3) power-ups + chave/portão, (4) animação. A (1) já saiu para game/physics.ts (B1), que devolve
|
|
12
|
+
// `{ran, dir}` — e `dir` existe justamente porque a animação lê a DIREÇÃO DO INPUT, não `vx` (a colisão zera
|
|
13
|
+
// `vx` por quadros inteiros e isso reiniciava o ciclo de passos; só apareciam 2 dos 8 quadros). As (2) e (3)
|
|
14
|
+
// são LÓGICA DE JOGO — dependem de `coins`, `powerups`, `gate`, do quiz e do HUD — e continuam no game.js;
|
|
15
|
+
// não vieram junto de propósito, porque arrastá-las para cá traria de volta metade do monólito.
|
|
16
|
+
// O corte começa em `const COYOTE=5` e termina em `pl._tx=tx`. A ÚLTIMA linha do original,
|
|
17
|
+
// `if(pl.sprite) pl.sprite.texture=playerVizTex(tx, pl.viz)`, NÃO está aqui: ela toca um sprite do PIXI e o
|
|
18
|
+
// recolor por modo de visão. Ela vive em render/draw.ts (`animatePlayer`), que é quem já lida com PIXI.
|
|
19
|
+
// Assim este arquivo fica sem PIXI, sem DOM e sem estado de módulo — testável quadro a quadro no project
|
|
20
|
+
// `node` do Vitest.
|
|
21
|
+
//
|
|
22
|
+
// O QUE É "PURO" AQUI (e o que não é)
|
|
23
|
+
// A função é DETERMINÍSTICA dado `(pl, env)` e não faz I/O nenhum. Ela ESCREVE em `pl`, e isso é contrato,
|
|
24
|
+
// não efeito colateral escondido: os relógios de animação (`anim`, `walkAnim`, `idleTime`, `flavorT`),
|
|
25
|
+
// o quadro congelado da ventosa (`climbFrame`), o estado da gracinha (`flavor`) e as flags que o RESTO do
|
|
26
|
+
// jogo lê (`walking`/`running`, que a bengala do modo cego consome em draw; `idleNow`; `_tx`, o quadro-base
|
|
27
|
+
// em cor que o multi-tela recolore por viewport) moram no objeto do jogador desde o v3. Mover esses campos
|
|
28
|
+
// para dentro do módulo criaria um segundo lugar de verdade sobre o jogador — o oposto do que a
|
|
29
|
+
// modularização quer. O teste, por isso, sonda `pl` depois da chamada tanto quanto o valor de retorno.
|
|
30
|
+
//
|
|
31
|
+
// INJEÇÃO: `held` E `rnd` ENTRAM PELO `env`, NÃO POR IMPORT — e isso é um DESVIO CONSCIENTE do precedente de
|
|
32
|
+
// game/physics.ts, que importa os dois. Lá a âncora de regressão são as 14 trajetórias-ouro, que exigem o
|
|
33
|
+
// singleton de input e o RNG semeado reais; aqui não há trajetória-ouro nenhuma, e o valor da extração é
|
|
34
|
+
// poder atravessar a cadeia de prioridade ramo a ramo. Com `held` injetado, um teste liga "Correr" sem
|
|
35
|
+
// montar `keys`+`ctrl`+`padCur`; com `rnd` injetado, a gracinha sorteada é ESCOLHIDA em vez de descoberta.
|
|
36
|
+
// O game.js passa exatamente os mesmos `held` (input/state) e `rnd` (core/rng), então a ordem de consumo da
|
|
37
|
+
// semente — de que o attract-mode/replay depende — fica idêntica à do monólito.
|
|
38
|
+
// As texturas também entram pelo `env` (`env.tex`): os `TEX_*` de render/sprites são bindings VIVOS que só
|
|
39
|
+
// `initCharacterSprites()` preenche, e importá-los aqui amarraria o módulo ao boot do PIXI justamente na
|
|
40
|
+
// parte que queremos testar sem ele. `ANIM` (as cadências) é constante pura → import normal.
|
|
41
|
+
//
|
|
42
|
+
// ARMADILHA DE ORDEM DE BOOT: nenhuma. O módulo não tem estado próprio nem toca nada no import.
|
|
43
|
+
import { ANIM } from '../core/constants.js';
|
|
44
|
+
/**
|
|
45
|
+
* Coyote-time da ANIMAÇÃO (E16), em quadros. Existe para matar o flicker walk↔jump no pouso: `onGround`
|
|
46
|
+
* pisca por 1 quadro ao repousar, e sem essa folga o personagem alternava entre andar e pular ao aterrissar.
|
|
47
|
+
* É o coyote do RENDER e é independente do coyote da física (game/physics) — mudar um não muda o outro.
|
|
48
|
+
*/
|
|
49
|
+
export const COYOTE = 5;
|
|
50
|
+
/**
|
|
51
|
+
* Escolhe o quadro do jogador para ESTE tick e adianta os relógios de animação.
|
|
52
|
+
*
|
|
53
|
+
* Cadeia de prioridade E17, do mais específico ao mais genérico — a ORDEM é a regra, não um detalhe:
|
|
54
|
+
* ventosa → escada → água → voo → aéreo(pulo) → andando → idle
|
|
55
|
+
* Estar na água DENTRO de uma escada mostra escada; grudado na parede dentro d'água mostra ventosa.
|
|
56
|
+
*
|
|
57
|
+
* Também escreve em `pl` (ver AnimPlayer): `walking`/`running` (bengala), `idleNow`, os relógios e `_tx`.
|
|
58
|
+
*
|
|
59
|
+
* @returns o quadro escolhido (o mesmo valor que fica em `pl._tx`).
|
|
60
|
+
*/
|
|
61
|
+
export function choosePlayerFrame(pl, env) {
|
|
62
|
+
const { dt, dir, tex, wheelchair } = env;
|
|
63
|
+
const held = env.held, rnd = env.rnd;
|
|
64
|
+
// E16: estado aéreo ESTÁVEL — subindo (vy<0) entra na hora; cair/sair de borda só após o coyote-time.
|
|
65
|
+
const grounded = pl.airTime <= COYOTE;
|
|
66
|
+
const airborne = !pl.clinging && ((pl.vy < 0 && !pl.onGround) || !grounded);
|
|
67
|
+
// 'moving' baseado no INPUT (direção segurada), NÃO em vx — a colisão zera vx por quadros e isso resetava
|
|
68
|
+
// o ciclo (só apareciam 2 dos 8 quadros). Assim os 8 quadros tocam contínuos.
|
|
69
|
+
const moving = (dir !== 0) && grounded && !pl.clinging;
|
|
70
|
+
pl.walking = moving && !pl.inWater && !pl.onLadder && !pl.flying; // p/ a bengala: só aparece andando
|
|
71
|
+
pl.running = pl.walking && held(pl, 'action1') && !!pl.runCane; // correndo: só com a bengala de corrida
|
|
72
|
+
pl.anim += dt; // idle (clock contínuo)
|
|
73
|
+
pl.walkAnim += dt; // clock do passo NUNCA reseta → ciclo de 8 sem reinício
|
|
74
|
+
const II = tex.idle;
|
|
75
|
+
const wcFreeze = wheelchair || !!pl.rmWalk; // cadeirante: pernas paradas — mesma via do movimento reduzido
|
|
76
|
+
let tx;
|
|
77
|
+
pl.idleNow = false;
|
|
78
|
+
if (pl.clinging) {
|
|
79
|
+
const ceil = (pl.clingN === 'U'); // E18f: teto e parede usam ciclos distintos
|
|
80
|
+
const CL = ceil ? tex.clingCeil : tex.clingWall;
|
|
81
|
+
if (!pl.rmWalk && (pl.vx !== 0 || pl.vy !== 0))
|
|
82
|
+
pl.climbFrame = (Math.floor(pl.walkAnim / ANIM.clingHold)) % CL.length; // só avança ao mover; parado MANTÉM o quadro
|
|
83
|
+
tx = CL[(pl.rmWalk ? 0 : (pl.climbFrame || 0)) % CL.length];
|
|
84
|
+
}
|
|
85
|
+
else if (pl.onLadder) {
|
|
86
|
+
const CB = tex.climb;
|
|
87
|
+
if (wheelchair) {
|
|
88
|
+
tx = II[0];
|
|
89
|
+
} // ELEVADOR cadeirante: pose PARADA (idle), não de escada
|
|
90
|
+
else {
|
|
91
|
+
const climbing = (pl.vy !== 0) && !pl.rmWalk;
|
|
92
|
+
tx = climbing ? CB[Math.floor(pl.walkAnim / ANIM.climbHold) % CB.length] : CB[0];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (pl.inWater) {
|
|
96
|
+
const stroking = ((dir !== 0) || held(pl, 'action2')) && !wcFreeze; // movendo = braçada; parado/congelado = pernas paradas
|
|
97
|
+
const SW = stroking ? tex.swim : tex.swimIdle;
|
|
98
|
+
tx = wcFreeze ? SW[0] : SW[Math.floor(pl.walkAnim / ANIM.swimHold) % SW.length];
|
|
99
|
+
}
|
|
100
|
+
else if (pl.flying) {
|
|
101
|
+
tx = tex.fly;
|
|
102
|
+
}
|
|
103
|
+
else if (airborne) {
|
|
104
|
+
if (wcFreeze)
|
|
105
|
+
tx = wheelchair ? II[0] : tex.jumpUp; // cadeirante caindo = pose neutra sentado; congelado = pulo num quadro
|
|
106
|
+
else
|
|
107
|
+
tx = pl.vy < 0 ? tex.jumpUp : tex.jumpDown; // subindo: pernas recolhidas / caindo: estendidas
|
|
108
|
+
}
|
|
109
|
+
else if (moving) {
|
|
110
|
+
if (wcFreeze) {
|
|
111
|
+
tx = II[0];
|
|
112
|
+
} // cadeirante/movimento reduzido: anda sem ciclo de passos
|
|
113
|
+
else {
|
|
114
|
+
const running = held(pl, 'action1'); // E19: correr ≠ andar — passada/cadência distintas
|
|
115
|
+
const M = running ? tex.run : tex.walk;
|
|
116
|
+
const hold = running ? ANIM.runHold : ANIM.walkHold;
|
|
117
|
+
tx = M[Math.floor(pl.walkAnim / hold) % M.length];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
pl.idleNow = true;
|
|
122
|
+
pl.idleTime += dt; // E20: parado → respira; após flavorDelay, uma gracinha
|
|
123
|
+
if (!pl.rmFlavor) { // gracinhas (toggle próprio — há quem se incomode)
|
|
124
|
+
if (pl.flavor < 0 && pl.idleTime > ANIM.flavorDelay) {
|
|
125
|
+
pl.flavor = Math.floor(rnd() * tex.flavors.length);
|
|
126
|
+
pl.flavorT = 0;
|
|
127
|
+
}
|
|
128
|
+
if (pl.flavor >= 0) {
|
|
129
|
+
const F = tex.flavors[pl.flavor];
|
|
130
|
+
const step = Math.floor(pl.flavorT / F.hold);
|
|
131
|
+
pl.flavorT += dt;
|
|
132
|
+
if (step >= F.seq.length) {
|
|
133
|
+
pl.flavor = -1;
|
|
134
|
+
pl.idleTime = 0;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
tx = F.tex[F.seq[step]];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
pl.flavor = -1;
|
|
143
|
+
if (pl.flavor < 0)
|
|
144
|
+
tx = pl.rmBreath ? II[0] : II[Math.floor(pl.anim / ANIM.idleHold) % II.length]; // respiração: congela ou cicla
|
|
145
|
+
}
|
|
146
|
+
if (!pl.idleNow) {
|
|
147
|
+
pl.idleTime = 0;
|
|
148
|
+
pl.flavor = -1;
|
|
149
|
+
} // saiu do idle → zera gracinha
|
|
150
|
+
pl._tx = tx; // quadro base (cor) p/ recolor por viewport
|
|
151
|
+
return tx;
|
|
152
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** O MÍNIMO de uma camada de desenho vetorial: limpar, preencher, retangular. */
|
|
3
|
+
export interface Desenho {
|
|
4
|
+
clear(): this;
|
|
5
|
+
beginFill(color: number, alpha?: number): this;
|
|
6
|
+
drawRect(x: number, y: number, w: number, h: number): this;
|
|
7
|
+
endFill(): this;
|
|
8
|
+
}
|
|
9
|
+
/** Desenho que também traça linha — chuva, cabos, tracinhos de lava. */
|
|
10
|
+
export interface DesenhoComLinha extends Desenho {
|
|
11
|
+
lineStyle(width: number, color?: number, alpha?: number): this;
|
|
12
|
+
moveTo(x: number, y: number): this;
|
|
13
|
+
lineTo(x: number, y: number): this;
|
|
14
|
+
}
|
|
15
|
+
/** O que se pode esconder. Separado de `Desenho` porque nem toda camada é escondida por quem a desenha. */
|
|
16
|
+
export interface Visivel {
|
|
17
|
+
visible: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Um contêiner do grafo de cena, do ponto de vista de quem só adiciona e remove filhos. */
|
|
20
|
+
export interface Camada {
|
|
21
|
+
addChild(c: unknown): unknown;
|
|
22
|
+
removeChild(c: unknown): unknown;
|
|
23
|
+
}
|
|
24
|
+
/** O que tem textura trocável — o recolor do alto contraste escreve aqui. */
|
|
25
|
+
export interface ComTextura {
|
|
26
|
+
texture: unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* DESENHAR UM OBJETO DENTRO DE UMA TEXTURA — a captura de tela de cada jogador no multi-tela.
|
|
30
|
+
*
|
|
31
|
+
* É uma FUNÇÃO e não um objeto `{ render(...) }`, e a diferença é o que faz a porta funcionar. Dois módulos
|
|
32
|
+
* declaravam `interface RendererLike { render(displayObject: unknown, options: { renderTexture: unknown }) }`
|
|
33
|
+
* — idênticos, e ambos recusados pelo PixiJS real: o `render` dele pede `IRenderableObject`, e `unknown` não
|
|
34
|
+
* é atribuível a isso. Por contravariância, quem DECLARA o parâmetro mais largo é quem não cabe.
|
|
35
|
+
*
|
|
36
|
+
* Pedir a CAPACIDADE resolve: o módulo diz o que quer que aconteça, e a raiz de composição — que é o único
|
|
37
|
+
* lugar onde o PixiJS já é conhecido — entrega a função. É o adaptador que o ADR-0035 prometeu, e a promessa
|
|
38
|
+
* só vira fato quando o pedido tem a forma de um verbo, não a de um objeto emprestado.
|
|
39
|
+
*
|
|
40
|
+
* ⚠️ `limpar` É OBRIGATÓRIO, e era opcional. Desenhar numa textura de render sem dizer se ela deve ser limpa
|
|
41
|
+
* antes é a diferença entre um quadro e um BORRÃO: sem limpar, cada quadro se acumula sobre o anterior e o
|
|
42
|
+
* personagem aparece várias vezes, em várias posições. Havia dois chamadores — um passava `false` de
|
|
43
|
+
* propósito (a passada extra de baixa visão, que compõe POR CIMA) e o outro não passava nada, deixando a
|
|
44
|
+
* decisão para um padrão que ninguém escreveu.
|
|
45
|
+
*
|
|
46
|
+
* Um padrão implícito aqui é caro de duas formas: ele muda com a versão do renderizador, e o sintoma dele é
|
|
47
|
+
* visual — nenhum teste de lógica o vê. Tornar o parâmetro obrigatório faz o compilador cobrar a intenção de
|
|
48
|
+
* cada chamador, uma vez, e para sempre.
|
|
49
|
+
*/
|
|
50
|
+
export type RenderizarEm = (objeto: unknown, alvo: unknown, limpar: boolean) => void;
|
|
51
|
+
/**
|
|
52
|
+
* CRIAR UM SPRITE a partir de uma textura, e um AZULEJO a partir dela.
|
|
53
|
+
*
|
|
54
|
+
* Fábricas, não construtores — e pelo mesmo motivo do `RenderizarEm` acima. Três módulos pediam
|
|
55
|
+
* `interface SpriteCtor { new (tex: unknown): Sprite }`, e o `PIXI.Sprite` real não cabe: o construtor dele
|
|
56
|
+
* aceita `Texture | undefined`, e um parâmetro declarado `unknown` é MAIS LARGO — por contravariância, quem
|
|
57
|
+
* promete aceitar qualquer coisa é quem não pode receber um construtor que só aceita textura.
|
|
58
|
+
*
|
|
59
|
+
* Uma função apaga o problema: quem chama passa a textura que já tem, e quem compõe fecha a diferença uma
|
|
60
|
+
* vez. `T` é o que o módulo espera de volta — cada um sabe qual fatia do sprite ele vai tocar.
|
|
61
|
+
*/
|
|
62
|
+
export type CriarSprite<T> = (textura: unknown) => T;
|
|
63
|
+
/** Idem para o azulejo do parallax, que também recebe largura e altura. */
|
|
64
|
+
export type CriarAzulejo<T> = (textura: unknown, largura: number, altura: number) => T;
|
|
65
|
+
/** E o desenho vetorial vazio (`new PIXI.Graphics()`), pela mesma razão. */
|
|
66
|
+
export type CriarDesenho<T> = () => T;
|
|
67
|
+
/**
|
|
68
|
+
* O QUE SE TINGE — um sprite do ponto de vista de quem só ESCREVE a cor nele.
|
|
69
|
+
*
|
|
70
|
+
* `tint: unknown`, e o `unknown` aqui é a coisa certa, não preguiça. Dois módulos declaravam
|
|
71
|
+
* `tint: number` — e o `PIXI.Sprite` real NÃO CABE nisso: o `tint` dele é `ColorSource`, que aceita
|
|
72
|
+
* número, texto (`'red'`), array e mais. `number` é um SUBTIPO estrito disso, e um módulo que não é
|
|
73
|
+
* dono do campo declarando algo MAIS ESTREITO que a verdade é exatamente o que o ADR-0039 proíbe:
|
|
74
|
+
* estreitar é o que quebra a atribuição.
|
|
75
|
+
*
|
|
76
|
+
* O supertipo verdadeiro que dá para provar sem importar PixiJS é `unknown`. O preço é conhecido e
|
|
77
|
+
* pequeno: quem escreve aqui não é mais conferido pelo compilador — mas as duas únicas escritas da
|
|
78
|
+
* árvore são constantes hexadecimais literais, e o `ColorSource` aceitaria todas elas de qualquer jeito.
|
|
79
|
+
*/
|
|
80
|
+
export interface Tingivel {
|
|
81
|
+
tint: unknown;
|
|
82
|
+
}
|
|
83
|
+
/** O que se descarta. `DisplayObject.destroy(options?)` do PixiJS satisfaz — o opcional não atrapalha. */
|
|
84
|
+
export interface Descartavel {
|
|
85
|
+
destroy(): void;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Camada que também ESVAZIA, devolvendo o que saiu para quem precisa destruir os filhos removidos.
|
|
89
|
+
*
|
|
90
|
+
* Separada de `Camada` porque só um consumidor esvazia, e porque o retorno é a parte delicada: quem
|
|
91
|
+
* declarava `removeChildren(): CoinSprite[]` pedia de volta algo mais ESPECÍFICO do que o PixiJS
|
|
92
|
+
* entrega (`DisplayObject[]`) — e retorno é covariante, então o pedido específico é o que não cabe.
|
|
93
|
+
* `Descartavel` é o que o chamador de fato usa.
|
|
94
|
+
*/
|
|
95
|
+
export interface CamadaEsvaziavel extends Camada {
|
|
96
|
+
removeChildren(): Descartavel[];
|
|
97
|
+
}
|
|
98
|
+
/** O que tem filtro de GPU — a câmera e cada sprite de saída do multi-tela. */
|
|
99
|
+
export interface ComFiltro {
|
|
100
|
+
filters: unknown;
|
|
101
|
+
}
|
|
102
|
+
/** Desenho que também traça CÍRCULO — a bolinha indicadora de cada viewport. */
|
|
103
|
+
export interface DesenhoComCirculo extends DesenhoComLinha {
|
|
104
|
+
drawCircle(x: number, y: number, r: number): this;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* APLICAR UM FILTRO CSS NO SOLO — o filtro global da tela, que compõe daltonismo, baixa visão,
|
|
108
|
+
* cegueira e o realce de luminância/quantização.
|
|
109
|
+
*
|
|
110
|
+
* Era `app: AppLike | null` com `AppLike { view?: { style: { filter: string } } }` — o módulo alcançava
|
|
111
|
+
* TRÊS níveis para dentro de um objeto que não é dele, e o `PIXI.Application` real não cabia: o `style`
|
|
112
|
+
* do `ICanvas` do PixiJS é `ICanvasStyle`, que sequer TEM `filter` (ele existe para a `OffscreenCanvas`,
|
|
113
|
+
* onde não há CSS). Em produção o `view` é um `HTMLCanvasElement` de verdade e o campo existe — mas isso
|
|
114
|
+
* é uma coisa que só a raiz de composição sabe, e é lá que a conversão pertence.
|
|
115
|
+
*
|
|
116
|
+
* Mesma lição do `RenderizarEm` e do `CriarSprite`: pedir o VERBO cabe onde emprestar o objeto não cabe.
|
|
117
|
+
*/
|
|
118
|
+
export type AplicarFiltroCss = (css: string, alcance: AlcanceDoFiltro) => void;
|
|
119
|
+
/**
|
|
120
|
+
* ONDE o filtro de acessibilidade cai — e a distinção é de PRODUTO, decidida pelo Dev em 2026-08-26.
|
|
121
|
+
*
|
|
122
|
+
* O quadro é metade canvas e metade DOM, e filtro de PIXI não alcança DOM. Até aqui o filtro caía SÓ na
|
|
123
|
+
* canvas, e os menus ficavam crus: a criança daltônica recebia o jogo corrigido e as palavras não (issue
|
|
124
|
+
* #82). Mas a correção não é o único modo, e nem todos devem alcançar o menu:
|
|
125
|
+
*
|
|
126
|
+
* · MELHORIA (`normal`, `hc-direto*`, `fix-*`) — existe para a criança ENXERGAR MELHOR. Tem de alcançar
|
|
127
|
+
* tudo que ela lê, menus inclusive. É o caso que estava quebrado.
|
|
128
|
+
* · EMPATIA (`sim-*`, `lv-*`, `blind`) — existe para um adulto SENTIR como é. Fica no mundo. O menu é o
|
|
129
|
+
* instrumento de SAIR da simulação, e uma cegueira que apagasse o menu de pausa trancaria a criança
|
|
130
|
+
* dentro dela.
|
|
131
|
+
*
|
|
132
|
+
* O catálogo já sabia disto antes de a regra ser escrita: `VIZ_MODES` traz `sim: true` exatamente nos nove
|
|
133
|
+
* modos de empatia, e `simulatesDisability(chave)` é a pergunta pronta. Nada de taxonomia nova.
|
|
134
|
+
*/
|
|
135
|
+
export type AlcanceDoFiltro = 'mundo' | 'mundo-e-menus';
|
|
136
|
+
/**
|
|
137
|
+
* LIGAR/DESLIGAR O ALTO CONTRASTE NO DOM — a metade que o filtro não alcança.
|
|
138
|
+
*
|
|
139
|
+
* O alto contraste não é filtro de CSS: é Renderização Direta, e repinta as TEXTURAS da canvas. O DOM não
|
|
140
|
+
* tem textura, então não há o que propagar — há que escrever o equivalente, e ele mora no `style.css` sob
|
|
141
|
+
* `#dom-layer.hc`. Aqui só se diz SE está ligado; o desenho é do CSS, com as razões medidas (issue #83).
|
|
142
|
+
*/
|
|
143
|
+
export type AplicarAltoContrasteNoDom = (ligado: boolean) => void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// render/port — A PORTA DO RENDERIZADOR (Fase D do plano, ADR-0035).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE ESTE ARQUIVO RESOLVE =========================
|
|
5
|
+
// O ADR-0035 apostou a reversibilidade da escolha do renderizador num adaptador: os módulos não importam
|
|
6
|
+
// PixiJS, recebem por injeção o que vão desenhar. A aposta foi cumprida pela metade — os módulos de fato não
|
|
7
|
+
// importam PixiJS, mas cada um descreveu a forma do que recebe POR CONTA PRÓPRIA, e as descrições
|
|
8
|
+
// divergiram.
|
|
9
|
+
//
|
|
10
|
+
// `Gfx` estava escrito CINCO vezes, em `render/scene-city`, `render/scene-sky`, `render/title-scene`,
|
|
11
|
+
// `render/weather` e `game/traffic`, com quatro definições diferentes: umas com `clear(): void`, outras com
|
|
12
|
+
// `clear(): Gfx`; uma com `visible`, outra com `lineStyle`/`moveTo`/`lineTo`. `Layer` e `SpriteCtor`, duas
|
|
13
|
+
// vezes cada. `RendererLike`, `ContainerLike` e `GraphicsLike`, duas cada.
|
|
14
|
+
//
|
|
15
|
+
// É o mesmo defeito do `DomQuery` (dezesseis cópias) e do `KeyScheme` (seis), agora no renderizador: uma
|
|
16
|
+
// verdade escrita em muitos lugares diverge, e a divergência aparece na raiz de composição — que é o único
|
|
17
|
+
// ponto onde o PixiJS de verdade encontra todas elas ao mesmo tempo.
|
|
18
|
+
//
|
|
19
|
+
// ========================= O QUE A PORTA NÃO É =========================
|
|
20
|
+
// NÃO é a implementação de `Scene`/`Handle` que a D14 do `demos` especifica. O plano é explícito: "não fazer
|
|
21
|
+
// agora a implementação completa de `Scene`; só a porta, e só onde os módulos já estão". Isto aqui descreve
|
|
22
|
+
// o que o renderizador ATUAL oferece, numa declaração só, de um jeito que o PixiJS satisfaz sem adaptador.
|
|
23
|
+
// A `Scene` nasce depois, e nasce podendo reusar estes nomes.
|
|
24
|
+
//
|
|
25
|
+
// ========================= POR QUE OS RETORNOS ENCADEIAM =========================
|
|
26
|
+
// `beginFill(…): this` e não `: void`. O PixiJS encadeia (`g.beginFill(c).drawRect(…).endFill()`), e três
|
|
27
|
+
// dos cinco módulos já dependiam disso. `this` em vez do nome do tipo é o que permite uma interface derivada
|
|
28
|
+
// continuar encadeando sem redeclarar cada método — foi o que fez as cinco cópias divergirem.
|
|
29
|
+
export {};
|