@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,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// core/scenes — A PILHA DE CENAS (item 22, opção C3 do ADR-0030). Módulo-FOLHA: zero dependências, zero I/O.
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE ELA SUBSTITUI, E POR QUE NÃO É UM ENUM MAIOR =========================
|
|
5
|
+
// Hoje o jogo tem `phase: 'title' | 'playing' | 'paused'` em `core/state`, lido por doze módulos. O ADR-0030
|
|
6
|
+
// registra C1 — alargar essa união — como NÃO-OPÇÃO, e a razão é curta: um segundo jogo continuaria amarrado
|
|
7
|
+
// ao NOSSO enum. Um jogo com tela de mapa, um com tela de resultados, um com senha entre sessões (item 23)
|
|
8
|
+
// precisariam pedir uma constante nova na engine para existir.
|
|
9
|
+
//
|
|
10
|
+
// Uma PILHA não tem esse problema porque não tem vocabulário: ela sabe que há cenas empilhadas e nada sobre o
|
|
11
|
+
// que cada uma significa. `title/playing/paused` vira `[titulo]`, `[jogo]`, `[jogo, pausa]` — e um mapa de
|
|
12
|
+
// fases vira `[mapa]` sem que este arquivo mude uma linha.
|
|
13
|
+
//
|
|
14
|
+
// ========================= AS TRÊS REGRAS, E O QUE CADA UMA PRESERVA =========================
|
|
15
|
+
// Elas não são convenção de biblioteca: cada uma reproduz um comportamento que o jogo já tem.
|
|
16
|
+
//
|
|
17
|
+
// · `update` SÓ NO TOPO. É o congelamento da pausa. Hoje ele é `if (phase !== 'playing') return` espalhado;
|
|
18
|
+
// aqui é estrutural — uma cena que não está no topo não recebe tempo, ponto.
|
|
19
|
+
// · `draw` DE BAIXO PARA CIMA. É o menu de pausa desenhado SOBRE o mundo, com o mundo ainda visível. Se
|
|
20
|
+
// desenhasse só o topo, pausar apagaria o jogo da tela.
|
|
21
|
+
// · `input` SÓ NO TOPO, e ele DIZ se consumiu. É a mesma forma que o ADR-0033 deu à entrada modal: quem
|
|
22
|
+
// está por cima decide, e o que ele não quer volta a descer. Sem o retorno, a pilha teria de adivinhar.
|
|
23
|
+
//
|
|
24
|
+
// ========================= O QUE ELA NÃO FAZ =========================
|
|
25
|
+
// Não desenha, não escuta evento, não conhece PIXI nem DOM. Recebe `dt` e devolve nada; recebe uma intenção e
|
|
26
|
+
// devolve um booleano. É o que a torna testável sem navegador e importável dos dois lados da fronteira.
|
|
27
|
+
/**
|
|
28
|
+
* Cria uma pilha vazia.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ `enter`/`exit` são chamados em TRY/CATCH? NÃO — e é decisão, não esquecimento. Um erro dentro de
|
|
31
|
+
* `enter()` significa que a cena não montou; engolir isso deixaria a pilha num estado que ninguém declarou, e
|
|
32
|
+
* o sintoma apareceria três quadros depois, longe da causa. As chamadas de `update`/`draw`/`input` também
|
|
33
|
+
* propagam: a engine não é o lugar de decidir que o erro de um jogo não importa.
|
|
34
|
+
*/
|
|
35
|
+
export function criarPilha() {
|
|
36
|
+
const pilha = [];
|
|
37
|
+
return {
|
|
38
|
+
push(s) {
|
|
39
|
+
pilha[pilha.length - 1]?.exit?.();
|
|
40
|
+
pilha.push(s);
|
|
41
|
+
s.enter?.();
|
|
42
|
+
},
|
|
43
|
+
pop() {
|
|
44
|
+
const fora = pilha.pop() ?? null;
|
|
45
|
+
fora?.exit?.();
|
|
46
|
+
pilha[pilha.length - 1]?.enter?.();
|
|
47
|
+
return fora;
|
|
48
|
+
},
|
|
49
|
+
replace(s) {
|
|
50
|
+
// NÃO é `this.pop()` seguido de `this.push(s)`: isso daria `enter()` à cena de baixo por um instante,
|
|
51
|
+
// e ela reapareceria no topo entre as duas chamadas. Trocar é UMA transição, não duas.
|
|
52
|
+
const fora = pilha.pop() ?? null;
|
|
53
|
+
fora?.exit?.();
|
|
54
|
+
pilha.push(s);
|
|
55
|
+
s.enter?.();
|
|
56
|
+
},
|
|
57
|
+
top() { return pilha[pilha.length - 1] ?? null; },
|
|
58
|
+
nomes() { return pilha.map((s) => s.nome); },
|
|
59
|
+
update(dt) { pilha[pilha.length - 1]?.update?.(dt); },
|
|
60
|
+
draw() { for (const s of pilha)
|
|
61
|
+
s.draw?.(); },
|
|
62
|
+
input(intent) { return pilha[pilha.length - 1]?.input?.(intent) === true; },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** Colunas e linhas da grade para `n` telas. */
|
|
3
|
+
export interface ScreenGrid {
|
|
4
|
+
cols: number;
|
|
5
|
+
rows: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A grade para `n` jogadores: 1 -> 1x1 · 2 -> 2x1 · 3 e 4 -> 2x2.
|
|
9
|
+
* `n` menor que 1 devolve a grade de uma tela, e não zero colunas.
|
|
10
|
+
*/
|
|
11
|
+
export declare function screenGrid(n: number): ScreenGrid;
|
|
12
|
+
/** Dimensões em pixels de arte da grade de `n` telas (320x180 por tela — o pixel canônico do ADR-0010). */
|
|
13
|
+
export declare function screenBaseSize(n: number): {
|
|
14
|
+
w: number;
|
|
15
|
+
h: number;
|
|
16
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// core/screens — a grade de telas do multiplayer, num só lugar.
|
|
3
|
+
//
|
|
4
|
+
// O jogo divide a janela em uma tela por jogador (pilar: multiplayer em telas SEPARADAS, sem split-screen —
|
|
5
|
+
// ADR-0010). Quantas colunas e quantas linhas isso dá era uma conta de uma linha copiada em CINCO lugares:
|
|
6
|
+
// ui/layout.ts (escala inteira), render/crt.ts (só as linhas, para o passo do scanline), ui/hud.ts (posição
|
|
7
|
+
// de cada `.player-screen`), e no game.js em configureRender() e fitsN().
|
|
8
|
+
//
|
|
9
|
+
// A quinta cópia estava DIVERGENTE: `cols = n<=2 ? n : 2`, sem a guarda de `n<=1`. Para n>=1 as duas contas
|
|
10
|
+
// concordam, então a divergência nunca chegou a aparecer — com n=0 uma devolve 1 e a outra devolve 0, e
|
|
11
|
+
// dividir por 0 colunas daria NaN em toda a geometria. Vale a pena guardar essa história: a cópia errada
|
|
12
|
+
// sobreviveu justamente porque o caso onde ela erra é inalcançável hoje, e não porque alguém a conferiu.
|
|
13
|
+
//
|
|
14
|
+
// Módulo FOLHA de propósito: zero dependências, nem de estado. `n` é parâmetro, nunca `numPlayers` global —
|
|
15
|
+
// é isso que deixa a conta testável e que permite o render, o layout e o HUD importarem daqui sem que um
|
|
16
|
+
// passe a depender do outro.
|
|
17
|
+
/**
|
|
18
|
+
* A grade para `n` jogadores: 1 -> 1x1 · 2 -> 2x1 · 3 e 4 -> 2x2.
|
|
19
|
+
* `n` menor que 1 devolve a grade de uma tela, e não zero colunas.
|
|
20
|
+
*/
|
|
21
|
+
export function screenGrid(n) {
|
|
22
|
+
return { cols: n <= 1 ? 1 : (n <= 2 ? n : 2), rows: n <= 2 ? 1 : 2 };
|
|
23
|
+
}
|
|
24
|
+
/** Dimensões em pixels de arte da grade de `n` telas (320x180 por tela — o pixel canônico do ADR-0010). */
|
|
25
|
+
export function screenBaseSize(n) {
|
|
26
|
+
const { cols, rows } = screenGrid(n);
|
|
27
|
+
return { w: 320 * cols, h: 180 * rows };
|
|
28
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* ========================= O BARRAMENTO, TIPADO (Fase C do plano) =========================
|
|
4
|
+
*
|
|
5
|
+
* Era `emit(evt: string, val: unknown)`. Duas coisas erradas numa assinatura só:
|
|
6
|
+
*
|
|
7
|
+
* · O NOME ERA `string`. Um `emit('viz', …)` em vez de `'vizMode'` não é erro em lugar nenhum — é
|
|
8
|
+
* SILÊNCIO. O assinante certo nunca é chamado, nada fica vermelho, e a única pista é um painel que
|
|
9
|
+
* parou de se atualizar. Hoje são dezessete pontos de emissão e cada nome aparece UMA vez; a chance de
|
|
10
|
+
* digitar errado é exatamente a chance de escrever a próxima linha.
|
|
11
|
+
* · A CARGA ERA `unknown`. Quem assinasse tinha de converter, e a conversão é onde a mentira entra.
|
|
12
|
+
*
|
|
13
|
+
* Agora `EventoDoJogo` é um mapa nome → carga, e `emit`/`on` são genéricos sobre ele. Nome inexistente não
|
|
14
|
+
* compila; carga errada não compila.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ POR QUE ISTO EXISTE ANTES DE TER ASSINANTE. Hoje o barramento tem ZERO `on()` em produção (só o teste
|
|
17
|
+
* assina). Um mecanismo sem uso normalmente é dívida — mas este precisa nascer tipado, não ser retipado
|
|
18
|
+
* depois: o ADR-0031 já exige que painéis e atividades se redesenhem quando a criança muda idioma ou fonte,
|
|
19
|
+
* e a casca do `demos` vai reagir a ajuste durante a partida. O primeiro assinante chega com o contrato
|
|
20
|
+
* pronto, em vez de chegar e ser seguido por uma migração.
|
|
21
|
+
*/
|
|
22
|
+
export interface EventoDoJogo {
|
|
23
|
+
numPlayers: number;
|
|
24
|
+
vizMode: string;
|
|
25
|
+
modoCego: boolean;
|
|
26
|
+
letterCase: LetterCase;
|
|
27
|
+
captionsOn: boolean;
|
|
28
|
+
menuIndexOn: boolean;
|
|
29
|
+
cbSafe: boolean;
|
|
30
|
+
ownerColors: boolean;
|
|
31
|
+
hcOutlineFg: OutlineLevel;
|
|
32
|
+
hcOutlineBg: OutlineLevel;
|
|
33
|
+
caneBlockDiv: number;
|
|
34
|
+
wheelchair: boolean;
|
|
35
|
+
oneButton: boolean;
|
|
36
|
+
}
|
|
37
|
+
type Ouvinte<K extends keyof EventoDoJogo> = (val: EventoDoJogo[K]) => void;
|
|
38
|
+
/** Assina `evt`. Devolve a função que cancela — guardar o retorno é mais barato que lembrar do `off`. */
|
|
39
|
+
export declare function on<K extends keyof EventoDoJogo>(evt: K, fn: Ouvinte<K>): () => void;
|
|
40
|
+
export declare function off<K extends keyof EventoDoJogo>(evt: K, fn: Ouvinte<K>): void;
|
|
41
|
+
/**
|
|
42
|
+
* Avisa os assinantes de `evt`. EXPORTADO desde 2026-08-25 (item 19) porque `game/state` emite pelos mesmos
|
|
43
|
+
* canais: um segundo mapa de assinantes seria um segundo barramento, e quem assinasse `coins` no lugar errado
|
|
44
|
+
* simplesmente não seria avisado — sem erro, sem teste vermelho.
|
|
45
|
+
*
|
|
46
|
+
* O `try` em volta de cada assinante NÃO é preguiça: um ouvinte que estoura não pode impedir os outros de
|
|
47
|
+
* receber. Um painel quebrado derruba o painel; não derruba o jogo.
|
|
48
|
+
*/
|
|
49
|
+
export declare function emit<K extends keyof EventoDoJogo>(evt: K, val: EventoDoJogo[K]): void;
|
|
50
|
+
export declare let vizMode: string;
|
|
51
|
+
export declare function initVizMode(mode: string): void;
|
|
52
|
+
export declare function setVizModeValue(mode: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* OS PADRÕES, com nome. Um valor por linha, e cada um usado em DOIS lugares: a leitura do boot (quando não há
|
|
55
|
+
* nada gravado) e o "restaurar padrões" do menu que o contém (ADR-0028).
|
|
56
|
+
*
|
|
57
|
+
* Existe porque a alternativa é escrever o mesmo padrão duas vezes — uma no `store.getBool(chave, X)` e outra
|
|
58
|
+
* no reset — e este repositório já mostrou quatro vezes o que acontece com duas cópias que ninguém obriga a
|
|
59
|
+
* concordar. Aqui a divergência seria pior que as anteriores: um reset que restaura um valor DIFERENTE do que
|
|
60
|
+
* o jogo usa quando nunca foi configurado deixa a criança num terceiro estado, que não é nem o dela nem o de
|
|
61
|
+
* fábrica, e que ela não tem como nomear para pedir ajuda.
|
|
62
|
+
*
|
|
63
|
+
* `as const` + `Object.freeze` de propósito: um padrão que alguém consiga escrever em tempo de execução deixa
|
|
64
|
+
* de ser padrão.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* O padrão do MOVIMENTO REDUZIDO não é uma constante — é o que o sistema operacional pede.
|
|
68
|
+
*
|
|
69
|
+
* Mora aqui, ao lado do DEFAULTS, porque a regra do ADR-0029 é que existe UMA fonte sobre o que é padrão, e
|
|
70
|
+
* um padrão calculado não deixa de ser padrão por não caber num objeto congelado. Quem lê isto: a carga do
|
|
71
|
+
* boot (main.js) e o "restaurar padrões" do menu de sensibilidade visual.
|
|
72
|
+
*
|
|
73
|
+
* Por que importa que o reset leia isto em vez de `false`: numa máquina cujo dono pediu menos movimento,
|
|
74
|
+
* devolver `false` RELIGARIA a animação — o reset passaria a fazer, sozinho, exatamente o que a WCAG 2.3.3
|
|
75
|
+
* existe para impedir, e faria isso na tela de quem já tinha dito que não aguenta.
|
|
76
|
+
*
|
|
77
|
+
* `matchMedia` é guardado: este módulo roda no projeto `node` dos testes, onde `window` não existe.
|
|
78
|
+
*/
|
|
79
|
+
export declare function defaultReducedMotion(): boolean;
|
|
80
|
+
export declare const DEFAULTS: Readonly<{
|
|
81
|
+
readonly modoCego: false;
|
|
82
|
+
readonly caneBlockDiv: 1;
|
|
83
|
+
readonly captionsOn: true;
|
|
84
|
+
readonly menuIndexOn: true;
|
|
85
|
+
readonly wheelchair: false;
|
|
86
|
+
readonly oneButton: false;
|
|
87
|
+
readonly easy: false;
|
|
88
|
+
readonly toggleMove: false;
|
|
89
|
+
readonly toggleRun: false;
|
|
90
|
+
readonly cbSafe: false;
|
|
91
|
+
readonly ownerColors: true;
|
|
92
|
+
readonly lq: 0;
|
|
93
|
+
readonly hcOutlineFg: 1;
|
|
94
|
+
readonly hcOutlineBg: 1;
|
|
95
|
+
readonly letterCase: "upper";
|
|
96
|
+
}>;
|
|
97
|
+
export declare let modoCego: boolean;
|
|
98
|
+
export declare function setModoCegoValue(on: boolean): void;
|
|
99
|
+
/**
|
|
100
|
+
* Um tile do portão. O portão é uma LISTA deles, não um objeto com posição — escrevi `{x, y}` na primeira
|
|
101
|
+
* versão e o navegador me desmentiu: `gate` é `[{tx:29,ty:36}, {tx:30,ty:36}, …]`.
|
|
102
|
+
*
|
|
103
|
+
* O `tsc` não podia pegar. Quem chama `setLevelExtras` é o `main.js`, que é JavaScript, então o tipo declarado
|
|
104
|
+
* aqui não tinha do outro lado nada que o contradissesse. É um argumento concreto para o `createGame()` do
|
|
105
|
+
* passo 4 nascer em TypeScript: enquanto o composition root for JS, todo contrato que só ele exercita é uma
|
|
106
|
+
* afirmação sem verificador.
|
|
107
|
+
*
|
|
108
|
+
* Mínimo ESTRUTURAL, como o `PlayerQuiz`: o `MapGateTile` de verdade mora em `game/level-geometry` e é
|
|
109
|
+
* atribuível a este. `core/` não importa de `game/`.
|
|
110
|
+
*/
|
|
111
|
+
export interface GateTile {
|
|
112
|
+
readonly tx: number;
|
|
113
|
+
readonly ty: number;
|
|
114
|
+
}
|
|
115
|
+
export type LetterCase = 'mixed' | 'upper';
|
|
116
|
+
export declare let letterCase: LetterCase;
|
|
117
|
+
export declare function setLetterCaseValue(c: LetterCase): void;
|
|
118
|
+
export declare let captionsOn: boolean;
|
|
119
|
+
export declare function setCaptionsOnValue(on: boolean): void;
|
|
120
|
+
export declare let menuIndexOn: boolean;
|
|
121
|
+
export declare function setMenuIndexOnValue(on: boolean): void;
|
|
122
|
+
export declare let cbSafe: boolean;
|
|
123
|
+
export declare function setCbSafeValue(on: boolean): void;
|
|
124
|
+
export declare let ownerColors: boolean;
|
|
125
|
+
export declare function setOwnerColorsValue(on: boolean): void;
|
|
126
|
+
/** Espessura de contorno: 0 nenhum · 1 fino · 2 grosso. Fora da faixa satura, não rejeita. */
|
|
127
|
+
export type OutlineLevel = 0 | 1 | 2;
|
|
128
|
+
export declare let hcOutlineFg: OutlineLevel;
|
|
129
|
+
export declare function setOutlineFgValue(v: number): void;
|
|
130
|
+
export declare let hcOutlineBg: OutlineLevel;
|
|
131
|
+
export declare function setOutlineBgValue(v: number): void;
|
|
132
|
+
export declare let caneBlockDiv: number;
|
|
133
|
+
export declare function setCaneBlockDivValue(div: number): void;
|
|
134
|
+
export declare let wheelchair: boolean;
|
|
135
|
+
export declare function setWheelchairValue(on: boolean): void;
|
|
136
|
+
export declare let oneButton: boolean;
|
|
137
|
+
export declare function setOneButtonValue(on: boolean): void;
|
|
138
|
+
export {};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// core/state.ts — estado/cena do jogo (FONTE ÚNICA). Módulo-folha. As 8 mega-variáveis migram do game.js
|
|
3
|
+
// UMA A UMA, lidas como binding vivo (import) e escritas por setter.
|
|
4
|
+
// Bus mínimo (Map<evento, Set<fn>>) para os poucos leitores "de longe" que virão com os outros subsistemas.
|
|
5
|
+
import * as store from '../platform/storage.js'; // persistência (as mega-vars com chave leem/gravam aqui)
|
|
6
|
+
const _subs = new Map();
|
|
7
|
+
/** Assina `evt`. Devolve a função que cancela — guardar o retorno é mais barato que lembrar do `off`. */
|
|
8
|
+
export function on(evt, fn) {
|
|
9
|
+
if (!_subs.has(evt))
|
|
10
|
+
_subs.set(evt, new Set());
|
|
11
|
+
_subs.get(evt).add(fn);
|
|
12
|
+
return () => off(evt, fn);
|
|
13
|
+
}
|
|
14
|
+
export function off(evt, fn) {
|
|
15
|
+
const s = _subs.get(evt);
|
|
16
|
+
if (s)
|
|
17
|
+
s.delete(fn);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Avisa os assinantes de `evt`. EXPORTADO desde 2026-08-25 (item 19) porque `game/state` emite pelos mesmos
|
|
21
|
+
* canais: um segundo mapa de assinantes seria um segundo barramento, e quem assinasse `coins` no lugar errado
|
|
22
|
+
* simplesmente não seria avisado — sem erro, sem teste vermelho.
|
|
23
|
+
*
|
|
24
|
+
* O `try` em volta de cada assinante NÃO é preguiça: um ouvinte que estoura não pode impedir os outros de
|
|
25
|
+
* receber. Um painel quebrado derruba o painel; não derruba o jogo.
|
|
26
|
+
*/
|
|
27
|
+
export function emit(evt, val) {
|
|
28
|
+
const s = _subs.get(evt);
|
|
29
|
+
if (s)
|
|
30
|
+
for (const fn of s) {
|
|
31
|
+
try {
|
|
32
|
+
fn(val);
|
|
33
|
+
}
|
|
34
|
+
catch (e) { /* noop */ }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// ========================= `phase` SAIU DAQUI (ADR-0030 C3, passo 3 da Fase B) =========================
|
|
38
|
+
// Ele não virou campo de fábrica como os outros doze de RODADA: virou uma PILHA. `core/scenes` já existia,
|
|
39
|
+
// testado e sem consumidor; agora a raiz de composição o usa, e `title`/`playing`/`paused` são
|
|
40
|
+
// `[titulo]`, `[jogo]` e `[jogo, pausa]`.
|
|
41
|
+
//
|
|
42
|
+
// A diferença que motivou a troca: `phase === 'paused'` APAGAVA a informação de que há um jogo por baixo. A
|
|
43
|
+
// pilha a mantém, e é dela que sai "o mundo continua desenhado, mas não recebe tempo".
|
|
44
|
+
//
|
|
45
|
+
// E o ADR-0030 registra ALARGAR a união (`'mapa' | 'resultado' | …`) como NÃO-OPÇÃO: um segundo jogo
|
|
46
|
+
// continuaria amarrado ao NOSSO vocabulário, e teria de pedir uma constante nova à engine para existir. Por
|
|
47
|
+
// isso os três nomes não moram em módulo nenhum da engine — moram na raiz, que é este jogo. Quem é engine
|
|
48
|
+
// recebe BOOLEANOS: `mundoRodando()`, `menuDePausa()`, `telaDeTitulo`. É a mesma correção que o
|
|
49
|
+
// `consumer-quiz` obrigou a fazer no `menu-nav` (`getPhase()` → `isNavigable()`), registrada em
|
|
50
|
+
// `core/constants` como o erro a não repetir.
|
|
51
|
+
/* (`quizLevel` SAIU daqui em 2026-08-25, item 19 — está em `game/state`. É o nível da atividade de
|
|
52
|
+
* alfabetização: conteúdo pedagógico, e nem sequer mecânica de engine. A regra é a do ADR-0033, aplicada ao
|
|
53
|
+
* estado: o estado COMPARTILHADO guarda o que a engine possui — acessibilidade, idioma, dispositivo.) */
|
|
54
|
+
// ========================= O RESTO DA RODADA SAIU DAQUI (ADR-0038, Fase B) =========================
|
|
55
|
+
// `ended`, `decorSeed`, `grassDensity`, `selVizPlayer` e `pauseActor` foram para `core/run-state`, na
|
|
56
|
+
// segunda fatia do passo 2. Nenhum deles persiste — que é o critério do corte —, e todos eram importados
|
|
57
|
+
// APENAS pelo composition root, o que manteve a mudança em dois arquivos, como na primeira fatia.
|
|
58
|
+
//
|
|
59
|
+
// O CLAMP do `grassDensity` foi junto, e ele é a razão de aquele setter existir: a fração vinha protegida só
|
|
60
|
+
// para quem entrasse pelo `window.__incl`, e qualquer outro caminho podia gravar 5 ou -1. Deixá-lo para trás
|
|
61
|
+
// transformaria a fábrica num `let` com nome novo.
|
|
62
|
+
//
|
|
63
|
+
// E a fatia grande saiu em 2026-08-26: `players` e `numPlayers`, com quatorze importadores CADA. Com o
|
|
64
|
+
// `phase` indo para a pilha de cenas no mesmo dia, a RODADA saiu INTEIRA daqui — este módulo é só de PÁGINA
|
|
65
|
+
// agora, e `tests/lifetime-gate.node.test.ts` afirma isso a cada rodada de testes.
|
|
66
|
+
// ========================= `cenario` E `activity` SAÍRAM DAQUI (ADR-0038, Fase B) =========================
|
|
67
|
+
// Os dois eram estado de JOGO morando na engine — a mesma exceção que o `coins` e o `quizLevel` já haviam
|
|
68
|
+
// deixado no item 19. O corte por LIFETIME do ADR-0038 os classifica como GAME: ambos são persistidos em
|
|
69
|
+
// chave `kJogo()`, que é o critério mecânico, e ambos viajam com o cartucho quando o jogo mudar de
|
|
70
|
+
// repositório (ADR-0036).
|
|
71
|
+
//
|
|
72
|
+
// Moram agora em `game/state`, com a MESMA forma — binding vivo + setter que persiste e emite. O que mudou
|
|
73
|
+
// foi só o endereço, e é isso que torna a mudança conferível.
|
|
74
|
+
//
|
|
75
|
+
// Um consumidor precisou de mais que um import novo: `ui/activities-menu` é ENGINE, e o gate de fronteira
|
|
76
|
+
// proíbe engine importar de `game/` (a lista dele esvaziou em 2026-08-25). Ele passou a receber
|
|
77
|
+
// `getActivityId`/`setActivityId` por injeção, que é o que a raiz de composição existe para fazer.
|
|
78
|
+
// --- vizMode: modo visual/cor ativo (persistido em incl_viz). A validação (VIZ_CYCLE) e o default por
|
|
79
|
+
// prefers-contrast ficam no game.js. initVizMode NÃO persiste (o default de mídia deve seguir o SO a cada
|
|
80
|
+
// boot; persistir travaria o rastreio de prefers-contrast). Mudanças do usuário usam setVizModeValue. ---
|
|
81
|
+
export let vizMode = 'normal';
|
|
82
|
+
export function initVizMode(mode) { vizMode = mode; }
|
|
83
|
+
export function setVizModeValue(mode) { vizMode = mode; store.set('incl_viz', mode); emit('vizMode', mode); }
|
|
84
|
+
/* (`coins` SAIU daqui em 2026-08-25, item 19 — está em `game/state`. Ele era `unknown[]` porque `core/` não
|
|
85
|
+
* podia conhecer o tipo, e esse `unknown` era o SINTOMA: um estado que não consegue declarar o próprio tipo
|
|
86
|
+
* está na camada errada. Do outro lado da fronteira ele é `unknown[]` ainda, mas por escolha de quem pode
|
|
87
|
+
* decidir — e o `game/coins` que o consome sabe exatamente o que há dentro.) */
|
|
88
|
+
// ========================= `players` E `numPlayers` SAÍRAM DAQUI (ADR-0038, Fase B) =========================
|
|
89
|
+
// Os dois são RODADA pelo critério mecânico do ADR-0038 — nenhum é persistido —, e moram em
|
|
90
|
+
// `core/run-state`, na instância que a raiz de composição possui.
|
|
91
|
+
//
|
|
92
|
+
// O `players` era o caso mais caro de todos os treze. Um `export let` é um binding vivo COMPARTILHADO: dois
|
|
93
|
+
// jogos na mesma página (que é o que a casca do `demos` faz, ADR-0036) veriam a MESMA lista, e o segundo
|
|
94
|
+
// começaria com os jogadores do primeiro ainda dentro. Não haveria erro em lugar nenhum — só uma criança a
|
|
95
|
+
// mais na tela.
|
|
96
|
+
//
|
|
97
|
+
// A referência continua sendo mutada NO LUGAR (`push`/`length`/`players[i]`), e é por isso que a rodada não
|
|
98
|
+
// tem setter para ela: trocar a lista inteira deixaria para trás as referências que os módulos já guardaram.
|
|
99
|
+
// Quem entra e quem sai é `game/session`, e só ele recebe a lista mutável.
|
|
100
|
+
//
|
|
101
|
+
// (O tipo continua sendo `Player[]` — a visão da ENGINE. Cada jogo acrescenta campos e estreita por conta
|
|
102
|
+
// própria, que é o que o ADR-0033 desenhou.)
|
|
103
|
+
/**
|
|
104
|
+
* OS PADRÕES, com nome. Um valor por linha, e cada um usado em DOIS lugares: a leitura do boot (quando não há
|
|
105
|
+
* nada gravado) e o "restaurar padrões" do menu que o contém (ADR-0028).
|
|
106
|
+
*
|
|
107
|
+
* Existe porque a alternativa é escrever o mesmo padrão duas vezes — uma no `store.getBool(chave, X)` e outra
|
|
108
|
+
* no reset — e este repositório já mostrou quatro vezes o que acontece com duas cópias que ninguém obriga a
|
|
109
|
+
* concordar. Aqui a divergência seria pior que as anteriores: um reset que restaura um valor DIFERENTE do que
|
|
110
|
+
* o jogo usa quando nunca foi configurado deixa a criança num terceiro estado, que não é nem o dela nem o de
|
|
111
|
+
* fábrica, e que ela não tem como nomear para pedir ajuda.
|
|
112
|
+
*
|
|
113
|
+
* `as const` + `Object.freeze` de propósito: um padrão que alguém consiga escrever em tempo de execução deixa
|
|
114
|
+
* de ser padrão.
|
|
115
|
+
*/
|
|
116
|
+
/**
|
|
117
|
+
* O padrão do MOVIMENTO REDUZIDO não é uma constante — é o que o sistema operacional pede.
|
|
118
|
+
*
|
|
119
|
+
* Mora aqui, ao lado do DEFAULTS, porque a regra do ADR-0029 é que existe UMA fonte sobre o que é padrão, e
|
|
120
|
+
* um padrão calculado não deixa de ser padrão por não caber num objeto congelado. Quem lê isto: a carga do
|
|
121
|
+
* boot (main.js) e o "restaurar padrões" do menu de sensibilidade visual.
|
|
122
|
+
*
|
|
123
|
+
* Por que importa que o reset leia isto em vez de `false`: numa máquina cujo dono pediu menos movimento,
|
|
124
|
+
* devolver `false` RELIGARIA a animação — o reset passaria a fazer, sozinho, exatamente o que a WCAG 2.3.3
|
|
125
|
+
* existe para impedir, e faria isso na tela de quem já tinha dito que não aguenta.
|
|
126
|
+
*
|
|
127
|
+
* `matchMedia` é guardado: este módulo roda no projeto `node` dos testes, onde `window` não existe.
|
|
128
|
+
*/
|
|
129
|
+
export function defaultReducedMotion() {
|
|
130
|
+
return !!(typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
131
|
+
}
|
|
132
|
+
export const DEFAULTS = Object.freeze({
|
|
133
|
+
// auditiva
|
|
134
|
+
modoCego: false,
|
|
135
|
+
caneBlockDiv: 1,
|
|
136
|
+
captionsOn: true,
|
|
137
|
+
menuIndexOn: true, // o indice nasce LIGADO: quem nao sabe que ele existe e quem mais precisa dele
|
|
138
|
+
// motora
|
|
139
|
+
wheelchair: false,
|
|
140
|
+
oneButton: false,
|
|
141
|
+
easy: false, // por jogador (Modo Fácil)
|
|
142
|
+
toggleMove: false, // por jogador (movimento por alternância)
|
|
143
|
+
// A alternância do botão de CORRER nasce desligada de FÁBRICA — e liga sozinha no controle de tela, que é
|
|
144
|
+
// contexto e não escolha. A distinção importa para a marca do ADR-0029: ver `refreshMarks` em
|
|
145
|
+
// ui/settings-motor, que marca a ESCOLHA guardada e não o estado.
|
|
146
|
+
toggleRun: false, // por jogador (alternância do botão de correr)
|
|
147
|
+
// visual
|
|
148
|
+
cbSafe: false,
|
|
149
|
+
ownerColors: true,
|
|
150
|
+
lq: 0, // realce de contraste L→Q desligado
|
|
151
|
+
hcOutlineFg: 1,
|
|
152
|
+
hcOutlineBg: 1,
|
|
153
|
+
// comunicação (hoje só a caixa da letra; o menu de CAA do ADR-0028 amplia isto)
|
|
154
|
+
letterCase: 'upper',
|
|
155
|
+
});
|
|
156
|
+
// --- modoCego: MODO CEGO (A12e auditiva). Só as ajudas de áudio — bengala, sonar, guarda de beirada,
|
|
157
|
+
// narração —, sem tela preta; a simulação de cegueira do Modo Empatia é outra coisa e liga esta por cima.
|
|
158
|
+
//
|
|
159
|
+
// Migrado do main.js (ADR-0027 passo 4 / #50). Era a variável com MAIS encanamento de injeção do projeto:
|
|
160
|
+
// dezesseis pontos em seis módulos passavam `getModoCego`/`setModoCego` por ctx, e a colisão a lia por
|
|
161
|
+
// closure. Estado que seis módulos consultam não é do composition root; e enquanto for, `createGame()` não
|
|
162
|
+
// pode existir sem capturá-la, que é justamente o teste de fronteira que o ADR-0027 quer rodar.
|
|
163
|
+
//
|
|
164
|
+
// O SETTER FAZ TRÊS COISAS E SÓ TRÊS: grava, persiste, avisa. Os efeitos que o main.js pendurava no
|
|
165
|
+
// antigo `setModoCego` — refazer os extras do nível, refletir o painel, anunciar ao leitor de tela — NÃO
|
|
166
|
+
// entram aqui: são reação, e quem reage assina o evento. Um setter que sabe redesenhar a tela é um setter
|
|
167
|
+
// que nenhum teste consegue chamar. ---
|
|
168
|
+
export let modoCego = store.getBool('incl_modocego', DEFAULTS.modoCego);
|
|
169
|
+
export function setModoCegoValue(on) {
|
|
170
|
+
if (modoCego === on)
|
|
171
|
+
return; // a guarda VEM DO ORIGINAL: sem ela o anúncio repetiria a cada clique redundante
|
|
172
|
+
modoCego = on;
|
|
173
|
+
store.setBool('incl_modocego', on);
|
|
174
|
+
emit('modoCego', on);
|
|
175
|
+
}
|
|
176
|
+
export let letterCase = store.get(store.KEYS.letterCase, DEFAULTS.letterCase) === 'upper' ? 'upper' : 'mixed';
|
|
177
|
+
export function setLetterCaseValue(c) {
|
|
178
|
+
if (letterCase === c)
|
|
179
|
+
return;
|
|
180
|
+
letterCase = c;
|
|
181
|
+
store.set(store.KEYS.letterCase, c);
|
|
182
|
+
emit('letterCase', c);
|
|
183
|
+
}
|
|
184
|
+
export let captionsOn = store.getBool(store.KEYS.captions, DEFAULTS.captionsOn);
|
|
185
|
+
export function setCaptionsOnValue(on) {
|
|
186
|
+
const v = !!on;
|
|
187
|
+
if (captionsOn === v)
|
|
188
|
+
return;
|
|
189
|
+
captionsOn = v;
|
|
190
|
+
store.setBool(store.KEYS.captions, v);
|
|
191
|
+
emit('captionsOn', v);
|
|
192
|
+
}
|
|
193
|
+
// --- menuIndexOn: o "6 de 10" no fim do anuncio de cada item de menu (ADR-0044, item 3).
|
|
194
|
+
//
|
|
195
|
+
// NASCE LIGADO, e a razao e a mesma do modo cego nascer com TTS e sonar: quem precisa do indice para se
|
|
196
|
+
// orientar nao tem como saber que ele existe se ele vier desligado. Quem NAO precisa descobre o ajuste
|
|
197
|
+
// lendo o menu, que e justamente a coisa que essa pessoa consegue fazer.
|
|
198
|
+
//
|
|
199
|
+
// PERSISTE em `incl_menuindex` (escopo da CRIANCA, ADR-0027): a preferencia segue com ela de jogo em jogo.
|
|
200
|
+
export let menuIndexOn = store.getBool(store.KEYS.menuIndex, DEFAULTS.menuIndexOn);
|
|
201
|
+
export function setMenuIndexOnValue(on) {
|
|
202
|
+
const v = !!on;
|
|
203
|
+
if (menuIndexOn === v)
|
|
204
|
+
return;
|
|
205
|
+
menuIndexOn = v;
|
|
206
|
+
store.setBool(store.KEYS.menuIndex, v);
|
|
207
|
+
emit('menuIndexOn', v);
|
|
208
|
+
}
|
|
209
|
+
// --- cbSafe: PALETA SEGURA PARA DALTONISMO (Okabe-Ito). Não é um filtro sobre a imagem — é a escolha das
|
|
210
|
+
// cores de origem, aplicada IN-PLACE em PCOLOR para que todo mundo que já referencia a array veja a troca. ---
|
|
211
|
+
export let cbSafe = store.getBool(store.KEYS.cbsafe, DEFAULTS.cbSafe);
|
|
212
|
+
export function setCbSafeValue(on) {
|
|
213
|
+
const v = !!on;
|
|
214
|
+
if (cbSafe === v)
|
|
215
|
+
return;
|
|
216
|
+
cbSafe = v;
|
|
217
|
+
store.setBool(store.KEYS.cbsafe, v);
|
|
218
|
+
emit('cbSafe', v);
|
|
219
|
+
}
|
|
220
|
+
// --- ownerColors: no multijogador, cada item aparece na cor de QUEM pode pegá-lo. Desligado, todos veem a cor
|
|
221
|
+
// original — o que é preferível para quem não distingue as cores dos donos. ---
|
|
222
|
+
export let ownerColors = store.getBool(store.KEYS.ownercolors, DEFAULTS.ownerColors);
|
|
223
|
+
export function setOwnerColorsValue(on) {
|
|
224
|
+
const v = !!on;
|
|
225
|
+
if (ownerColors === v)
|
|
226
|
+
return;
|
|
227
|
+
ownerColors = v;
|
|
228
|
+
store.setBool(store.KEYS.ownercolors, v);
|
|
229
|
+
emit('ownerColors', v);
|
|
230
|
+
}
|
|
231
|
+
const nivelContorno = (v) => Math.max(0, Math.min(2, v | 0));
|
|
232
|
+
// --- hcOutlineFg / hcOutlineBg: CONTORNOS do alto contraste, e são dois porque servem a critérios diferentes.
|
|
233
|
+
// `fg` contorna o primeiro plano — personagem e itens — e atende a WCAG 2.4.7 (foco visível). `bg` contorna
|
|
234
|
+
// o perímetro externo de plataforma, água e lava, delimitando navegável × não-navegável, e atende a
|
|
235
|
+
// WCAG 1.4.11 (contraste de componente ≥ 3:1). Confundi-los apagaria uma das duas garantias.
|
|
236
|
+
//
|
|
237
|
+
// A saturação em 0..2 vem do original e é dupla: no boot (contra um localStorage corrompido) e na escrita
|
|
238
|
+
// (contra um chamador). No main.js isso obrigava a declarar com um valor provisório e reatribuir na linha
|
|
239
|
+
// seguinte, porque a leitura saturada não cabia no mesmo `let`; aqui a função a resolve de uma vez. ---
|
|
240
|
+
export let hcOutlineFg = nivelContorno(store.getNum(store.KEYS.outfg, DEFAULTS.hcOutlineFg));
|
|
241
|
+
export function setOutlineFgValue(v) {
|
|
242
|
+
const n = nivelContorno(v);
|
|
243
|
+
if (hcOutlineFg === n)
|
|
244
|
+
return;
|
|
245
|
+
hcOutlineFg = n;
|
|
246
|
+
store.set(store.KEYS.outfg, n);
|
|
247
|
+
emit('hcOutlineFg', n);
|
|
248
|
+
}
|
|
249
|
+
export let hcOutlineBg = nivelContorno(store.getNum(store.KEYS.outbg, DEFAULTS.hcOutlineBg));
|
|
250
|
+
export function setOutlineBgValue(v) {
|
|
251
|
+
const n = nivelContorno(v);
|
|
252
|
+
if (hcOutlineBg === n)
|
|
253
|
+
return;
|
|
254
|
+
hcOutlineBg = n;
|
|
255
|
+
store.set(store.KEYS.outbg, n);
|
|
256
|
+
emit('hcOutlineBg', n);
|
|
257
|
+
}
|
|
258
|
+
// --- caneBlockDiv: espaçamento da batida da BENGALA, em blocos pisados. 1 = uma batida por bloco;
|
|
259
|
+
// 2 = uma batida a cada meio bloco. Não é preferência de som: é a resolução com que uma criança cega
|
|
260
|
+
// mede a distância que andou, e por isso a colisão a lê a cada passo. ---
|
|
261
|
+
export let caneBlockDiv = store.getNum('incl_cane_div', DEFAULTS.caneBlockDiv) || DEFAULTS.caneBlockDiv;
|
|
262
|
+
export function setCaneBlockDivValue(div) {
|
|
263
|
+
const d = (+div) || 1; // o `|| 1` vem do original: um valor corrompido no localStorage viraria NaN e a
|
|
264
|
+
if (caneBlockDiv === d)
|
|
265
|
+
return; // bengala pararia de bater, que é o modo de falha mais silencioso possível
|
|
266
|
+
caneBlockDiv = d;
|
|
267
|
+
store.set('incl_cane_div', d);
|
|
268
|
+
emit('caneBlockDiv', d);
|
|
269
|
+
}
|
|
270
|
+
// --- wheelchair: MODO CADEIRANTE. Muda a geometria do nível inteiro — degraus e escada viram rampas e
|
|
271
|
+
// elevadores, moedas descem para o chão, lava vira chão, e só voo e super-corrida sobrevivem como poderes.
|
|
272
|
+
// Por isso a colisão a lê: `isSolidType` responde diferente com ela ligada. ---
|
|
273
|
+
export let wheelchair = store.getBool('incl_wheelchair', DEFAULTS.wheelchair);
|
|
274
|
+
export function setWheelchairValue(on) {
|
|
275
|
+
if (wheelchair === on)
|
|
276
|
+
return;
|
|
277
|
+
wheelchair = on;
|
|
278
|
+
store.setBool('incl_wheelchair', on);
|
|
279
|
+
emit('wheelchair', on);
|
|
280
|
+
}
|
|
281
|
+
// --- oneButton: UM BOTÃO POR VEZ. Ignora combinações simultâneas, para quem não consegue pressionar duas
|
|
282
|
+
// teclas ao mesmo tempo. ---
|
|
283
|
+
export let oneButton = store.getBool('incl_onebtn', DEFAULTS.oneButton);
|
|
284
|
+
export function setOneButtonValue(on) {
|
|
285
|
+
if (oneButton === on)
|
|
286
|
+
return;
|
|
287
|
+
oneButton = on;
|
|
288
|
+
store.setBool('incl_onebtn', on);
|
|
289
|
+
emit('oneButton', on);
|
|
290
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
export declare const TYPE_GLYPH: Record<number, string>;
|
|
3
|
+
export declare const TILE_NAME: Record<number, string>;
|
|
4
|
+
export declare const GLYPH_TYPE: Record<string, number>;
|
|
5
|
+
export declare function parseLevel(text: string): number[][];
|
|
6
|
+
export declare function gridToGlyphs(grid: number[][]): string;
|
|
7
|
+
export declare function selfTest(): {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
unique: boolean;
|
|
10
|
+
invertible: boolean;
|
|
11
|
+
missing: number[];
|
|
12
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// core/tiles.ts — Legend de tiles + parser do mapa em texto-glifo (1 glifo significativo por tile). Módulo-folha,
|
|
3
|
+
// ZERO deps. O jogo NÃO lê Tiled/Aseprite em runtime — só este texto-glifo, legível/diffável como ascii art.
|
|
4
|
+
// tipo (0–14) → glifo. Terreno = símbolos; power-ups = LETRAS maiúsculas mnemônicas.
|
|
5
|
+
export const TYPE_GLYPH = {
|
|
6
|
+
1: '.', // ar (iluminado)
|
|
7
|
+
0: ':', // ar escuro / região secreta
|
|
8
|
+
2: '#', // pedra / chão sólido
|
|
9
|
+
6: '=', // parede dura (sem quicar)
|
|
10
|
+
3: '~', // água
|
|
11
|
+
4: 'H', // escada
|
|
12
|
+
5: '^', // trampolim
|
|
13
|
+
9: 'x', // lava / perigo
|
|
14
|
+
10: '|', // portão
|
|
15
|
+
11: '*', // chave
|
|
16
|
+
7: 'S', // power-up super-pulo
|
|
17
|
+
8: 'F', // power-up voo
|
|
18
|
+
12: 'T', // power-up super-corrida (turbo)
|
|
19
|
+
13: 'U', // power-up ultra-pulo
|
|
20
|
+
14: 'C', // power-up ventosa (cling)
|
|
21
|
+
};
|
|
22
|
+
// tipo → nome legível (UI e editor de mapa). Uma verdade só.
|
|
23
|
+
export const TILE_NAME = {
|
|
24
|
+
0: 'ar escuro/secreto', 1: 'ar', 2: 'pedra', 3: 'água', 4: 'escada', 5: 'trampolim', 6: 'parede',
|
|
25
|
+
7: 'super-pulo', 8: 'voo', 9: 'lava/perigo', 10: 'portão', 11: 'chave', 12: 'super-corrida',
|
|
26
|
+
13: 'ultra-pulo', 14: 'ventosa',
|
|
27
|
+
};
|
|
28
|
+
// glifo → tipo (inverso). Construído do TYPE_GLYPH para não divergir.
|
|
29
|
+
export const GLYPH_TYPE = Object.fromEntries(Object.entries(TYPE_GLYPH).map(([t, g]) => [g, +t]));
|
|
30
|
+
const AIR = 1; // glifo desconhecido / vazio à direita → ar iluminado
|
|
31
|
+
// Texto-glifo → grid numérico. Preserva o comprimento de cada linha (mapa é irregular; o buildWorld preenche
|
|
32
|
+
// o vazio à direita com ar). Linhas iniciadas por "#!" são meta (nome/autor) e são ignoradas — "#" sozinho é
|
|
33
|
+
// parede, então o marcador de meta é "#!" no INÍCIO da linha.
|
|
34
|
+
export function parseLevel(text) {
|
|
35
|
+
const rows = [];
|
|
36
|
+
for (const line of text.split(/\r?\n/)) {
|
|
37
|
+
if (line.startsWith('#!'))
|
|
38
|
+
continue; // meta
|
|
39
|
+
if (line.length === 0)
|
|
40
|
+
continue; // linha vazia (ex.: \n final) — nossos mapas não têm linha vazia
|
|
41
|
+
const row = [];
|
|
42
|
+
for (const ch of line)
|
|
43
|
+
row.push(ch in GLYPH_TYPE ? GLYPH_TYPE[ch] : AIR);
|
|
44
|
+
rows.push(row);
|
|
45
|
+
}
|
|
46
|
+
return rows;
|
|
47
|
+
}
|
|
48
|
+
// Grid numérico → texto-glifo (para gerar o .map.txt e para o teste de ida-e-volta). Sem newline final.
|
|
49
|
+
export function gridToGlyphs(grid) {
|
|
50
|
+
return grid.map((row) => row.map((t) => TYPE_GLYPH[t] ?? '.').join('')).join('\n');
|
|
51
|
+
}
|
|
52
|
+
// Sanidade: o legend é uma bijeção sobre 0..14? (glifos únicos e todos os tipos mapeados). Prova que a
|
|
53
|
+
// conversão é sem perda para QUALQUER grid. Retorna {ok, unique, invertible, missing}.
|
|
54
|
+
export function selfTest() {
|
|
55
|
+
const types = Array.from({ length: 15 }, (_, i) => i);
|
|
56
|
+
const glyphs = types.map((t) => TYPE_GLYPH[t]);
|
|
57
|
+
const missing = types.filter((t) => !(t in TYPE_GLYPH));
|
|
58
|
+
const unique = new Set(glyphs).size === glyphs.length;
|
|
59
|
+
const invertible = types.every((t) => GLYPH_TYPE[TYPE_GLYPH[t]] === t);
|
|
60
|
+
return { ok: missing.length === 0 && unique && invertible, unique, invertible, missing };
|
|
61
|
+
}
|