@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,210 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type RectLike } from './pointer-space.js';
|
|
3
|
+
import type { PlayerView } from '../core/entity.js';
|
|
4
|
+
import type { DomQuery } from '../core/dom-query.js';
|
|
5
|
+
import type { KeyScheme } from '../core/entity.js';
|
|
6
|
+
export type { DomQuery } from '../core/dom-query.js';
|
|
7
|
+
/** ação -> lista de códigos físicos. Cópia ESTRUTURAL do `KeyScheme` de `input/keyboard-runtime.ts` — a casa
|
|
8
|
+
* prefere a cópia a puxar um alias de tipo através de camadas (mesmo precedente de `input/keydown.ts`). */
|
|
9
|
+
export type { KeyScheme } from '../core/entity.js';
|
|
10
|
+
/** As seis bordas de entrada que o toque levanta no jogador (consumidas e zeradas pela física). */
|
|
11
|
+
export type EdgeFlag = 'jumpEdge' | 'runEdge' | 'leftEdge' | 'rightEdge' | 'swapEdge' | 'specialEdge';
|
|
12
|
+
/** Uma borda a levantar: qual jogador (POSIÇÃO no array) e qual flag. */
|
|
13
|
+
export interface EdgeRaise {
|
|
14
|
+
playerIndex: number;
|
|
15
|
+
edge: EdgeFlag;
|
|
16
|
+
}
|
|
17
|
+
/** O que este módulo lê (e escreve) de um jogador — e SÓ isso. DERIVADA de core/entity.
|
|
18
|
+
* `easy`: quem está no modo Fácil não levanta `runEdge`, e portanto não gruda na parede — a mesma regra do
|
|
19
|
+
* teclado e do controle, que é justamente a que divergiu em três cópias uma vez. Ver o cabeçalho. */
|
|
20
|
+
export type TouchBindPlayer = PlayerView<'ctrl' | 'easy' | 'jumpEdge' | 'runEdge' | 'leftEdge' | 'rightEdge' | 'swapEdge' | 'specialEdge'>;
|
|
21
|
+
/** TUDO o que a decisão precisa saber do mundo, num objeto só, montado ANTES de qualquer efeito. */
|
|
22
|
+
export interface TouchBindSnapshot {
|
|
23
|
+
/** `kbRuntime.controlsState().controls` — o esquema do Jogador 1 (remapeável). É dele, e só dele, que sai a
|
|
24
|
+
* tecla que o botão da tela injeta: o toque é sempre o J1, mesmo em multi-tela. */
|
|
25
|
+
controls: KeyScheme;
|
|
26
|
+
/** o array vivo de jogadores: as bordas são levantadas em TODOS que tenham o código no próprio esquema. */
|
|
27
|
+
players: readonly TouchBindPlayer[];
|
|
28
|
+
/** `keys` de `input/state.ts`: as teclas seguradas AGORA (teclado, toque e webcam, misturados). */
|
|
29
|
+
heldKeys: ReadonlySet<string>;
|
|
30
|
+
}
|
|
31
|
+
/** Reexportado: a definicao passou a viver em `input/pointer-space`, com a conta que a usa (issue #105). */
|
|
32
|
+
export type { RectLike } from './pointer-space.js';
|
|
33
|
+
/** As quatro direções físicas do direcional, ligadas ou não. */
|
|
34
|
+
export interface DirSet {
|
|
35
|
+
left: boolean;
|
|
36
|
+
right: boolean;
|
|
37
|
+
up: boolean;
|
|
38
|
+
down: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Um evento de ponteiro, reduzido ao que este módulo lê (`PointerEvent` real é atribuível a isto). */
|
|
41
|
+
export interface PointerLike {
|
|
42
|
+
pointerId: number;
|
|
43
|
+
clientX: number;
|
|
44
|
+
clientY: number;
|
|
45
|
+
preventDefault(): void;
|
|
46
|
+
}
|
|
47
|
+
/** A resposta única: o que este toque SIGNIFICA. */
|
|
48
|
+
export type TouchDecision =
|
|
49
|
+
/** ação sem tecla mapeada, slot vazio, ou SOLTAR de `pause` — nada acontece. */
|
|
50
|
+
{
|
|
51
|
+
kind: 'noop';
|
|
52
|
+
}
|
|
53
|
+
/** `pause` é o único caso especial: não vira tecla, chama `togglePause()` direto (e só no APERTAR). */
|
|
54
|
+
| {
|
|
55
|
+
kind: 'pause';
|
|
56
|
+
}
|
|
57
|
+
/** apertar. `addKey=false` = a tecla JÁ estava segurada (outro dedo, ou o teclado): não re-injeta nem
|
|
58
|
+
* levanta borda de novo — é isso que faz disto uma BORDA e não um estado. `hideTips` é independente. */
|
|
59
|
+
| {
|
|
60
|
+
kind: 'press';
|
|
61
|
+
code: string;
|
|
62
|
+
addKey: boolean;
|
|
63
|
+
edges: EdgeRaise[];
|
|
64
|
+
hideTips: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** soltar: some com o código do conjunto. NÃO abaixa borda nenhuma (quem zera as bordas é a física). */
|
|
67
|
+
| {
|
|
68
|
+
kind: 'release';
|
|
69
|
+
code: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Ação → borda, na MESMA ordem das outras duas cópias do projeto.
|
|
73
|
+
* ⚠️ TERCEIRA CÓPIA DE UMA TABELA QUE JÁ EXISTE DUAS VEZES — `input/keydown.ts` (`EDGE_BY_ACTION`) e
|
|
74
|
+
* `input/gamepad.ts` (seis `if` dentro de `pollPads`). Ver o bloco de aviso no cabeçalho: as três não
|
|
75
|
+
* concordam sobre o modo Fácil, e a divergência está PRESERVADA de propósito.
|
|
76
|
+
*/
|
|
77
|
+
export { EDGE_BY_ACTION as TOUCH_EDGE_BY_ACTION } from './edges.js';
|
|
78
|
+
/** `?touch=1` na URL força os controles de toque a aparecerem no desktop (atalho de teste do José). */
|
|
79
|
+
export declare const TOUCH_FORCE_RE: RegExp;
|
|
80
|
+
/** Predicado do `?touch=1`, isolado para o teste não repetir o regex. */
|
|
81
|
+
export declare function wantsForcedTouch(search: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Que TECLA este botão de tela finge apertar? A 1ª do esquema do Jogador 1 para aquela ação — 1ª, e não
|
|
84
|
+
* todas, porque só uma precisa entrar em `keys` para o jogo inteiro reagir. Remapear o teclado remapeia o
|
|
85
|
+
* toque junto, de graça: é a mesma tabela.
|
|
86
|
+
* `null` = a ação não existe no esquema (ou o slot do mapa de toque está vazio) → o toque não faz nada.
|
|
87
|
+
*/
|
|
88
|
+
export declare function codeForAction(act: string | undefined | null, controls: KeyScheme): string | null;
|
|
89
|
+
/**
|
|
90
|
+
* As bordas que este toque levanta, e em quem.
|
|
91
|
+
*
|
|
92
|
+
* ⚠️ SEM A GUARDA DO MODO FÁCIL — VERBATIM DO main.js, DEFEITO PRESERVADO.
|
|
93
|
+
* `input/keydown.ts:edgesFor` pula `run` quando `p.easy` ("Fácil: sem correr"); `input/gamepad.ts:pollPads`
|
|
94
|
+
* também (`if (edge('run') && !p.easy)`). Este caminho NÃO pula. Como `runEdge` é o gatilho de grudar/soltar
|
|
95
|
+
* da parede (`game/physics.ts:177` e `:179`), e não a velocidade de corrida, o efeito prático é que no modo
|
|
96
|
+
* Fácil a escalada existe pelo botão da tela e não existe pelo teclado nem pelo controle.
|
|
97
|
+
* NÃO acrescente a guarda aqui sem consertar as três cópias de uma vez — ver o cabeçalho.
|
|
98
|
+
*
|
|
99
|
+
* ÚNICO DESVIO DE LITERALIDADE, declarado: `p.ctrl[a] || []`. O original alcançava `p.ctrl.jump`/`.run`/
|
|
100
|
+
* `.left`/`.right` DIRETO (e estouraria num esquema sem a ação), enquanto guardava `swap` e `especial` com
|
|
101
|
+
* `&&` — assimetria sem intenção. Trocar um TypeError por um no-op não remove rede de conserto nenhuma; é o
|
|
102
|
+
* mesmo precedente que `input/keydown.ts:edgesFor` já abriu, e está anotado lá pelo mesmo motivo.
|
|
103
|
+
*/
|
|
104
|
+
export declare function touchEdgesFor(act: string, code: string, players: readonly TouchBindPlayer[]): EdgeRaise[];
|
|
105
|
+
/**
|
|
106
|
+
* A DECISÃO. Gesto (ação mapeada + apertar/soltar) + mundo → o que isso significa.
|
|
107
|
+
* `pause` primeiro, porque é o único que não passa pelo teclado sintético. Depois a tecla; sem tecla, nada.
|
|
108
|
+
*/
|
|
109
|
+
export declare function decideTouch(act: string | undefined | null, on: boolean, s: TouchBindSnapshot): TouchDecision;
|
|
110
|
+
/** Zona morta da CRUZ: ~18% do LADO a partir do centro, em cada eixo. Ver a nota de `crossDirsAt` sobre o
|
|
111
|
+
* `width` valer também para o eixo vertical — é verbatim, e é uma assimetria de verdade. */
|
|
112
|
+
export declare const CROSS_DEAD_FRACTION = 0.18;
|
|
113
|
+
/**
|
|
114
|
+
* Cruz (D-pad): onde o dedo está → quais direções ligam. Miolo neutro para não disparar por encostar no meio.
|
|
115
|
+
* As quatro comparações são INDEPENDENTES: fora do miolo, uma diagonal liga DUAS direções (é o que dá o
|
|
116
|
+
* comportamento de D-pad físico de 8 setores). O centro exato não liga nenhuma.
|
|
117
|
+
*
|
|
118
|
+
* VERBATIM, incluindo a assimetria: a zona morta dos DOIS eixos sai de `rect.width` — a altura nunca entra na
|
|
119
|
+
* conta. Numa cruz quadrada (é o que o CSS `--dpad-span` produz) dá no mesmo; numa cruz achatada, o miolo
|
|
120
|
+
* vertical ficaria proporcionalmente maior ou menor que o horizontal. Anotado, não consertado.
|
|
121
|
+
*/
|
|
122
|
+
export declare function crossDirsAt(px: number, py: number, rect: RectLike): DirSet;
|
|
123
|
+
/**
|
|
124
|
+
* Analógico virtual: mesma regra da cruz, mas a zona morta vem em PX de `input/touch.ts` (derivada dos mm
|
|
125
|
+
* configuráveis — A12e motora), não de uma fração do elemento.
|
|
126
|
+
*/
|
|
127
|
+
export declare function stickDirsAt(px: number, py: number, rect: RectLike, deadPx: number): DirSet;
|
|
128
|
+
/**
|
|
129
|
+
* Para onde a manopla do analógico anda (px, relativo ao centro da base): segue o dedo até o limite do curso
|
|
130
|
+
* `travelPx` e ali PARA, mantendo o ângulo (recorte radial, não por eixo — por isso `hypot`, e não `clamp`).
|
|
131
|
+
* O `|| 1` do original evita divisão por zero quando o dedo cai no centro exato.
|
|
132
|
+
*/
|
|
133
|
+
export declare function stickKnobOffset(px: number, py: number, rect: RectLike, travelPx: number): {
|
|
134
|
+
x: number;
|
|
135
|
+
y: number;
|
|
136
|
+
};
|
|
137
|
+
/** Só o que `attach()` precisa da janela. `(e: never)` é o mesmo truque de `input/keydown.ts`: o handler é
|
|
138
|
+
* convertido no ponto de registro, e o alvo real (window) satisfaz isto de sobra. */
|
|
139
|
+
/**
|
|
140
|
+
* A porta de ESCUTA de eventos, e ela é genérica sobre o mapa de eventos porque é isso que o `window` é.
|
|
141
|
+
*
|
|
142
|
+
* ⚠️ ELA DIZIA `fn: (e: never) => void`, E ESTAVA ERRADA — de um jeito que já tinha cobrado duas vezes. Um
|
|
143
|
+
* parâmetro `never` parece dizer "o ouvinte não olha o evento", mas por contravariância ele exige que o
|
|
144
|
+
* ALVO aceite qualquer coisa, e o `window` real declara `ev: any`, que não é atribuível a `never`. O
|
|
145
|
+
* resultado era o pior dos dois lados: um `as (e: never) => void` em CADA registro dentro da engine, e um
|
|
146
|
+
* adaptador de uma linha em CADA consumidor. O `boot/create-game` escrevia esse adaptador (Achado 12) e o
|
|
147
|
+
* `consumer-quiz/main-quiz` registrou por escrito que "a engine pede uma forma de `window` que o `window`
|
|
148
|
+
* não tem".
|
|
149
|
+
*
|
|
150
|
+
* Genérica sobre `WindowEventMap`, o `window` a satisfaz DIRETO e cada ouvinte recebe o evento certo:
|
|
151
|
+
* `'keydown'` casa com `KeyboardEvent`, `'pointerdown'` com `PointerEvent`. Sem cast e sem adaptador.
|
|
152
|
+
*
|
|
153
|
+
* `WindowEventMap` é global de `lib.dom`, ligado no `tsconfig` — não há import a fazer, e portanto não há
|
|
154
|
+
* aresta nova de dependência.
|
|
155
|
+
*/
|
|
156
|
+
export interface EventTargetLike {
|
|
157
|
+
addEventListener<K extends keyof WindowEventMap>(type: K, fn: (e: WindowEventMap[K]) => void, opts?: boolean | {
|
|
158
|
+
capture?: boolean;
|
|
159
|
+
passive?: boolean;
|
|
160
|
+
}): void;
|
|
161
|
+
}
|
|
162
|
+
/** Quanto tempo o START segura a ação, quando ela é momentânea (não é `pause`). */
|
|
163
|
+
export declare const START_TAP_MS = 140;
|
|
164
|
+
export interface TouchBindingsCtx {
|
|
165
|
+
/** `ui/dom.ts` `$` — injetado; o módulo nunca alcança `document`. */
|
|
166
|
+
$: DomQuery;
|
|
167
|
+
/** `window`, só para os dois ouvintes globais de "houve um dedo/ponteiro". */
|
|
168
|
+
win: EventTargetLike;
|
|
169
|
+
/** `location.search` — lido por getter para o teste poder mentir sem mexer na URL. */
|
|
170
|
+
getSearch: () => string;
|
|
171
|
+
/** `kbRuntime.controlsState().controls` (memorizado do lado de lá) — o esquema do J1, lido a cada toque. */
|
|
172
|
+
getControls: () => KeyScheme;
|
|
173
|
+
/** o array vivo de jogadores. Getter: o main.js o repovoa a cada `restartGame`. */
|
|
174
|
+
getPlayers: () => readonly TouchBindPlayer[];
|
|
175
|
+
/** `keys` de `input/state.ts`: `const` mutado in place → entra por VALOR (é sempre o mesmo objeto). */
|
|
176
|
+
heldKeys: Set<string>;
|
|
177
|
+
/** `game/attract.ts`: zera a ociosidade e encerra a demo; `true` = o toque foi só para acordar.
|
|
178
|
+
* LAZY obrigatoriamente — `attractCtl` é `const` declarado ABAIXO do ponto de init no main.js. */
|
|
179
|
+
attractOnInput: () => boolean;
|
|
180
|
+
/** `input/touch.ts`: o toque REVELA os botões (o `hideTouchControls` é do keydown, não daqui). */
|
|
181
|
+
showTouchControls: () => void;
|
|
182
|
+
/** main.js: hoje um stub vazio (as dicas de início saíram em 2026-07-04); o call-site é mantido verbatim. */
|
|
183
|
+
hideTips: () => void;
|
|
184
|
+
togglePause: () => void;
|
|
185
|
+
/** `input/touch.ts:getTouchMap()` — slot → ação, remapeável. Lido A CADA evento, verbatim: remapear no
|
|
186
|
+
* painel passa a valer no toque seguinte, sem re-amarrar ouvinte nenhum. */
|
|
187
|
+
getTouchMap: () => Record<string, string>;
|
|
188
|
+
/** A ação do slot `start`. VER O RELATO DA EXTRAÇÃO: no main.js a linha original é `touchMap.start`, e
|
|
189
|
+
* `touchMap` NÃO EXISTE naquele escopo — o botão START da tela está quebrado hoje. Este campo é o ponto
|
|
190
|
+
* exato onde esse defeito vive, isolado para poder ser consertado (ou não) numa linha só, com decisão. */
|
|
191
|
+
getStartAction: () => string | undefined;
|
|
192
|
+
/** `input/touch.ts`: curso e zona morta do analógico, em px, recalculados a cada `applyPadPhysical()`. */
|
|
193
|
+
getStickTravelPx: () => number;
|
|
194
|
+
getStickDeadPx: () => number;
|
|
195
|
+
/** `setTimeout` — injetável só para o teste do START não esperar 140 ms de relógio. */
|
|
196
|
+
defer?: (fn: () => void, ms: number) => void;
|
|
197
|
+
}
|
|
198
|
+
export interface TouchBindingsApi {
|
|
199
|
+
/** A decisão pura, já com o mundo de agora. Exposta para o teste comparar sem aplicar. */
|
|
200
|
+
decide: (act: string | undefined, on: boolean) => TouchDecision;
|
|
201
|
+
/** O despacho inteiro: decide e carimba no mundo. É o `doTouch` do main.js. */
|
|
202
|
+
doTouch: (act: string | undefined, on: boolean) => void;
|
|
203
|
+
/** O que o botão START faz num clique (pausar, ou apertar-e-soltar depois de `START_TAP_MS`). */
|
|
204
|
+
pressStart: () => void;
|
|
205
|
+
/** `window.__incl.showTouch` — revela os botões à força, para teste em desktop. */
|
|
206
|
+
revealForTests: () => void;
|
|
207
|
+
/** Amarra tudo: os dois ouvintes globais, os `.touch-btn`, o START, o analógico e a cruz. */
|
|
208
|
+
attach: () => void;
|
|
209
|
+
}
|
|
210
|
+
export declare function initTouchBindings(ctx: TouchBindingsCtx): TouchBindingsApi;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/touch-bindings.ts — AS AMARRAS DO CONTROLE POR TOQUE: o gesto vira TECLA.
|
|
3
|
+
//
|
|
4
|
+
// Este módulo é a outra metade de `input/touch.ts`. Aquele é o DONO DO PAD — a geometria física (mm→px), o
|
|
5
|
+
// painel de configuração, o mapa remapeável (`getTouchMap`), mostrar/esconder os botões. Ele sabe onde o
|
|
6
|
+
// polegar encosta e de que tamanho o alvo tem de ser. Não sabe o que acontece depois do toque.
|
|
7
|
+
// É isto aqui. E o que acontece depois do toque é uma coisa só, e surpreendente: o botão da tela **finge ser
|
|
8
|
+
// o teclado**. `press('jump')` não chama a física, não empurra o personagem, não fala com o jogo — ele
|
|
9
|
+
// descobre qual TECLA está mapeada para "pular" no esquema do Jogador 1, injeta esse código no `keys` de
|
|
10
|
+
// `input/state.ts` (o mesmo conjunto que o `keydown` alimenta) e levanta a borda `jumpEdge` em todo jogador
|
|
11
|
+
// que tenha aquele código no próprio esquema. O resto do jogo nunca fica sabendo que houve um dedo na tela.
|
|
12
|
+
//
|
|
13
|
+
// ======================= POR QUE UM ARQUIVO NOVO, E NÃO DENTRO DE input/touch.ts =======================
|
|
14
|
+
// Porque são duas perguntas diferentes, e a segunda depende de coisas que a primeira não conhece.
|
|
15
|
+
// `input/touch.ts` fecha sobre `$`, `store`, `root`, `viewport` — desenho e persistência. Este módulo precisa
|
|
16
|
+
// do ESQUEMA DE TECLAS vivo (`kbRuntime.controlsState()`), do ARRAY DE JOGADORES, do `keys` compartilhado, do
|
|
17
|
+
// `togglePause`, do `attract` e do `hideTips`: seis dependências que não têm nada a ver com desenhar um pad e
|
|
18
|
+
// que arrastariam metade do jogo para dentro de um módulo que hoje é quase todo geometria pura.
|
|
19
|
+
// A fronteira caiu, portanto, entre "onde o dedo está" (lá) e "o que o dedo significa" (aqui).
|
|
20
|
+
// FUNDIR DEPOIS? Na minha leitura, NÃO — mas se um dia se fundir, que seja como DOIS `init` no mesmo arquivo
|
|
21
|
+
// (`initTouch` e `initTouchBindings`), nunca como um `ctx` só: o `ctx` unificado teria quinze campos e o
|
|
22
|
+
// módulo passaria a ser "tudo que tem a ver com toque", que é um tema, não uma responsabilidade. O acoplamento
|
|
23
|
+
// real entre os dois é estreitíssimo e já está explícito no `ctx` daqui: `getTouchMap`, `getStickTravelPx`,
|
|
24
|
+
// `getStickDeadPx` e `showTouchControls` — quatro getters, todos de leitura.
|
|
25
|
+
//
|
|
26
|
+
// ======================= A ENTREGA: DECIDIR ≠ EXECUTAR =======================
|
|
27
|
+
// O bloco original (`touchSetup` do main.js) trançava três coisas num `const` de uma linha cada: a tradução
|
|
28
|
+
// gesto→tecla, a geometria do direcional e o amarrado de ouvintes. As duas primeiras são PURAS e aqui estão
|
|
29
|
+
// separadas, no mesmo movimento que `input/keydown.ts` fez com `decideKeydown`:
|
|
30
|
+
// · `decideTouch(acao, ligado, snapshot) -> TouchDecision` — dado (ação, esquema do J1, esquemas dos
|
|
31
|
+
// jogadores, teclas já seguradas), existe UMA resposta. Sem DOM, sem ponteiro, sem `window`. Roda no
|
|
32
|
+
// project `node`. É onde a divergência do modo Fácil (abaixo) deixa de ser observável só num tablet.
|
|
33
|
+
// · `crossDirsAt` / `stickDirsAt` / `stickKnobOffset` — posição do ponteiro + retângulo do elemento →
|
|
34
|
+
// conjunto de direções ligadas. Zona morta, quadrantes e a borda da zona morta viram casos de teste.
|
|
35
|
+
// · `initTouchBindings(ctx).attach()` — a metade IMPURA: captura de ponteiro, `preventDefault`, `classList`,
|
|
36
|
+
// `setTimeout`. É a única parte que precisa de navegador de verdade.
|
|
37
|
+
//
|
|
38
|
+
// ======================= ⚠️ A TABELA "AÇÃO → BORDA" EXISTE TRÊS VEZES, E UMA DELAS DISCORDA =======================
|
|
39
|
+
// A mesma correspondência entre as seis ações e as seis flags de borda está escrita em três lugares:
|
|
40
|
+
// 1. `input/keydown.ts` (`EDGE_BY_ACTION`, tabela congelada; consumida em `edgesFor`, que aplica a guarda
|
|
41
|
+
// `if (act === 'run' && p.easy) continue; // Fácil: sem correr`);
|
|
42
|
+
// 2. `input/gamepad.ts` (seis `if` à mão dentro de `pollPads`; o do `run` TEM a guarda: `if (edge('run') &&
|
|
43
|
+
// !p.easy) p.runEdge = true;`);
|
|
44
|
+
// 3. AQUI (`TOUCH_EDGE_BY_ACTION` + `touchEdgesFor`), que veio dos seis `if` à mão do main.js — e o do `run`
|
|
45
|
+
// **NÃO TEM** a guarda do Fácil.
|
|
46
|
+
// Isso FOI CORRIGIDO: o `run` daqui passou a ter a mesma guarda dos outros dois caminhos.
|
|
47
|
+
//
|
|
48
|
+
// A consequência é de acessibilidade, e é ao contrário do que o nome sugere. `runEdge` não é a velocidade de
|
|
49
|
+
// corrida (isso é `held(pl,'run')`, lido em `game/physics.ts:169-170`): `runEdge` é a BORDA que gruda e solta
|
|
50
|
+
// da parede — a ventosa/homem-aranha de `updateCling`, em `game/physics.ts:177` e `:179`. Logo, hoje, no modo
|
|
51
|
+
// Fácil (deficiência motora), a criança NÃO consegue escalar parede pelo teclado nem pelo controle físico —
|
|
52
|
+
// mas CONSEGUE pelo botão da tela. Três caminhos de entrada, dois comportamentos.
|
|
53
|
+
// O teste continua comparando os TRÊS caminhos lado a lado, com o `edgesFor` real importado de `keydown.js` —
|
|
54
|
+
// agora exigindo que CONCORDEM. As três tabelas ainda são três; fundi-las numa só é o passo seguinte, e é o
|
|
55
|
+
// que impede a divergência de voltar.
|
|
56
|
+
//
|
|
57
|
+
// ======================= O QUE FICOU DE FORA, E POR QUÊ =======================
|
|
58
|
+
// · `hideTouchControls`/`showTouchControls` são de `input/touch.ts`. Aqui só se CHAMA `showTouchControls`
|
|
59
|
+
// (o toque revela os botões); esconder é do `keydown`, que sabe que o teclado assumiu.
|
|
60
|
+
// · `attractCtl.onInput()` não vira parte pura, pelo mesmo motivo do cabeçalho de `keydown.ts`: ela DECIDE E
|
|
61
|
+
// AGE na mesma chamada (zera a ociosidade, encerra a demo, devolve se encerrou). Fica no ouvinte.
|
|
62
|
+
// · A geometria FÍSICA do direcional (quantos px vale o curso, quantos px vale a zona morta do analógico) é
|
|
63
|
+
// de `input/touch.ts`, que a recalcula a cada `applyPadPhysical()`. Entra por getter e é lida A CADA
|
|
64
|
+
// MOVIMENTO, verbatim — girar o aparelho no meio de um gesto muda o curso no gesto seguinte.
|
|
65
|
+
// · A zona morta da CRUZ, ao contrário, não é configurável: são 18% do lado, escritos no lugar. Continua
|
|
66
|
+
// assim (`CROSS_DEAD_FRACTION`), agora com nome.
|
|
67
|
+
//
|
|
68
|
+
// ======================= ARMADILHAS DE ORDEM DE BOOT =======================
|
|
69
|
+
// `initTouchBindings(ctx)` não toca em DOM nenhum: só fecha sobre o `ctx` e devolve a api. Todo efeito está em
|
|
70
|
+
// `attach()`, que é onde o `#touch-controls` é procurado (e onde a função inteira desiste, se ele não existir —
|
|
71
|
+
// a guarda `if(!tc)return` do original).
|
|
72
|
+
// MESMO ASSIM, o ctx é quase todo GETTER, e por dois motivos independentes:
|
|
73
|
+
// 1. TDZ. No main.js, `attractCtl` é `const` declarado ABAIXO do ponto onde este bloco mora. O IIFE original
|
|
74
|
+
// só funcionava porque o corpo dos ouvintes é preguiçoso. Passar `attractCtl` por VALOR derruba o boot.
|
|
75
|
+
// 2. Reatribuição. A regra da casa: o que o main.js REATRIBUI entra por getter. `keys` é `const` de
|
|
76
|
+
// `input/state.ts`, mutado in place → entra por VALOR (é sempre o mesmo objeto, para sempre).
|
|
77
|
+
// ORDEM DE REGISTRO: `attach()` instala um `pointerdown` de CAPTURA na janela. Ele é hoje o primeiro ouvinte
|
|
78
|
+
// de ponteiro do jogo e é ele que encerra a demo; registrar mais tarde só passa a importar se algum dia nascer
|
|
79
|
+
// outro ouvinte de ponteiro de janela no meio. Mantenha a chamada no lugar exato onde o IIFE estava.
|
|
80
|
+
//
|
|
81
|
+
// SEM I/O NO IMPORT: o corpo do módulo só declara constantes congeladas e funções puras.
|
|
82
|
+
//
|
|
83
|
+
// Ver docs/5-Refactoring/plano-modularizacao-mapa.md (D3-b).
|
|
84
|
+
/* ===================== interfaces mínimas ===================== */
|
|
85
|
+
/** `ui/dom.ts` `$` — injetado; o módulo nunca alcança `document`. */
|
|
86
|
+
import { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
|
|
87
|
+
import { doCentro } from './pointer-space.js';
|
|
88
|
+
/* ===================== PURO (sem DOM — project node) ===================== */
|
|
89
|
+
/**
|
|
90
|
+
* Ação → borda, na MESMA ordem das outras duas cópias do projeto.
|
|
91
|
+
* ⚠️ TERCEIRA CÓPIA DE UMA TABELA QUE JÁ EXISTE DUAS VEZES — `input/keydown.ts` (`EDGE_BY_ACTION`) e
|
|
92
|
+
* `input/gamepad.ts` (seis `if` dentro de `pollPads`). Ver o bloco de aviso no cabeçalho: as três não
|
|
93
|
+
* concordam sobre o modo Fácil, e a divergência está PRESERVADA de propósito.
|
|
94
|
+
*/
|
|
95
|
+
export { EDGE_BY_ACTION as TOUCH_EDGE_BY_ACTION } from './edges.js'; // MESMA tabela dos outros dois caminhos
|
|
96
|
+
/** `?touch=1` na URL força os controles de toque a aparecerem no desktop (atalho de teste do José). */
|
|
97
|
+
export const TOUCH_FORCE_RE = /[?&]touch=1/;
|
|
98
|
+
/** Predicado do `?touch=1`, isolado para o teste não repetir o regex. */
|
|
99
|
+
export function wantsForcedTouch(search) {
|
|
100
|
+
return TOUCH_FORCE_RE.test(search || '');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Que TECLA este botão de tela finge apertar? A 1ª do esquema do Jogador 1 para aquela ação — 1ª, e não
|
|
104
|
+
* todas, porque só uma precisa entrar em `keys` para o jogo inteiro reagir. Remapear o teclado remapeia o
|
|
105
|
+
* toque junto, de graça: é a mesma tabela.
|
|
106
|
+
* `null` = a ação não existe no esquema (ou o slot do mapa de toque está vazio) → o toque não faz nada.
|
|
107
|
+
*/
|
|
108
|
+
export function codeForAction(act, controls) {
|
|
109
|
+
if (!act)
|
|
110
|
+
return null;
|
|
111
|
+
const list = controls[act];
|
|
112
|
+
return (list && list[0]) || null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* As bordas que este toque levanta, e em quem.
|
|
116
|
+
*
|
|
117
|
+
* ⚠️ SEM A GUARDA DO MODO FÁCIL — VERBATIM DO main.js, DEFEITO PRESERVADO.
|
|
118
|
+
* `input/keydown.ts:edgesFor` pula `run` quando `p.easy` ("Fácil: sem correr"); `input/gamepad.ts:pollPads`
|
|
119
|
+
* também (`if (edge('run') && !p.easy)`). Este caminho NÃO pula. Como `runEdge` é o gatilho de grudar/soltar
|
|
120
|
+
* da parede (`game/physics.ts:177` e `:179`), e não a velocidade de corrida, o efeito prático é que no modo
|
|
121
|
+
* Fácil a escalada existe pelo botão da tela e não existe pelo teclado nem pelo controle.
|
|
122
|
+
* NÃO acrescente a guarda aqui sem consertar as três cópias de uma vez — ver o cabeçalho.
|
|
123
|
+
*
|
|
124
|
+
* ÚNICO DESVIO DE LITERALIDADE, declarado: `p.ctrl[a] || []`. O original alcançava `p.ctrl.jump`/`.run`/
|
|
125
|
+
* `.left`/`.right` DIRETO (e estouraria num esquema sem a ação), enquanto guardava `swap` e `especial` com
|
|
126
|
+
* `&&` — assimetria sem intenção. Trocar um TypeError por um no-op não remove rede de conserto nenhuma; é o
|
|
127
|
+
* mesmo precedente que `input/keydown.ts:edgesFor` já abriu, e está anotado lá pelo mesmo motivo.
|
|
128
|
+
*/
|
|
129
|
+
export function touchEdgesFor(act, code, players) {
|
|
130
|
+
const out = [];
|
|
131
|
+
players.forEach((p, idx) => {
|
|
132
|
+
if (!p.ctrl)
|
|
133
|
+
return; // jogador sem esquema (tela não ativada) não recebe borda
|
|
134
|
+
for (const [a, edge] of EDGE_BY_ACTION) {
|
|
135
|
+
if (a !== act)
|
|
136
|
+
continue;
|
|
137
|
+
if (!edgeAllowed(a, p.easy))
|
|
138
|
+
continue; // Fácil: sem correr (input/edges.ts)
|
|
139
|
+
if ((p.ctrl[a] || []).includes(code))
|
|
140
|
+
out.push({ playerIndex: idx, edge });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A DECISÃO. Gesto (ação mapeada + apertar/soltar) + mundo → o que isso significa.
|
|
147
|
+
* `pause` primeiro, porque é o único que não passa pelo teclado sintético. Depois a tecla; sem tecla, nada.
|
|
148
|
+
*/
|
|
149
|
+
export function decideTouch(act, on, s) {
|
|
150
|
+
if (act === 'start')
|
|
151
|
+
return on ? { kind: 'pause' } : { kind: 'noop' }; // SOLTAR o START não despausa
|
|
152
|
+
const code = codeForAction(act, s.controls);
|
|
153
|
+
if (!code)
|
|
154
|
+
return { kind: 'noop' };
|
|
155
|
+
if (!on)
|
|
156
|
+
return { kind: 'release', code };
|
|
157
|
+
// BORDA: só a PRIMEIRA vez que o código entra em `keys`. Se o teclado (ou o outro polegar) já o segurava,
|
|
158
|
+
// o toque não re-levanta as bordas — mas `hideTips` roda igual, verbatim.
|
|
159
|
+
const fresh = !s.heldKeys.has(code);
|
|
160
|
+
return {
|
|
161
|
+
kind: 'press', code, addKey: fresh,
|
|
162
|
+
edges: fresh ? touchEdgesFor(act, code, s.players) : [],
|
|
163
|
+
hideTips: act === 'action2',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/* --- geometria do direcional (pura: posição + retângulo → direções) --- */
|
|
167
|
+
/** Zona morta da CRUZ: ~18% do LADO a partir do centro, em cada eixo. Ver a nota de `crossDirsAt` sobre o
|
|
168
|
+
* `width` valer também para o eixo vertical — é verbatim, e é uma assimetria de verdade. */
|
|
169
|
+
export const CROSS_DEAD_FRACTION = 0.18;
|
|
170
|
+
/**
|
|
171
|
+
* Cruz (D-pad): onde o dedo está → quais direções ligam. Miolo neutro para não disparar por encostar no meio.
|
|
172
|
+
* As quatro comparações são INDEPENDENTES: fora do miolo, uma diagonal liga DUAS direções (é o que dá o
|
|
173
|
+
* comportamento de D-pad físico de 8 setores). O centro exato não liga nenhuma.
|
|
174
|
+
*
|
|
175
|
+
* VERBATIM, incluindo a assimetria: a zona morta dos DOIS eixos sai de `rect.width` — a altura nunca entra na
|
|
176
|
+
* conta. Numa cruz quadrada (é o que o CSS `--dpad-span` produz) dá no mesmo; numa cruz achatada, o miolo
|
|
177
|
+
* vertical ficaria proporcionalmente maior ou menor que o horizontal. Anotado, não consertado.
|
|
178
|
+
*/
|
|
179
|
+
export function crossDirsAt(px, py, rect) {
|
|
180
|
+
const { dx, dy } = doCentro(px, py, rect);
|
|
181
|
+
const dead = rect.width * CROSS_DEAD_FRACTION;
|
|
182
|
+
return { left: dx < -dead, right: dx > dead, up: dy < -dead, down: dy > dead };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Analógico virtual: mesma regra da cruz, mas a zona morta vem em PX de `input/touch.ts` (derivada dos mm
|
|
186
|
+
* configuráveis — A12e motora), não de uma fração do elemento.
|
|
187
|
+
*/
|
|
188
|
+
export function stickDirsAt(px, py, rect, deadPx) {
|
|
189
|
+
const { dx, dy } = doCentro(px, py, rect);
|
|
190
|
+
return { left: dx < -deadPx, right: dx > deadPx, up: dy < -deadPx, down: dy > deadPx };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Para onde a manopla do analógico anda (px, relativo ao centro da base): segue o dedo até o limite do curso
|
|
194
|
+
* `travelPx` e ali PARA, mantendo o ângulo (recorte radial, não por eixo — por isso `hypot`, e não `clamp`).
|
|
195
|
+
* O `|| 1` do original evita divisão por zero quando o dedo cai no centro exato.
|
|
196
|
+
*/
|
|
197
|
+
export function stickKnobOffset(px, py, rect, travelPx) {
|
|
198
|
+
const { dx, dy } = doCentro(px, py, rect);
|
|
199
|
+
const m = Math.hypot(dx, dy) || 1;
|
|
200
|
+
const f = m > travelPx ? travelPx / m : 1;
|
|
201
|
+
return { x: dx * f, y: dy * f };
|
|
202
|
+
}
|
|
203
|
+
/** Quanto tempo o START segura a ação, quando ela é momentânea (não é `pause`). */
|
|
204
|
+
export const START_TAP_MS = 140;
|
|
205
|
+
export function initTouchBindings(ctx) {
|
|
206
|
+
const later = ctx.defer || ((fn, ms) => { setTimeout(fn, ms); });
|
|
207
|
+
function snapshot() {
|
|
208
|
+
return { controls: ctx.getControls(), players: ctx.getPlayers(), heldKeys: ctx.heldKeys };
|
|
209
|
+
}
|
|
210
|
+
function decide(act, on) {
|
|
211
|
+
return decideTouch(act, on, snapshot());
|
|
212
|
+
}
|
|
213
|
+
/** A metade IMPURA: pega a decisão pronta e a carimba no mundo. Ordem verbatim: a tecla entra em `keys`
|
|
214
|
+
* ANTES das bordas, e `hideTips` vem depois de tudo. */
|
|
215
|
+
function apply(d) {
|
|
216
|
+
if (d.kind === 'noop')
|
|
217
|
+
return;
|
|
218
|
+
if (d.kind === 'pause') {
|
|
219
|
+
ctx.togglePause();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (d.kind === 'release') {
|
|
223
|
+
ctx.heldKeys.delete(d.code);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (d.addKey) {
|
|
227
|
+
ctx.heldKeys.add(d.code);
|
|
228
|
+
const players = ctx.getPlayers();
|
|
229
|
+
for (const { playerIndex, edge } of d.edges) {
|
|
230
|
+
const p = players[playerIndex];
|
|
231
|
+
if (p)
|
|
232
|
+
p[edge] = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (d.hideTips)
|
|
236
|
+
ctx.hideTips();
|
|
237
|
+
}
|
|
238
|
+
function doTouch(act, on) { apply(decide(act, on)); }
|
|
239
|
+
function pressStart() {
|
|
240
|
+
const a = ctx.getStartAction();
|
|
241
|
+
if (a === 'start') {
|
|
242
|
+
ctx.togglePause();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
doTouch(a, true);
|
|
246
|
+
later(() => doTouch(a, false), START_TAP_MS); // ação momentânea: aperta e solta sozinho
|
|
247
|
+
}
|
|
248
|
+
function revealForTests() {
|
|
249
|
+
const tc = ctx.$('#touch-controls');
|
|
250
|
+
if (tc)
|
|
251
|
+
tc.hidden = false;
|
|
252
|
+
}
|
|
253
|
+
/** Direcional (analógico ou cruz): um só padrão — estado por direção, só despacha na MUDANÇA. Sem isso,
|
|
254
|
+
* arrastar o polegar dentro do mesmo quadrante re-levantaria a borda a cada `pointermove`. */
|
|
255
|
+
function makeDirGate(onChange) {
|
|
256
|
+
const state = { left: false, right: false, up: false, down: false };
|
|
257
|
+
const set = (dir, on) => {
|
|
258
|
+
if (state[dir] === on)
|
|
259
|
+
return;
|
|
260
|
+
state[dir] = on;
|
|
261
|
+
onChange(dir, on);
|
|
262
|
+
};
|
|
263
|
+
return {
|
|
264
|
+
set,
|
|
265
|
+
apply: (dirs) => { set('left', dirs.left); set('right', dirs.right); set('up', dirs.up); set('down', dirs.down); },
|
|
266
|
+
reset: () => { ['left', 'right', 'up', 'down'].forEach((d) => set(d, false)); },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/* --- os .touch-btn do losango --- */
|
|
270
|
+
function wireButtons(tc) {
|
|
271
|
+
tc.querySelectorAll('.touch-btn').forEach((b) => {
|
|
272
|
+
const slot = 'b' + (b.dataset.btn || ''); // a função vem do touchMap (remapeável), não do data-act
|
|
273
|
+
const down = (e) => { e.preventDefault(); doTouch(ctx.getTouchMap()[slot], true); };
|
|
274
|
+
const up = (e) => { e.preventDefault(); doTouch(ctx.getTouchMap()[slot], false); };
|
|
275
|
+
b.addEventListener('pointerdown', down);
|
|
276
|
+
b.addEventListener('pointerup', up);
|
|
277
|
+
b.addEventListener('pointerleave', up); // o dedo escorregou para fora: conta como soltar
|
|
278
|
+
b.addEventListener('pointercancel', up);
|
|
279
|
+
b.addEventListener('contextmenu', (e) => e.preventDefault()); // segurar não abre menu do sistema
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Analógico e cruz compartilham TODO o ciclo de vida do ponteiro (captura, um dedo por vez, soltar/cancelar,
|
|
284
|
+
* perder a captura). O que muda é só a conta de direção e o retorno visual — por isso os dois entram aqui e
|
|
285
|
+
* a diferença vira dois callbacks.
|
|
286
|
+
*/
|
|
287
|
+
function wirePointerPad(el, dirsAt, onMove, onReset, onDirVisual) {
|
|
288
|
+
let pid = null;
|
|
289
|
+
const gate = makeDirGate((dir, on) => {
|
|
290
|
+
doTouch(ctx.getTouchMap()[dir], on); // direção FÍSICA → função mapeada (a cruz também é remapeável)
|
|
291
|
+
if (onDirVisual)
|
|
292
|
+
onDirVisual(dir, on);
|
|
293
|
+
});
|
|
294
|
+
const at = (px, py) => {
|
|
295
|
+
const rect = el.getBoundingClientRect();
|
|
296
|
+
if (onMove)
|
|
297
|
+
onMove(px, py, rect);
|
|
298
|
+
gate.apply(dirsAt(px, py, rect));
|
|
299
|
+
};
|
|
300
|
+
const reset = () => { if (onReset)
|
|
301
|
+
onReset(); gate.reset(); pid = null; };
|
|
302
|
+
el.addEventListener('pointerdown', (e) => {
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
const id = e.pointerId;
|
|
305
|
+
pid = id;
|
|
306
|
+
// captura: o dedo pode sair do elemento sem que o gesto acabe (é o que faz um direcional ser usável)
|
|
307
|
+
try {
|
|
308
|
+
el.setPointerCapture(id);
|
|
309
|
+
}
|
|
310
|
+
catch { /* sem captura de ponteiro: o gesto ainda funciona por cima do elemento */ }
|
|
311
|
+
at(e.clientX, e.clientY);
|
|
312
|
+
});
|
|
313
|
+
el.addEventListener('pointermove', (e) => {
|
|
314
|
+
if (pid !== e.pointerId)
|
|
315
|
+
return; // um dedo por vez: o segundo ponteiro é ignorado, não disputa
|
|
316
|
+
e.preventDefault();
|
|
317
|
+
at(e.clientX, e.clientY);
|
|
318
|
+
});
|
|
319
|
+
const end = (e) => { if (pid !== e.pointerId)
|
|
320
|
+
return; e.preventDefault(); reset(); };
|
|
321
|
+
el.addEventListener('pointerup', end);
|
|
322
|
+
el.addEventListener('pointercancel', end);
|
|
323
|
+
el.addEventListener('lostpointercapture', reset); // rede: perdeu a captura = solta tudo (nada fica preso)
|
|
324
|
+
el.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
325
|
+
}
|
|
326
|
+
function attach() {
|
|
327
|
+
const tc = ctx.$('#touch-controls');
|
|
328
|
+
if (!tc)
|
|
329
|
+
return; // sem os botões no documento, não há o que amarrar (guarda do IIFE original)
|
|
330
|
+
// alternância por modalidade: toque/clique MOSTRA; teclado/controle OCULTA (lá no keydown/gamepad).
|
|
331
|
+
if (wantsForcedTouch(ctx.getSearch()))
|
|
332
|
+
ctx.showTouchControls();
|
|
333
|
+
const onPointerDown = () => { if (ctx.attractOnInput())
|
|
334
|
+
return; ctx.showTouchControls(); }; // toque revela (e encerra a demo)
|
|
335
|
+
const onTouchStart = () => { ctx.showTouchControls(); };
|
|
336
|
+
ctx.win.addEventListener('pointerdown', onPointerDown, true);
|
|
337
|
+
ctx.win.addEventListener('touchstart', onTouchStart, { capture: true, passive: true });
|
|
338
|
+
wireButtons(tc);
|
|
339
|
+
const startBtn = ctx.$('#touch-start');
|
|
340
|
+
if (startBtn)
|
|
341
|
+
startBtn.addEventListener('click', pressStart);
|
|
342
|
+
// analógico: base (círculo grande) + manopla que desliza para a direção tocada
|
|
343
|
+
const stick = ctx.$('#touch-stick');
|
|
344
|
+
const knob = stick && stick.querySelector('.touch-knob');
|
|
345
|
+
if (stick && knob) {
|
|
346
|
+
wirePointerPad(stick, (px, py, rect) => stickDirsAt(px, py, rect, ctx.getStickDeadPx()), // px/mm relidos a cada movimento
|
|
347
|
+
(px, py, rect) => {
|
|
348
|
+
const o = stickKnobOffset(px, py, rect, ctx.getStickTravelPx());
|
|
349
|
+
knob.style.transform = `translate(${o.x}px,${o.y}px)`;
|
|
350
|
+
}, () => { knob.style.transform = 'translate(0,0)'; }, null);
|
|
351
|
+
}
|
|
352
|
+
// cruz (estilo alternativo ao analógico): superfície dividida por hit-test, com miolo neutro
|
|
353
|
+
const cross = ctx.$('#touch-cross');
|
|
354
|
+
if (cross) {
|
|
355
|
+
const arms = {
|
|
356
|
+
up: cross.querySelector('.dpad-up'),
|
|
357
|
+
down: cross.querySelector('.dpad-down'),
|
|
358
|
+
left: cross.querySelector('.dpad-left'),
|
|
359
|
+
right: cross.querySelector('.dpad-right'),
|
|
360
|
+
};
|
|
361
|
+
wirePointerPad(cross, crossDirsAt, null, null, (dir, on) => { arms[dir]?.classList.toggle('on', on); });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return { decide, doTouch, pressStart, revealForTests, attach };
|
|
365
|
+
}
|