@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,381 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/keydown.ts — O ROTEADOR DE TECLADO: o que UMA tecla SIGNIFICA, aqui e agora.
|
|
3
|
+
//
|
|
4
|
+
// Este é o ouvinte de `keydown` de BOLHA do game.js — o único ponto do jogo por onde passa toda tecla que não
|
|
5
|
+
// foi interceptada antes. Ele não é "o controle do personagem": andar/pular são consequência da ÚLTIMA das
|
|
6
|
+
// nove perguntas que ele faz. As oito primeiras decidem se a tecla é do jogo ou de outra coisa — demo,
|
|
7
|
+
// remapeamento, diálogo, tela de vitória, menu inicial, número de telas, pausa, quiz — e é essa SEQUÊNCIA que
|
|
8
|
+
// é a especificação: quem vem antes vence, e mudar a ordem de duas guardas muda o jogo sem quebrar nada que
|
|
9
|
+
// um build, um `tsc` ou um olho consigam ver.
|
|
10
|
+
//
|
|
11
|
+
// ======================= A ENTREGA: DECIDIR ≠ EXECUTAR =======================
|
|
12
|
+
// O bloco original é uma CADEIA DE DECISÃO PURA DISFARÇADA DE EFEITO. Dado (código da tecla, fase, quem tem
|
|
13
|
+
// quiz aberto, qual diálogo está visível, número de telas, esquemas de tecla), existe UMA resposta sobre o que
|
|
14
|
+
// a tecla significa — e o `preventDefault`, o `click()`, o `togglePause()` vêm só DEPOIS dessa resposta. No
|
|
15
|
+
// monólito as duas metades estavam trançadas: cada `if` misturava a pergunta ("tem diálogo aberto?") com a
|
|
16
|
+
// resposta ("então esconde e sai"), e por isso a ordem de precedência — a parte que realmente importa — só
|
|
17
|
+
// podia ser verificada jogando.
|
|
18
|
+
//
|
|
19
|
+
// Aqui elas são dois objetos separados:
|
|
20
|
+
// · `decideKeydown(evento, estado) -> KeydownDecision` — PURA. Sem DOM, sem PIXI, sem áudio, sem `window`.
|
|
21
|
+
// Roda no project `node`. Cada ramo da ordem de precedência é um caso de teste, e a precedência ENTRE os
|
|
22
|
+
// ramos (quiz aberto E diálogo visível: quem ganha?) é uma asserção, não um relato.
|
|
23
|
+
// · `initKeydown(ctx).onKeydown(e)` — a metade IMPURA: pega a decisão pronta e a carimba no mundo.
|
|
24
|
+
// Mesmo movimento que `ui/shell.ts` fez com `phaseView`/`applyPhaseView` e `ui/title.ts` com
|
|
25
|
+
// `computeTitleMenuView`; é o precedente da casa, e aqui ele vale mais do que lá, porque o que está em jogo é
|
|
26
|
+
// o teclado — a única modalidade que TODA pessoa que joga sem mouse, sem visão ou sem controle depende.
|
|
27
|
+
//
|
|
28
|
+
// ======================= O QUE FICOU DE FORA DA PARTE PURA, E POR QUÊ =======================
|
|
29
|
+
// Duas guardas do topo NÃO cabem em `decideKeydown`, e forçá-las lá seria mentira:
|
|
30
|
+
// · `attractCtl.onInput()` (game/attract.ts) — ela DECIDE E AGE na mesma chamada: zera o contador de
|
|
31
|
+
// ociosidade, encerra a demo se estiver rodando, e devolve se encerrou. Não há como perguntar sem mexer.
|
|
32
|
+
// · `ctrlPanel.handleCaptureKeydown(e)` (ui/settings-controls.ts) — idem: se há remapeamento em curso, ela
|
|
33
|
+
// GRAVA a tecla no esquema e devolve `true`.
|
|
34
|
+
// As duas continuam sendo as duas primeiras linhas de `onKeydown`, na mesma ordem, e o teste que prova que
|
|
35
|
+
// elas vêm antes de tudo é do wrapper (com sondas de mentira), não da função pura.
|
|
36
|
+
// A leitura de VISIBILIDADE dos diálogos também não entra pura: ela é `querySelector` + `.hidden`. O que entra
|
|
37
|
+
// na parte pura é o RESULTADO dela (três booleanos e um id), montado por `snapshot()`.
|
|
38
|
+
//
|
|
39
|
+
// ======================= ⚠️ PAUSADO, ESTE OUVINTE NÃO É ALCANÇADO PARA Escape =======================
|
|
40
|
+
// `ui/menu-nav.ts` registra o PRÓPRIO `keydown` em fase de CAPTURA e dá `stopPropagation()`. Com o jogo
|
|
41
|
+
// PAUSADO (`phase==='paused'`), toda tecla com intenção de menu — Escape inclusive — morre lá em cima e nunca
|
|
42
|
+
// desce até aqui. Consequência MEDIDA no navegador, e anotada tanto no game.js quanto no cabeçalho de
|
|
43
|
+
// menu-nav (DEFEITO 2): a cadeia `overlays.escapeTarget()` e o `togglePause()` de Escape são, na prática,
|
|
44
|
+
// código morto enquanto pausado; quem fecha é sempre o topo da pilha de z-index.
|
|
45
|
+
// ISTO É COMPORTAMENTO ATUAL E FOI PRESERVADO — não é bug para consertar aqui, é rede de segurança de que o
|
|
46
|
+
// conserto futuro vai precisar (tirar o `stopPropagation()` sem mais nada faz o `if(Escape||Enter)
|
|
47
|
+
// togglePause()` daqui DESPAUSAR o jogo com a Ajuda aberta). Os testes o ancoram como está: `decideKeydown`
|
|
48
|
+
// devolve a decisão que ELE tomaria, e o teste do wrapper documenta que, pausado, ela não é consultada porque
|
|
49
|
+
// o evento não chega. Ver `tests/keydown.node.test.js`, bloco "a captura do menu-nav chega antes".
|
|
50
|
+
//
|
|
51
|
+
// ======================= O QUE VEIO JUNTO, E O QUE NÃO VEIO =======================
|
|
52
|
+
// · `keyup` VEIO. Ele é a outra metade exata do `keys.add(e.code)` da última linha do keydown: sem os dois no
|
|
53
|
+
// mesmo lugar, quem lê "quando uma tecla entra em `keys`" tem de procurar a saída em outro arquivo. `attach()`
|
|
54
|
+
// instala os dois, na mesma ordem de antes.
|
|
55
|
+
// · `blur` NÃO veio. Parece irmão, mas não é: `keys` também recebe códigos INJETADOS pelos botões de toque
|
|
56
|
+
// (o `press`/`release` do game.js, que faz `keys.add(codeFor(act))`) e pelo controle por webcam
|
|
57
|
+
// (`ui/webcam.ts` despacha `KeyboardEvent` sintético). `blur -> keys.clear()` é uma rede de ciclo de vida da
|
|
58
|
+
// JANELA sobre um conjunto que não é só do teclado; trazê-la para cá daria a este módulo a
|
|
59
|
+
// responsabilidade de limpar estado que ele não escreve. Fica no game.js, uma linha, ao lado do `resize`.
|
|
60
|
+
// · A cadeia de Escape, o registro de overlays e o `closeById` são de `ui/settings-panel.ts`. Este módulo
|
|
61
|
+
// CONSOME (`escapeTarget`, `closeOverlayById`) e não reimplementa — mesma disciplina de menu-nav.
|
|
62
|
+
// · `whichPlayer`/`actionOf`/`controlsState` são de `input/keyboard-runtime.ts`, que é quem sabe de esquema de
|
|
63
|
+
// teclas. Aqui eles entram injetados e são só consultados. `controlsState()` é MEMORIZADO lá: este módulo o
|
|
64
|
+
// chama uma vez por tecla, como o original.
|
|
65
|
+
// · A navegação do menu inicial (`navTitle`) é de `ui/activities-menu.ts`; a do menu de pausa é de
|
|
66
|
+
// `ui/menu-nav.ts`. Este módulo decide QUE a tecla é de navegação e para QUEM ela vai — nunca desenha menu.
|
|
67
|
+
//
|
|
68
|
+
// ======================= INJEÇÃO E ORDEM DE BOOT =======================
|
|
69
|
+
// `initKeydown` tem de ser chamado NO LUGAR EXATO onde o `addEventListener('keydown', …)` estava (logo depois
|
|
70
|
+
// de `assignControls()`), por dois motivos independentes:
|
|
71
|
+
// 1. ORDEM DE REGISTRO. Os ouvintes de bolha do mesmo alvo disparam na ordem em que foram registrados. Este é
|
|
72
|
+
// hoje o primeiro `keydown` de bolha da janela, e o único de captura (menu-nav) já vence por fase.
|
|
73
|
+
// Registrar mais tarde não muda nada HOJE, mas passa a depender de nenhum outro ouvinte de janela nascer
|
|
74
|
+
// no meio — dependência invisível que não vale a pena criar.
|
|
75
|
+
// 2. TDZ. Quase tudo o que este módulo aciona (`attractCtl`, `ctrlPanel`, `gamepadApi`, `hud`, `navTitle`,
|
|
76
|
+
// `activateScreens`, `togglePause`, `hideTouchControls`, as quatro funções de quiz) é `const`/`function`
|
|
77
|
+
// declarado CENTENAS de linhas ABAIXO desse ponto. O ouvinte original só funcionava porque o corpo dele é
|
|
78
|
+
// preguiçoso: nada é lido até a primeira tecla. Por isso TODO o ctx é `() => …`: valor nenhum é resolvido
|
|
79
|
+
// na montagem. Passar `attractCtl` (ou `hud`, ou `navTitle`) por VALOR derruba o boot em TDZ.
|
|
80
|
+
// A regra da casa vale igual: o que o game.js REATRIBUI entra por GETTER (`oneButton`, `numPlayers`, `players`,
|
|
81
|
+
// o estado de controles). `keys` é `const` de `input/state.ts`, mutado in place — entra por VALOR (é o mesmo
|
|
82
|
+
// objeto para sempre). `phase` é binding vivo de `core/state.ts`, importado direto, como em menu-nav.
|
|
83
|
+
//
|
|
84
|
+
// ÚNICA DIFERENÇA DE COMPORTAMENTO EM RELAÇÃO AO MONÓLITO, declarada: `snapshot()` lê os três `hidden` e o
|
|
85
|
+
// `escapeTarget()` de uma vez, enquanto o original os lia em curto-circuito (só chegava ao `#padwiz` se não
|
|
86
|
+
// houvesse overlay nem `#touchcfg`). São no máximo quatro `querySelector` a mais por tecla, todos de leitura
|
|
87
|
+
// pura (`escapeTarget` só varre o registro e testa `.hidden` — conferido em ui/settings-panel.ts). Nada
|
|
88
|
+
// observável muda; anotado porque medir depois é mais caro do que escrever agora.
|
|
89
|
+
//
|
|
90
|
+
// SEM I/O NO IMPORT: o corpo do módulo só declara tabelas congeladas e funções puras. Todo efeito passa por
|
|
91
|
+
// `initKeydown` (e mesmo ele não faz nada até `attach()`).
|
|
92
|
+
//
|
|
93
|
+
// Ver docs/5-Refactoring/plano-modularizacao-mapa.md (D2-a).
|
|
94
|
+
import { t } from '../core/i18n.js';
|
|
95
|
+
import { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
|
|
96
|
+
/* --- tabelas (congeladas: são dados, e um `Set` nomeado deixa a tabela auditável de fora) --- */
|
|
97
|
+
/** Fácil (SOLO): Ctrl = Especial, Shift = Trocar poder. Fora de Win/Alt/AltGr, de propósito. */
|
|
98
|
+
export const EASY_SHORTCUTS = new Set(['ControlLeft', 'ControlRight', 'ShiftLeft', 'ShiftRight']);
|
|
99
|
+
/** Alt + fileira de números (NÃO o teclado numérico) = 1..4 telas. Alt fica livre: o solo não o usa. */
|
|
100
|
+
export const SCREEN_DIGITS = /^Digit[1234]$/;
|
|
101
|
+
/** Pausa: Escape ou o Enter CENTRAL. `NumpadEnter` NÃO pausa (E14, verbatim). */
|
|
102
|
+
export const PAUSE_KEYS = new Set(['Escape', 'Enter']);
|
|
103
|
+
// A tabela e a regra do Fácil vivem em input/edges.ts, compartilhadas com gamepad e touch-bindings. Reexporta
|
|
104
|
+
// pelo nome antigo porque quem importa daqui (teste e leitor) espera achá-la aqui.
|
|
105
|
+
export { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
|
|
106
|
+
/* --- predicados puros (exportados: o teste importa a peça, não repete a expressão) --- */
|
|
107
|
+
/** A tecla vale como PULO para alguém? O alias do J1 OU a ação `jump` de qualquer jogador (é o que faz
|
|
108
|
+
* "qualquer um pode apertar Jogar de novo" e "qualquer um confirma no título" serem verdade). */
|
|
109
|
+
export function isJumpKey(code, s) {
|
|
110
|
+
return s.controls.action2.includes(code) || s.players.some((_p, i) => s.actionOf(code, i) === 'action2');
|
|
111
|
+
}
|
|
112
|
+
/** A tecla é de JOGO? Verbatim do `isGameKeyCode` do game.js — o alias achatado OU o esquema de algum
|
|
113
|
+
* jogador. NÃO inclui os atalhos do Fácil (o original também não: por isso `easyKey` é somado à parte). */
|
|
114
|
+
export function isGameKeyCode(code, s) {
|
|
115
|
+
return s.controls.gameKeys.includes(code)
|
|
116
|
+
|| s.players.some((p) => !!p.ctrl && Object.values(p.ctrl).some((arr) => arr.includes(code)));
|
|
117
|
+
}
|
|
118
|
+
/** Os atalhos de acessibilidade do Fácil só existem SOLO (`numPlayers<=1`) e só para o Jogador 1. */
|
|
119
|
+
export function isEasyShortcut(code, s) {
|
|
120
|
+
return !!s.players[0]?.easy && s.numPlayers <= 1 && EASY_SHORTCUTS.has(code);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* A intenção no menu inicial. Repare na ASSIMETRIA, preservada verbatim: `up`/`down` aceitam ArrowUp/ArrowDown
|
|
124
|
+
* ALÉM do alias configurado, `left`/`right` não aceitam ArrowLeft/ArrowRight. No esquema de fábrica isso não
|
|
125
|
+
* aparece (o solo já tem as setas nos quatro lados); aparece para quem REMAPEIA — remapeou esquerda para KeyA
|
|
126
|
+
* e a seta deixa de andar no menu, enquanto cima continua funcionando pelas duas. Está anotado como caso de
|
|
127
|
+
* teste em tests/keydown.node.test.js, e é candidato a conserto, não a "limpeza" silenciosa.
|
|
128
|
+
*/
|
|
129
|
+
export function titleNavOf(code, s, action2) {
|
|
130
|
+
return {
|
|
131
|
+
yes: action2 || code === 'Enter',
|
|
132
|
+
no: code === 'Escape' || s.players.some((_p, i) => s.actionOf(code, i) === 'action3'),
|
|
133
|
+
up: s.controls.up.includes(code) || code === 'ArrowUp',
|
|
134
|
+
down: s.controls.down.includes(code) || code === 'ArrowDown',
|
|
135
|
+
left: s.controls.left.includes(code),
|
|
136
|
+
right: s.controls.right.includes(code),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Alguma intenção foi expressa? Definição única em input/edges; aqui só o nome que este módulo sempre teve. */
|
|
140
|
+
import { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
141
|
+
export { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
142
|
+
/**
|
|
143
|
+
* Quem é o dono do modal que esta tecla comanda? Devolve a POSIÇÃO no array, ou -1.
|
|
144
|
+
* Regra verbatim: tecla de um jogador vai para o modal DAQUELE jogador (e só se ele tiver um aberto); tecla
|
|
145
|
+
* genérica cai no Jogador 1. Tecla de um jogador SEM modal aberto não cai no P1 — ela desce para o jogo
|
|
146
|
+
* normal, que é o que deixa a partida dele continuar enquanto o outro resolve o desafio.
|
|
147
|
+
*/
|
|
148
|
+
export function modalOwnerIndex(code, s) {
|
|
149
|
+
const owner = s.whichPlayer(code);
|
|
150
|
+
if (owner >= 0)
|
|
151
|
+
return s.modalOpen[owner] ? owner : -1;
|
|
152
|
+
return s.modalOpen[0] ? 0 : -1;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Que INTENÇÃO esta tecla exprime dentro do modal do dono. `owner` é a posição no array; `generic` diz se a
|
|
156
|
+
* tecla chegou sem dono (aí as seis leituras usam os aliases do J1 em vez da ação remapeada).
|
|
157
|
+
*
|
|
158
|
+
* As seis leituras são as mesmas de antes, letra por letra. O que saiu foi a linha seguinte: a que trocava
|
|
159
|
+
* "cima" por `delta:-3` e "esquerda" por `delta:-1`, e o desvio de Braille. Isso é grade, e grade é do jogo.
|
|
160
|
+
*/
|
|
161
|
+
export function modalIntentOf(code, s, owner, generic) {
|
|
162
|
+
const pl = s.players[owner];
|
|
163
|
+
if (!pl)
|
|
164
|
+
return null;
|
|
165
|
+
const act = generic ? null : s.actionOf(code, pl.i); // `qpl.i`, não a posição — verbatim
|
|
166
|
+
const K = s.controls;
|
|
167
|
+
const L = act ? act === 'left' : K.left.includes(code);
|
|
168
|
+
const R = act ? act === 'right' : K.right.includes(code);
|
|
169
|
+
const U = act ? act === 'up' : K.up.includes(code);
|
|
170
|
+
const D = act ? act === 'down' : K.down.includes(code);
|
|
171
|
+
const J = act ? act === 'action2' : K.action2.includes(code);
|
|
172
|
+
// ESPECIAL = apagar a última sílaba/letra. A leitura genérica sai de `pl.ctrl`, NÃO de `K` — assimetria do
|
|
173
|
+
// original preservada (não existe alias `KESPECIAL` em ControlsState; o original alcançava `qpl.ctrl`).
|
|
174
|
+
const E = act ? act === 'action3' : ((pl.ctrl?.action3 || []).includes(code));
|
|
175
|
+
// A ORDEM é a do original e importa: com um esquema em que a mesma tecla é `left` e `jump`, ganha `left`.
|
|
176
|
+
if (L)
|
|
177
|
+
return 'left';
|
|
178
|
+
if (R)
|
|
179
|
+
return 'right';
|
|
180
|
+
if (U)
|
|
181
|
+
return 'up';
|
|
182
|
+
if (D)
|
|
183
|
+
return 'down';
|
|
184
|
+
if (J)
|
|
185
|
+
return 'confirm';
|
|
186
|
+
if (E)
|
|
187
|
+
return 'erase';
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* As bordas que esta tecla levanta. Só é chamada quando a tecla AINDA NÃO está segurada — é isso que a torna
|
|
192
|
+
* uma BORDA e não um estado, e é o que impede o auto-repeat do sistema de virar dez pulos.
|
|
193
|
+
* `p.ctrl[act] || []`: o original alcançava `p.ctrl.jump` direto e estouraria num esquema sem a ação. Trocar
|
|
194
|
+
* um TypeError por um no-op não remove rede de conserto nenhuma (o precedente é a guarda de `navPause` em
|
|
195
|
+
* ui/menu-nav.ts) — é a única linha desta função que não é cópia literal.
|
|
196
|
+
*/
|
|
197
|
+
export function edgesFor(code, s) {
|
|
198
|
+
const out = [];
|
|
199
|
+
s.players.forEach((p, idx) => {
|
|
200
|
+
if (!p.ctrl)
|
|
201
|
+
return;
|
|
202
|
+
for (const [act, edge] of EDGE_BY_ACTION) {
|
|
203
|
+
if (!edgeAllowed(act, p.easy))
|
|
204
|
+
continue; // Fácil: sem correr (input/edges.ts, valendo nos três caminhos)
|
|
205
|
+
if ((p.ctrl?.[act] || []).includes(code))
|
|
206
|
+
out.push({ playerIndex: idx, edge });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
// Fácil (solo): Ctrl vira Especial e Shift vira Trocar poder, SEMPRE no Jogador 1 (`player` do original).
|
|
210
|
+
if (isEasyShortcut(code, s))
|
|
211
|
+
out.push({ playerIndex: 0, edge: code.startsWith('Control') ? 'specialEdge' : 'swapEdge' });
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A CADEIA. Nove perguntas, nesta ordem — e a ordem É a especificação.
|
|
216
|
+
* O que NÃO está aqui, e vem antes no wrapper: a demo (attract) e a captura de remapeamento. Ver o cabeçalho.
|
|
217
|
+
*/
|
|
218
|
+
export function decideKeydown(ev, s) {
|
|
219
|
+
const code = ev.code;
|
|
220
|
+
// 1..3 · diálogo aberto BLOQUEIA o jogo — mas só se o elemento estiver DE FATO visível (uma flag presa não
|
|
221
|
+
// trava mais o teclado). Os três saem sem `preventDefault`, verbatim: o diálogo é DOM e quer o comportamento
|
|
222
|
+
// nativo do navegador (Tab, digitação num range) por baixo.
|
|
223
|
+
if (s.escapeTargetId)
|
|
224
|
+
return { kind: 'overlay', closeId: code === 'Escape' ? s.escapeTargetId : null, preventDefault: false };
|
|
225
|
+
if (s.touchCfgVisible)
|
|
226
|
+
return { kind: 'touchcfg', close: code === 'Escape', preventDefault: false };
|
|
227
|
+
if (s.padWizVisible)
|
|
228
|
+
return { kind: 'padwiz', close: code === 'Escape', preventDefault: false };
|
|
229
|
+
// 4..5 · fim de fase / título. O pulo de QUALQUER jogador aciona o botão principal, sem depender do foco do
|
|
230
|
+
// mouse (clicar na tela tirava o foco do botão e o teclado parava de funcionar — report do José).
|
|
231
|
+
const action2 = isJumpKey(code, s);
|
|
232
|
+
const pauseKey = PAUSE_KEYS.has(code);
|
|
233
|
+
if (s.winVisible) {
|
|
234
|
+
const again = action2 || pauseKey;
|
|
235
|
+
return { kind: 'win', again, preventDefault: again };
|
|
236
|
+
}
|
|
237
|
+
if (s.telaDeTitulo) {
|
|
238
|
+
// multi-tela: só o Jogador 1 escolhe o jogo. A tecla de um dos outros é consumida com um aviso falado.
|
|
239
|
+
if (s.numPlayers > 1 && s.whichPlayer(code) > 0)
|
|
240
|
+
return { kind: 'title', wait: true, nav: null, preventDefault: true };
|
|
241
|
+
const nav = titleNavOf(code, s, action2);
|
|
242
|
+
const has = hasTitleIntent(nav);
|
|
243
|
+
return { kind: 'title', wait: false, nav: has ? nav : null, preventDefault: has };
|
|
244
|
+
}
|
|
245
|
+
// 6..7 · número de telas e pausa. As DUAS exigem quiz FECHADO: com um desafio aberto na tela, Alt+3 não pode
|
|
246
|
+
// reconfigurar o jogo por baixo dele, e Enter é a confirmação do quiz, não a pausa.
|
|
247
|
+
const anyModal = s.modalOpen.some(Boolean);
|
|
248
|
+
const inGame = s.emJogo;
|
|
249
|
+
if (ev.altKey && !ev.ctrlKey && SCREEN_DIGITS.test(code) && inGame && !anyModal) {
|
|
250
|
+
return { kind: 'screens', count: +code.slice(5), preventDefault: true };
|
|
251
|
+
}
|
|
252
|
+
if (!anyModal && pauseKey && inGame)
|
|
253
|
+
return { kind: 'pause', preventDefault: true };
|
|
254
|
+
// 8 · modal. A tecla age no modal do DONO dela; genérica cai no P1. `preventDefault` NÃO depende de a
|
|
255
|
+
// tecla ter significado lá dentro: basta ser tecla de jogo — o desafio engole a tecla de qualquer forma.
|
|
256
|
+
if (anyModal) {
|
|
257
|
+
const owner = modalOwnerIndex(code, s);
|
|
258
|
+
if (owner >= 0) {
|
|
259
|
+
return { kind: 'modal', playerIndex: owner, intent: modalIntentOf(code, s, owner, s.whichPlayer(code) < 0),
|
|
260
|
+
preventDefault: s.controls.gameKeys.includes(code) };
|
|
261
|
+
}
|
|
262
|
+
// tecla de um jogador SEM modal cai no jogo normal (a partida dele continua) — segue adiante
|
|
263
|
+
}
|
|
264
|
+
// 9 · jogo normal.
|
|
265
|
+
const gameKey = isEasyShortcut(code, s) || isGameKeyCode(code, s);
|
|
266
|
+
const wake = s.players.reduce((acc, p, idx) => { if (p.waiting && s.actionOf(code, p.i))
|
|
267
|
+
acc.push(idx); return acc; }, []);
|
|
268
|
+
const edges = s.heldKeys.has(code) ? [] : edgesFor(code, s);
|
|
269
|
+
// Empatia motora (um botão por vez): a tecla nova entra e as OUTRAS de jogo saem. `keys.add` vem depois da
|
|
270
|
+
// limpeza no wrapper, então a recém-chegada sobrevive mesmo se já estivesse na lista.
|
|
271
|
+
const releaseKeys = s.oneButton && gameKey ? [...s.heldKeys].filter((k) => isGameKeyCode(k, s)) : [];
|
|
272
|
+
return { kind: 'play', gameKey, wake, edges, releaseKeys, preventDefault: gameKey };
|
|
273
|
+
}
|
|
274
|
+
export function initKeydown(ctx) {
|
|
275
|
+
/** Diálogo aberto = elemento que existe E não está `hidden`. Verbatim do `dlgVis` do monólito, que nasceu
|
|
276
|
+
* justamente para uma flag presa em `true` não travar o teclado do jogo inteiro. */
|
|
277
|
+
const visible = (id) => { const el = ctx.$('#' + id); return !!el && !el.hidden; };
|
|
278
|
+
function snapshot() {
|
|
279
|
+
return {
|
|
280
|
+
telaDeTitulo: ctx.isTelaDeTitulo(), emJogo: ctx.isEmJogo(), numPlayers: ctx.getNumPlayers(), players: ctx.getPlayers(), controls: ctx.getControls(),
|
|
281
|
+
// A RESPOSTA, ao lado dos jogadores — e montada aqui, onde eles já estão na mão (ADR-0033).
|
|
282
|
+
modalOpen: ctx.getPlayers().map((_, i) => ctx.hasModal(i)),
|
|
283
|
+
heldKeys: ctx.heldKeys, oneButton: ctx.isOneButton(),
|
|
284
|
+
escapeTargetId: ctx.escapeTarget(),
|
|
285
|
+
touchCfgVisible: visible('touchcfg'), padWizVisible: visible('padwiz'), winVisible: visible('win-overlay'),
|
|
286
|
+
actionOf: ctx.actionOf, whichPlayer: ctx.whichPlayer,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/** A metade IMPURA: pega a decisão pronta e a carimba no mundo. */
|
|
290
|
+
function apply(d, code) {
|
|
291
|
+
switch (d.kind) {
|
|
292
|
+
case 'overlay':
|
|
293
|
+
if (d.closeId)
|
|
294
|
+
ctx.closeOverlayById(d.closeId);
|
|
295
|
+
return;
|
|
296
|
+
case 'touchcfg': {
|
|
297
|
+
if (!d.close)
|
|
298
|
+
return;
|
|
299
|
+
const t = ctx.$('#touchcfg');
|
|
300
|
+
if (t)
|
|
301
|
+
t.hidden = true;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
case 'padwiz':
|
|
305
|
+
if (d.close)
|
|
306
|
+
ctx.closePadWiz(false);
|
|
307
|
+
return; // wizard de gamepad: Esc CANCELA (não salva)
|
|
308
|
+
case 'win': {
|
|
309
|
+
if (!d.again)
|
|
310
|
+
return;
|
|
311
|
+
const b = ctx.$('#btn-again');
|
|
312
|
+
if (b)
|
|
313
|
+
b.click();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case 'title':
|
|
317
|
+
ctx.hideTouchControls(); // teclado no splash oculta os controles virtuais — ANTES do aviso, verbatim
|
|
318
|
+
if (d.wait) {
|
|
319
|
+
ctx.srSay(t('sr.title.waitP1'));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (d.nav)
|
|
323
|
+
ctx.navTitle(d.nav);
|
|
324
|
+
return;
|
|
325
|
+
case 'screens':
|
|
326
|
+
ctx.activateScreens(d.count);
|
|
327
|
+
return;
|
|
328
|
+
case 'pause':
|
|
329
|
+
ctx.togglePause();
|
|
330
|
+
return;
|
|
331
|
+
case 'modal': {
|
|
332
|
+
// A BUSCA DO JOGADOR SAIU DAQUI: este módulo a fazia só para repassar o objeto, e o objeto que ele
|
|
333
|
+
// sabia descrever não tinha `quiz` — que é justamente o que o outro lado precisa ler. Passa o índice.
|
|
334
|
+
if (!d.intent)
|
|
335
|
+
return;
|
|
336
|
+
ctx.modalInput(d.playerIndex, d.intent);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
case 'play': {
|
|
340
|
+
const players = ctx.getPlayers();
|
|
341
|
+
if (d.gameKey)
|
|
342
|
+
ctx.hideTouchControls('teclado'); // E13: jogar no teclado oculta os botões de toque
|
|
343
|
+
for (const idx of d.wake) { // a tecla DAQUELE jogador ativa a tela em espera
|
|
344
|
+
const p = players[idx];
|
|
345
|
+
if (!p)
|
|
346
|
+
continue;
|
|
347
|
+
p.waiting = false;
|
|
348
|
+
ctx.clearWaitingBadge(p.i);
|
|
349
|
+
ctx.srSay(t('sr.player.entered', { n: p.i + 1 }));
|
|
350
|
+
}
|
|
351
|
+
for (const { playerIndex, edge } of d.edges) {
|
|
352
|
+
const p = players[playerIndex];
|
|
353
|
+
if (p)
|
|
354
|
+
p[edge] = true;
|
|
355
|
+
}
|
|
356
|
+
for (const k of d.releaseKeys)
|
|
357
|
+
ctx.heldKeys.delete(k);
|
|
358
|
+
ctx.heldKeys.add(code);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function onKeydown(e) {
|
|
364
|
+
if (ctx.attractOnInput()) {
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
return;
|
|
367
|
+
} // qualquer tecla encerra a demo
|
|
368
|
+
if (ctx.handleCaptureKeydown(e))
|
|
369
|
+
return; // remap: a próxima tecla vira o controle
|
|
370
|
+
const d = decideKeydown(e, snapshot());
|
|
371
|
+
if (d.preventDefault)
|
|
372
|
+
e.preventDefault();
|
|
373
|
+
apply(d, e.code);
|
|
374
|
+
}
|
|
375
|
+
function onKeyup(e) { ctx.heldKeys.delete(e.code); }
|
|
376
|
+
function attach() {
|
|
377
|
+
ctx.win.addEventListener('keydown', onKeydown);
|
|
378
|
+
ctx.win.addEventListener('keyup', onKeyup);
|
|
379
|
+
}
|
|
380
|
+
return { snapshot, onKeydown, onKeyup, attach };
|
|
381
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** Sentido travado: -1 esquerda, 0 parado, 1 direita. */
|
|
3
|
+
export type LatchDir = -1 | 0 | 1;
|
|
4
|
+
/** Fração da velocidade de caminhada quando o jogador SEGURA o botão no sentido travado. */
|
|
5
|
+
export declare const LATCH_HELD: number;
|
|
6
|
+
/** Fração quando a direção está travada mas nenhum botão está pressionado — o "andar sozinho". */
|
|
7
|
+
export declare const LATCH_IDLE: number;
|
|
8
|
+
/**
|
|
9
|
+
* O próximo sentido travado, dado o atual e as bordas de toque deste quadro.
|
|
10
|
+
*
|
|
11
|
+
* Um toque no sentido em que já se anda PARA (é o mesmo botão que liga e desliga — não há botão de parar, e
|
|
12
|
+
* inventar um custaria a quem tem um dedo só o dobro de alcances). Um toque no sentido oposto inverte
|
|
13
|
+
* direto, sem passar pelo zero: quem toca "esquerda" andando para a direita quer ir para a esquerda, não
|
|
14
|
+
* quer parar e tocar de novo.
|
|
15
|
+
*
|
|
16
|
+
* As duas bordas no MESMO quadro são possíveis (dois dedos, ou um switch duplo mal calibrado) e a ordem
|
|
17
|
+
* importa: esquerda é avaliada primeiro e direita depois, então direita vence. É o comportamento que a
|
|
18
|
+
* física sempre teve — duas linhas `if` em sequência —, preservado aqui de propósito e agora com um teste que
|
|
19
|
+
* o fixa, em vez de ser um efeito não intencional da ordem em que alguém escreveu dois `if`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function nextLatchedDir(current: LatchDir, leftEdge: boolean, rightEdge: boolean): LatchDir;
|
|
22
|
+
/**
|
|
23
|
+
* O multiplicador da velocidade de caminhada, COM sinal: o sentido travado vezes a fração que corresponde a
|
|
24
|
+
* estar ou não segurando o botão daquele sentido.
|
|
25
|
+
*
|
|
26
|
+
* Devolve o produto (e não só a fração) porque parado é 0 e nenhuma fração precisa opinar sobre isso —
|
|
27
|
+
* exatamente como a física fazia ao multiplicar por `dir`. Quem chama multiplica por `TUNE.hWalk`; a
|
|
28
|
+
* constante de ajuste do jogo não entra neste módulo.
|
|
29
|
+
*/
|
|
30
|
+
export declare function latchedDrive(dir: LatchDir, holdingLeft: boolean, holdingRight: boolean): number;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/latch — SEGURAR VIRA ALTERNAR. Passo 2 do ADR-0027, metade da "entrada como dado".
|
|
3
|
+
//
|
|
4
|
+
// O QUE É. Quem não consegue MANTER um botão pressionado — espasticidade, tremor, fadiga, um único dedo, um
|
|
5
|
+
// switch de sopro — não perde o jogo: um TOQUE trava a direção, e o personagem segue andando sozinho. Tocar de
|
|
6
|
+
// novo no mesmo sentido para; tocar no sentido oposto inverte. Segurar, para quem consegue, acelera.
|
|
7
|
+
//
|
|
8
|
+
// POR QUE SAIU DA FÍSICA. A política morava dentro de `game/physics.ts`, no meio do cálculo do movimento
|
|
9
|
+
// horizontal. Três consequências, todas ruins:
|
|
10
|
+
// 1. Uma decisão de ENTRADA estava escrita como se fosse uma regra do jogo, num arquivo que ninguém abre
|
|
11
|
+
// para mexer em acessibilidade motora.
|
|
12
|
+
// 2. Só o movimento horizontal tinha caminho de alternância. Pulo, corrida e troca de poder não têm — e não
|
|
13
|
+
// por decisão, por acidente de onde o código foi parar. Este módulo é onde essa lacuna se fecha quando
|
|
14
|
+
// for fechada; NÃO a fecho agora, porque cada ação precisa da sua própria resposta de desenho (o que
|
|
15
|
+
// significa "pulo travado"?) e inventar isso sem o Dev seria pior que a lacuna.
|
|
16
|
+
// 3. A engine não podia oferecer a alternância a OUTRO jogo sem levar a física do plataforma junto.
|
|
17
|
+
//
|
|
18
|
+
// DE QUE LADO DA LINHA ISTO CAI. O ADR-0027 separa PRESENTAÇÃO (como o estado vira pixel e som), OPERAÇÃO
|
|
19
|
+
// (como a intenção vira entrada) e REGRA (o que é alcançável). O critério objetivo: uma adaptação cruza a
|
|
20
|
+
// linha quando altera o CONJUNTO DE ESTADOS ALCANÇÁVEIS ou a PROBABILIDADE de alcançá-los. A alternância não
|
|
21
|
+
// altera nem um nem outro — o jogador chega exatamente aos mesmos lugares, gastando menos motricidade. É
|
|
22
|
+
// OPERAÇÃO, e por isso a engine a oferece e o jogo não pode recusar. (O modo Fácil, no mesmo botão hoje, é
|
|
23
|
+
// REGRA: hitbox de +4px, proteção de beirada e gravidade 2/3 mudam o que é alcançável. Não estão aqui.)
|
|
24
|
+
//
|
|
25
|
+
// PURO DE PROPÓSITO: sem `document`, sem PIXI, sem `TUNE`. Recebe números e devolve números, roda no project
|
|
26
|
+
// `node` do Vitest, e não sabe que existe um plataforma do outro lado.
|
|
27
|
+
/** Fração da velocidade de caminhada quando o jogador SEGURA o botão no sentido travado. */
|
|
28
|
+
export const LATCH_HELD = 2 / 3;
|
|
29
|
+
/** Fração quando a direção está travada mas nenhum botão está pressionado — o "andar sozinho". */
|
|
30
|
+
export const LATCH_IDLE = 1 / 3;
|
|
31
|
+
/**
|
|
32
|
+
* O próximo sentido travado, dado o atual e as bordas de toque deste quadro.
|
|
33
|
+
*
|
|
34
|
+
* Um toque no sentido em que já se anda PARA (é o mesmo botão que liga e desliga — não há botão de parar, e
|
|
35
|
+
* inventar um custaria a quem tem um dedo só o dobro de alcances). Um toque no sentido oposto inverte
|
|
36
|
+
* direto, sem passar pelo zero: quem toca "esquerda" andando para a direita quer ir para a esquerda, não
|
|
37
|
+
* quer parar e tocar de novo.
|
|
38
|
+
*
|
|
39
|
+
* As duas bordas no MESMO quadro são possíveis (dois dedos, ou um switch duplo mal calibrado) e a ordem
|
|
40
|
+
* importa: esquerda é avaliada primeiro e direita depois, então direita vence. É o comportamento que a
|
|
41
|
+
* física sempre teve — duas linhas `if` em sequência —, preservado aqui de propósito e agora com um teste que
|
|
42
|
+
* o fixa, em vez de ser um efeito não intencional da ordem em que alguém escreveu dois `if`.
|
|
43
|
+
*/
|
|
44
|
+
export function nextLatchedDir(current, leftEdge, rightEdge) {
|
|
45
|
+
let dir = current;
|
|
46
|
+
if (leftEdge)
|
|
47
|
+
dir = dir === -1 ? 0 : -1;
|
|
48
|
+
if (rightEdge)
|
|
49
|
+
dir = dir === 1 ? 0 : 1;
|
|
50
|
+
return dir;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* O multiplicador da velocidade de caminhada, COM sinal: o sentido travado vezes a fração que corresponde a
|
|
54
|
+
* estar ou não segurando o botão daquele sentido.
|
|
55
|
+
*
|
|
56
|
+
* Devolve o produto (e não só a fração) porque parado é 0 e nenhuma fração precisa opinar sobre isso —
|
|
57
|
+
* exatamente como a física fazia ao multiplicar por `dir`. Quem chama multiplica por `TUNE.hWalk`; a
|
|
58
|
+
* constante de ajuste do jogo não entra neste módulo.
|
|
59
|
+
*/
|
|
60
|
+
export function latchedDrive(dir, holdingLeft, holdingRight) {
|
|
61
|
+
const holding = (dir === -1 && holdingLeft) || (dir === 1 && holdingRight);
|
|
62
|
+
return dir * (holding ? LATCH_HELD : LATCH_IDLE);
|
|
63
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** Retângulo do elemento (o que `getBoundingClientRect()` entrega, reduzido ao que a conta usa). */
|
|
3
|
+
export interface RectLike {
|
|
4
|
+
left: number;
|
|
5
|
+
top: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
/** Um ponto em px, relativo ao CENTRO do elemento. É a forma que um direcional lê. */
|
|
10
|
+
export interface DoCentro {
|
|
11
|
+
dx: number;
|
|
12
|
+
dy: number;
|
|
13
|
+
}
|
|
14
|
+
/** Um ponto em FRAÇÃO do elemento: `0,0` é o canto superior esquerdo e `1,1` o inferior direito. */
|
|
15
|
+
export interface EmFracao {
|
|
16
|
+
fx: number;
|
|
17
|
+
fy: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* O ponto relativo ao CENTRO, em px do elemento.
|
|
21
|
+
*
|
|
22
|
+
* As três funções de direcional de `touch-bindings` abriam com estas mesmas duas linhas. Repetição de duas
|
|
23
|
+
* linhas não costuma pagar uma extração — esta paga por outra razão: enquanto a conta do centro estava
|
|
24
|
+
* escrita dentro de cada uma, não havia como um consumidor NOVO (um ponteiro, um olhar) obter o ponto sem
|
|
25
|
+
* passar por uma função que já o transformou em direções.
|
|
26
|
+
*/
|
|
27
|
+
export declare function doCentro(px: number, py: number, rect: RectLike): DoCentro;
|
|
28
|
+
/**
|
|
29
|
+
* O ponto como FRAÇÃO do elemento.
|
|
30
|
+
*
|
|
31
|
+
* ⚠️ NÃO SATURA EM 0..1 de propósito. Um ponteiro capturado sai do elemento e continua a valer — é isso que
|
|
32
|
+
* `setPointerCapture` existe para permitir, e saturar aqui apagaria a diferença entre «na borda» e «muito
|
|
33
|
+
* para lá da borda», que é justamente o que um arrasto precisa de saber. Quem quiser saturar, satura; quem
|
|
34
|
+
* saturasse aqui não teria como voltar atrás.
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ E LARGURA ZERO DEVOLVE ZERO em vez de `Infinity`. Um elemento ainda não medido (display:none, primeiro
|
|
37
|
+
* quadro) dá `width: 0`, e um `NaN`/`Infinity` a partir daqui viajaria para dentro da física antes de alguém
|
|
38
|
+
* o ver. O `onGaze` já se protegia disso com um `if (!r.width) return`; a proteção passa a ser da conta.
|
|
39
|
+
*/
|
|
40
|
+
export declare function emFracao(px: number, py: number, rect: RectLike): EmFracao;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/pointer-space.ts — O ÚNICO LUGAR QUE CONVERTE UM PONTO DE TELA. Módulo-folha: só aritmética.
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE A MEDIÇÃO DA ISSUE #105 ENCONTROU =========================
|
|
5
|
+
// A issue manda LER antes de escrever — *"estabeleça quanto de um ponteiro já existe dentro de
|
|
6
|
+
// `input/touch.ts`; se a resposta for «quase tudo», isto vira uma extração e não um subsistema novo"*. A
|
|
7
|
+
// resposta medida é mais interessante do que «quase tudo» ou «quase nada»:
|
|
8
|
+
//
|
|
9
|
+
// · A CAPTURA já existe INTEIRA, em `touch-bindings.wirePointerPad`: `pointerdown`/`move`/`up`/`cancel`,
|
|
10
|
+
// `setPointerCapture` com recuo gracioso, `lostpointercapture` como rede, `contextmenu` suprimido, e um
|
|
11
|
+
// ponteiro de cada vez. Nada disso precisa de ser escrito outra vez.
|
|
12
|
+
// · O PONTO, ao contrário, é RECEBIDO E DEITADO FORA — e em quatro lugares, com duas convenções:
|
|
13
|
+
// `crossDirsAt`, `stickDirsAt` e `stickKnobOffset` reduzem-no a `dx,dy` do CENTRO e devolvem direções;
|
|
14
|
+
// `ui/webcam.onGaze` reduz o olhar a uma FRAÇÃO do elemento e a três limiares, emitindo teclas.
|
|
15
|
+
//
|
|
16
|
+
// ⚠️ E É O MESMO DEFEITO NOS DOIS SÍTIOS: uma posição contínua chega, é comprimida em «esquerda/direita/cima»
|
|
17
|
+
// antes de qualquer outro consumidor a ver, e não sobra nada para quem precisasse dela. O ponteiro não é
|
|
18
|
+
// fundação nova — é a fundação que o OLHAR já precisava e nunca teve.
|
|
19
|
+
//
|
|
20
|
+
// ⚠️ E O QUE NÃO EXISTE EM LADO NENHUM: hover (posição sem aperto), botões (`PointerLike` não tem `button`),
|
|
21
|
+
// roda (zero ocorrências) e — o que a `definition of done` da #105 pede em primeiro lugar — UM lugar que
|
|
22
|
+
// converta tela→jogo. Este ficheiro é esse lugar; o resto continua aberto na issue.
|
|
23
|
+
/**
|
|
24
|
+
* O ponto relativo ao CENTRO, em px do elemento.
|
|
25
|
+
*
|
|
26
|
+
* As três funções de direcional de `touch-bindings` abriam com estas mesmas duas linhas. Repetição de duas
|
|
27
|
+
* linhas não costuma pagar uma extração — esta paga por outra razão: enquanto a conta do centro estava
|
|
28
|
+
* escrita dentro de cada uma, não havia como um consumidor NOVO (um ponteiro, um olhar) obter o ponto sem
|
|
29
|
+
* passar por uma função que já o transformou em direções.
|
|
30
|
+
*/
|
|
31
|
+
export function doCentro(px, py, rect) {
|
|
32
|
+
return { dx: px - (rect.left + rect.width / 2), dy: py - (rect.top + rect.height / 2) };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* O ponto como FRAÇÃO do elemento.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ NÃO SATURA EM 0..1 de propósito. Um ponteiro capturado sai do elemento e continua a valer — é isso que
|
|
38
|
+
* `setPointerCapture` existe para permitir, e saturar aqui apagaria a diferença entre «na borda» e «muito
|
|
39
|
+
* para lá da borda», que é justamente o que um arrasto precisa de saber. Quem quiser saturar, satura; quem
|
|
40
|
+
* saturasse aqui não teria como voltar atrás.
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ E LARGURA ZERO DEVOLVE ZERO em vez de `Infinity`. Um elemento ainda não medido (display:none, primeiro
|
|
43
|
+
* quadro) dá `width: 0`, e um `NaN`/`Infinity` a partir daqui viajaria para dentro da física antes de alguém
|
|
44
|
+
* o ver. O `onGaze` já se protegia disso com um `if (!r.width) return`; a proteção passa a ser da conta.
|
|
45
|
+
*/
|
|
46
|
+
export function emFracao(px, py, rect) {
|
|
47
|
+
return {
|
|
48
|
+
fx: rect.width ? (px - rect.left) / rect.width : 0,
|
|
49
|
+
fy: rect.height ? (py - rect.top) / rect.height : 0,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { ControlledPlayer } from '../core/entity.js';
|
|
3
|
+
export declare const keys: Set<string>;
|
|
4
|
+
type PadState = Record<string, boolean>;
|
|
5
|
+
export declare const padCur: Record<number, PadState>;
|
|
6
|
+
export declare const padPrevAct: Record<number, PadState>;
|
|
7
|
+
export declare const padPrevStart: Record<number, boolean>;
|
|
8
|
+
export declare const PAD_DEAD = 0.5;
|
|
9
|
+
type HeldPlayer = Pick<ControlledPlayer, 'ctrl' | 'pad'>;
|
|
10
|
+
export declare const held: (pl: HeldPlayer, act: string) => boolean;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/state.ts — ESTADO de input em runtime + query genérica, compartilhado pelos handlers (que ficam no
|
|
3
|
+
// game.js e mutam estes objetos IN-PLACE): teclas seguradas (keys), estado de gamepad por controle
|
|
4
|
+
// (padCur/padPrevAct/padPrevStart) e a zona morta do analógico (PAD_DEAD). held(pl,act) = o jogador está
|
|
5
|
+
// segurando a ação, por teclado (pl.ctrl) OU pelo gamepad associado (pl.pad). Módulo-folha, ZERO deps. (Fase 2.22)
|
|
6
|
+
export const keys = new Set();
|
|
7
|
+
export const padCur = {};
|
|
8
|
+
export const padPrevAct = {};
|
|
9
|
+
export const padPrevStart = {};
|
|
10
|
+
export const PAD_DEAD = 0.5; // zona morta = primeira METADE do curso do analógico (ergonomia — José 2026-07-02)
|
|
11
|
+
// Jogador está segurando a ação? teclado (algum code do esquema pl.ctrl) OU o gamepad associado (pl.pad).
|
|
12
|
+
export const held = (pl, act) => pl.ctrl[act].some((k) => keys.has(k)) || (pl.pad >= 0 && !!padCur[pl.pad]?.[act]);
|