@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,173 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { DomQuery } from '../core/dom-query.js';
|
|
3
|
+
/** Minimal DOM-selector shape (matches ui/dom.ts's `$`). */
|
|
4
|
+
export type { DomQuery } from '../core/dom-query.js';
|
|
5
|
+
/** Minimal platform/storage.ts shape this module needs. */
|
|
6
|
+
export interface TouchStore {
|
|
7
|
+
get(key: string, fallback?: string | null): string | null;
|
|
8
|
+
set(key: string, value: string | number | boolean): boolean;
|
|
9
|
+
getNum(key: string, fallback?: number): number;
|
|
10
|
+
getJSON<T = unknown>(key: string, fallback?: T | null): T | null;
|
|
11
|
+
setJSON(key: string, obj: unknown): void;
|
|
12
|
+
}
|
|
13
|
+
export interface TouchCtx {
|
|
14
|
+
/** DOM selector (querySelector), injected — never reaches `document` globally. */
|
|
15
|
+
$: DomQuery;
|
|
16
|
+
/** Screen-reader "polite" announcement (core/a11y-sr's srSay), injected. */
|
|
17
|
+
srSay: (msg: string) => void;
|
|
18
|
+
/**
|
|
19
|
+
* AS POSIÇÕES QUE ESTE JOGO USA, cada uma com a palavra dele, no idioma vigente.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ Substitui `TOUCH_ACTS` + `TOUCH_ACT_LABELS`. O menu de cada slot oferecia NOVE opções fixas — quer
|
|
22
|
+
* dizer, a engine decidia que todo jogo tem pular, correr, trocar e especial, e uma criança num quiz
|
|
23
|
+
* poderia atribuir «Trocar poder» a um botão da tela, que depois não faria nada.
|
|
24
|
+
*
|
|
25
|
+
* A ordem também vem daqui: é a ordem canônica de `core/actions`, a mesma que a tela de remapeamento usa,
|
|
26
|
+
* para a criança não ter de reaprender a lista ao trocar de painel.
|
|
27
|
+
*/
|
|
28
|
+
acoesDoJogo: () => readonly {
|
|
29
|
+
readonly acao: string;
|
|
30
|
+
readonly rotulo: string;
|
|
31
|
+
}[];
|
|
32
|
+
/** Persistence (platform/storage.ts), injected. */
|
|
33
|
+
store: TouchStore;
|
|
34
|
+
/** Element the --pad-* CSS custom properties are written to: document.documentElement in production
|
|
35
|
+
* (mirrors ui/settings-typo.ts's `root` injection for the same reason — no direct `document` access). */
|
|
36
|
+
root: HTMLElement;
|
|
37
|
+
/** Shared device-class check (game.js's isMobile(), used by several subsystems) — injected, not duplicated. */
|
|
38
|
+
isMobile: () => boolean;
|
|
39
|
+
/** Screen dimensions, injected (never reads `window.innerWidth/innerHeight` directly). */
|
|
40
|
+
viewport: () => {
|
|
41
|
+
w: number;
|
|
42
|
+
h: number;
|
|
43
|
+
};
|
|
44
|
+
/** Shared overlay z-index/focus helper (game.js's frontOverlay), used by every panel — injected. */
|
|
45
|
+
frontOverlay: (el: HTMLElement | null) => void;
|
|
46
|
+
/** Optional hook fired after applyPadDesign() changes padDesign — game.js's renderPauseLegend() (Sim/Não
|
|
47
|
+
* glyphs in the pause menu) is NOT part of this module's boundary but must still refresh; see report. */
|
|
48
|
+
onPadDesignApplied?: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* O controle de tela ENTROU em cena. Existe para que a raiz possa ligar sozinha o que só faz sentido no
|
|
51
|
+
* toque — hoje, a alternância do botão de correr: num botão virtual ninguém "segura" com conforto, porque
|
|
52
|
+
* o dedo que segura é o mesmo que precisa alcançar os outros.
|
|
53
|
+
*
|
|
54
|
+
* Gancho e não regra aqui dentro: este módulo desenha controles e não conhece ajuste de acessibilidade
|
|
55
|
+
* nenhum. Quem sabe o que ligar é quem possui os jogadores.
|
|
56
|
+
*/
|
|
57
|
+
onTouchControlsShown?: () => void;
|
|
58
|
+
/**
|
|
59
|
+
* PODE mostrar o pad virtual AGORA? Injetado, e é o corte do item 19 neste módulo.
|
|
60
|
+
*
|
|
61
|
+
* Era uma linha que lia TRÊS coisas por importação de `core/state`:
|
|
62
|
+
* `if (numPlayers > 1 || phase !== 'playing' || players.some((p) => p.quiz)) return;`
|
|
63
|
+
* — o número de jogadores, a fase, e se algum jogador tem um QUIZ aberto. A última é a que denunciava: a
|
|
64
|
+
* camada de TOQUE sabia que existe atividade de alfabetização. E as três juntas eram uma POLÍTICA do jogo
|
|
65
|
+
* escrita dentro da engine.
|
|
66
|
+
*
|
|
67
|
+
* É o mesmo movimento do achado 10 (`isNavigable`): injetar o BOOLEANO, não o estado. A plataforma responde
|
|
68
|
+
* "um jogador, jogando, sem desafio aberto"; um jogo de outro gênero responde o que for verdade nele. E o
|
|
69
|
+
* módulo deixou de importar `core/state` — não sobrou leitura de estado compartilhado nenhuma.
|
|
70
|
+
*/
|
|
71
|
+
padAllowed: () => boolean;
|
|
72
|
+
}
|
|
73
|
+
export declare const IPHONE16_LONG_MM = 141.1;
|
|
74
|
+
export declare const IPHONE16_LONG_PX = 852;
|
|
75
|
+
export declare const IPHONE16_PXMM: number;
|
|
76
|
+
/** mm→px CSS. No celular, ancora na aresta longa da JANELA (físico exato, retrato ou paisagem). No
|
|
77
|
+
* desktop/notebook, fixa no ratio do iPhone 16 (não cresce com a largura do monitor). Determinístico e puro:
|
|
78
|
+
* `mobile`/`viewW`/`viewH` chegam já resolvidos (isMobile()/innerWidth/innerHeight ficam no lado impuro). */
|
|
79
|
+
export declare function padPxPerMm(mobile: boolean, viewW: number, viewH: number): number;
|
|
80
|
+
export type HandTag = 'crianca' | 'adulto' | 'inter';
|
|
81
|
+
/** Classifica um valor em mm na faixa mão-de-criança / intermediário / mão-de-adulto (usado nas etiquetas
|
|
82
|
+
* do painel de configuração de toque). */
|
|
83
|
+
export declare function padHandTag(v: number, lo: number, hi: number): HandTag;
|
|
84
|
+
export interface PadMm {
|
|
85
|
+
btn: number;
|
|
86
|
+
gap: number;
|
|
87
|
+
stick: number;
|
|
88
|
+
travel: number;
|
|
89
|
+
dpad: number;
|
|
90
|
+
}
|
|
91
|
+
export interface PadPhysicalPx {
|
|
92
|
+
btnPx: number;
|
|
93
|
+
diamPx: number;
|
|
94
|
+
knobPx: number;
|
|
95
|
+
basePx: number;
|
|
96
|
+
armPx: number;
|
|
97
|
+
armWPx: number;
|
|
98
|
+
spanPx: number;
|
|
99
|
+
stickTravelPx: number;
|
|
100
|
+
stickDeadPx: number;
|
|
101
|
+
}
|
|
102
|
+
/** Toda a geometria em px a partir dos mm configurados + do fator px/mm — extraída de applyPadPhysical() para
|
|
103
|
+
* ficar pura/testável (dpr alto/baixo, tela pequena, valores extremos). applyPadPhysical() só chama isto e
|
|
104
|
+
* escreve o resultado em custom properties CSS + nos rótulos do painel. */
|
|
105
|
+
export declare function computePadPhysicalPx(mm: PadMm, pxPerMm: number): PadPhysicalPx;
|
|
106
|
+
export type PadLayout = 'sony' | 'nintendo' | 'microsoft' | 'generic';
|
|
107
|
+
/** Layout de rotulagem dos botões pelo id reportado do controle (heurística por substring/VID). */
|
|
108
|
+
export declare function padLayoutFromId(id: string | null | undefined): PadLayout;
|
|
109
|
+
/** As 9 posições de toque remapeáveis (direcional×4, START, botões 0–3) — `lbl` é a chave i18n do rótulo. */
|
|
110
|
+
export declare const TOUCH_SLOTS: ReadonlyArray<{
|
|
111
|
+
k: string;
|
|
112
|
+
lbl: string;
|
|
113
|
+
}>;
|
|
114
|
+
/**
|
|
115
|
+
* As ações que o TRANSPORTE de toque consegue carregar.
|
|
116
|
+
*
|
|
117
|
+
* ⚠️ DEIXOU DE SER A LISTA DO `<select>`: as opções vêm agora de `ctx.acoesDoJogo()`, porque quem decide
|
|
118
|
+
* quais ações existem é o jogo. Esta lista continua a valer como o que ESTE transporte alcança — e é contra
|
|
119
|
+
* ela que o gate de `input/touch-bindings` confere se o despacho reconhece tudo o que se pode oferecer.
|
|
120
|
+
* As duas coisas eram a mesma por acidente enquanto só havia um jogo.
|
|
121
|
+
*/
|
|
122
|
+
export declare const TOUCH_ACTS: readonly string[];
|
|
123
|
+
/** Funde o mapa persistido (JSON solto do localStorage) sobre TOUCH_DEFAULT. Mantido IDÊNTICO ao original:
|
|
124
|
+
* NÃO valida chaves/valores contra TOUCH_SLOTS/TOUCH_ACTS — um JSON malformado com chaves/valores estranhos
|
|
125
|
+
* passa como está (só falha se não for um objeto). Ver a nota "bug surfaced" no retorno da extração. */
|
|
126
|
+
export declare function normalizeTouchMap(stored: unknown): Record<string, string>;
|
|
127
|
+
export type PadKind = 'kb' | 'x' | 'd';
|
|
128
|
+
/** UNUSED hoje — grep em game.js (`\bpadKind\b`) só acha a própria definição, nenhum chamador. Extraído porque
|
|
129
|
+
* a fronteira explicitamente listou; NÃO apagado (relatado, não conserto). Lê navigator.getGamepads() direto,
|
|
130
|
+
* no mesmo padrão de ui/settings-audio.ts / core/i18n.ts (acesso direto a navigator, sem DI) — mesmo assim,
|
|
131
|
+
* é semanticamente "ler o controle", território do agente de input/gamepad; ver a nota de fronteira no retorno. */
|
|
132
|
+
export declare function padKind(): PadKind;
|
|
133
|
+
export interface PadMmPatch {
|
|
134
|
+
btn?: number;
|
|
135
|
+
gap?: number;
|
|
136
|
+
stick?: number;
|
|
137
|
+
travel?: number;
|
|
138
|
+
dpad?: number;
|
|
139
|
+
}
|
|
140
|
+
export interface TouchApi {
|
|
141
|
+
/** (Re)desenha #touchmap-list a partir do touchMap atual e prende os <select> de cada slot. */
|
|
142
|
+
renderTouchMap(): void;
|
|
143
|
+
/** Abre o diálogo "Botões de tela touch" (#touchcfg). */
|
|
144
|
+
openTouchCfg(): void;
|
|
145
|
+
/** Fecha o diálogo #touchcfg e devolve o foco ao botão que o abriu. */
|
|
146
|
+
closeTouchCfg(): void;
|
|
147
|
+
/** Esconde os controles de toque (teclado/controle físico assumiu, ou um menu abriu por cima). O parâmetro
|
|
148
|
+
* `reason` é só um rótulo de depuração nos call-sites originais — o game.js já o descartava (função de
|
|
149
|
+
* zero parâmetros); mantido aqui só de fachada para não quebrar chamadas existentes. */
|
|
150
|
+
hideTouchControls(reason?: string): void;
|
|
151
|
+
/** Mostra os controles de toque (toque/clique detectado). No-op em multi-tela, fora de 'playing', ou com
|
|
152
|
+
* quiz aberto — dá pra tocar direto nos botões da tela nesses casos. */
|
|
153
|
+
showTouchControls(): void;
|
|
154
|
+
/** Alterna a visibilidade do analógico virtual vs. da cruz (D-pad), conforme `padDir`. */
|
|
155
|
+
applyDirStyle(): void;
|
|
156
|
+
/** Recalcula toda a geometria (mm→px) e escreve as custom properties + os rótulos do painel. */
|
|
157
|
+
applyPadPhysical(): void;
|
|
158
|
+
/** Atualiza um ou mais tamanhos (mm), persiste e reaplica a geometria. */
|
|
159
|
+
setPadMm(patch: PadMmPatch): void;
|
|
160
|
+
/** Aplica (e opcionalmente troca) o desenho dos botões; persiste; repinta os botões físicos do losango de
|
|
161
|
+
* toque (#pad-diamond); dispara ctx.onPadDesignApplied() (legenda Sim/Não da pausa, fora daqui). Retorna o
|
|
162
|
+
* design resultante. */
|
|
163
|
+
applyPadDesign(d?: string): string;
|
|
164
|
+
/** Design de botão atualmente ativo ('generic'|'microsoft'|'sony'|'nintendo'). */
|
|
165
|
+
getPadDesign(): string;
|
|
166
|
+
/** Referência viva do mapa de toque atual (slot → ação) — mutada por renderTouchMap(); NÃO é uma cópia. */
|
|
167
|
+
getTouchMap(): Record<string, string>;
|
|
168
|
+
/** Deslocamento útil do analógico virtual em px, recalculado a cada applyPadPhysical(). */
|
|
169
|
+
getStickTravelPx(): number;
|
|
170
|
+
/** Zona-morta do analógico virtual em px, recalculada a cada applyPadPhysical(). */
|
|
171
|
+
getStickDeadPx(): number;
|
|
172
|
+
}
|
|
173
|
+
export declare function initTouch(ctx: TouchCtx): TouchApi;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/touch.ts — on-screen touch controls (Estágio 4): the physical geometry of the touch pad (mm→px) and
|
|
3
|
+
// its config UI (#touchcfg, #touch-controls visibility). Extracted from game.js's E13 block. Geometry/layout/
|
|
4
|
+
// touch-map-merge are pure (padPxPerMm/padHandTag/computePadPhysicalPx/padLayoutFromId/normalizeTouchMap —
|
|
5
|
+
// project node); DOM wiring + persisted state (touchMap/padDesign/padBtnMm/padGapMm/padStickMm/padTravelMm/
|
|
6
|
+
// padDpadMm/padDir) live behind initTouch(ctx). PAD_DESIGNS/TOUCH_ACT_LABELS/TOUCH_DEFAULT come from
|
|
7
|
+
// ./devices.js (not reimplemented). Reading the real Gamepad API (polling, mapping wizard) is input/gamepad's
|
|
8
|
+
// territory, not this module's — see the header note on padKind() for the one deliberate exception.
|
|
9
|
+
import { PAD_DESIGNS, TOUCH_DEFAULT } from './devices.js';
|
|
10
|
+
import { migrarMapaDeToque } from './vocabulary-migration.js';
|
|
11
|
+
import { t } from '../core/i18n.js';
|
|
12
|
+
import { KEYS } from '../platform/storage.js'; // só as CHAVES (constantes) — leitura/escrita passam por ctx.store (DI)
|
|
13
|
+
import { setMinimapCorner } from '../render/minimap.js'; // já módulo próprio (Estágio 4, Tier 1) — importado direto
|
|
14
|
+
// ===================== PURO (sem DOM/store — project node) =====================
|
|
15
|
+
// Alvo físico ancorado no iPhone 16 a tela cheia (aresta longa 141,1mm do display 1179×2556 @460ppi →
|
|
16
|
+
// ~6,04 px CSS/mm). Ver o comentário original em game.js para a justificativa completa (WCAG 2.5.5 / GAG).
|
|
17
|
+
export const IPHONE16_LONG_MM = 141.1;
|
|
18
|
+
export const IPHONE16_LONG_PX = 852;
|
|
19
|
+
export const IPHONE16_PXMM = IPHONE16_LONG_PX / IPHONE16_LONG_MM; // ~6,04 px CSS/mm
|
|
20
|
+
/** mm→px CSS. No celular, ancora na aresta longa da JANELA (físico exato, retrato ou paisagem). No
|
|
21
|
+
* desktop/notebook, fixa no ratio do iPhone 16 (não cresce com a largura do monitor). Determinístico e puro:
|
|
22
|
+
* `mobile`/`viewW`/`viewH` chegam já resolvidos (isMobile()/innerWidth/innerHeight ficam no lado impuro). */
|
|
23
|
+
export function padPxPerMm(mobile, viewW, viewH) {
|
|
24
|
+
return mobile ? Math.max(viewW, viewH) / IPHONE16_LONG_MM : IPHONE16_PXMM;
|
|
25
|
+
}
|
|
26
|
+
/** Classifica um valor em mm na faixa mão-de-criança / intermediário / mão-de-adulto (usado nas etiquetas
|
|
27
|
+
* do painel de configuração de toque). */
|
|
28
|
+
export function padHandTag(v, lo, hi) {
|
|
29
|
+
return v <= lo ? 'crianca' : v >= hi ? 'adulto' : 'inter';
|
|
30
|
+
}
|
|
31
|
+
/** Toda a geometria em px a partir dos mm configurados + do fator px/mm — extraída de applyPadPhysical() para
|
|
32
|
+
* ficar pura/testável (dpr alto/baixo, tela pequena, valores extremos). applyPadPhysical() só chama isto e
|
|
33
|
+
* escreve o resultado em custom properties CSS + nos rótulos do painel. */
|
|
34
|
+
export function computePadPhysicalPx(mm, pxPerMm) {
|
|
35
|
+
const btnPx = mm.btn * pxPerMm, gapPx = mm.gap * pxPerMm;
|
|
36
|
+
const diamPx = btnPx + Math.SQRT2 * (btnPx + gapPx); // losango: folga de aresta = gap
|
|
37
|
+
const knobPx = mm.stick * pxPerMm, travelPx = mm.travel * pxPerMm;
|
|
38
|
+
const basePx = knobPx + 2 * travelPx + 16; // base do analógico = contato + curso
|
|
39
|
+
const armPx = mm.dpad * pxPerMm, armWPx = armPx * 0.8, spanPx = 2 * armPx + armWPx; // cruz: braço + largura(0,8×)
|
|
40
|
+
const stickTravelPx = travelPx, stickDeadPx = Math.max(6, travelPx * 0.4); // zona-morta ~40% do curso
|
|
41
|
+
return { btnPx, diamPx, knobPx, basePx, armPx, armWPx, spanPx, stickTravelPx, stickDeadPx };
|
|
42
|
+
}
|
|
43
|
+
/** Layout de rotulagem dos botões pelo id reportado do controle (heurística por substring/VID). */
|
|
44
|
+
export function padLayoutFromId(id) {
|
|
45
|
+
const s = (id || '').toLowerCase();
|
|
46
|
+
if (/dualshock|dualsense|playstation|054c/.test(s))
|
|
47
|
+
return 'sony';
|
|
48
|
+
if (/switch|nintendo|joy-con|057e/.test(s))
|
|
49
|
+
return 'nintendo';
|
|
50
|
+
if (/xbox|xinput|microsoft|045e/.test(s))
|
|
51
|
+
return 'microsoft';
|
|
52
|
+
return 'generic';
|
|
53
|
+
}
|
|
54
|
+
/** As 9 posições de toque remapeáveis (direcional×4, START, botões 0–3) — `lbl` é a chave i18n do rótulo. */
|
|
55
|
+
// `lbl` guarda a CHAVE i18n, não o texto: tabela de módulo resolvida no import congelaria o idioma no boot
|
|
56
|
+
// (ver a nota em input/devices). A seta e o nome do botão viajam DENTRO da tradução, porque em inglês o
|
|
57
|
+
// "(cima)" vira "(up)" e a seta fica onde está — é moldura inteira, não conteúdo interpolado.
|
|
58
|
+
export const TOUCH_SLOTS = [
|
|
59
|
+
{ k: 'up', lbl: 'touch.slot.up' }, { k: 'down', lbl: 'touch.slot.down' },
|
|
60
|
+
{ k: 'left', lbl: 'touch.slot.left' }, { k: 'right', lbl: 'touch.slot.right' },
|
|
61
|
+
{ k: 'start', lbl: 'touch.slot.start' },
|
|
62
|
+
{ k: 'b0', lbl: 'touch.slot.b0' }, { k: 'b1', lbl: 'touch.slot.b1' },
|
|
63
|
+
{ k: 'b2', lbl: 'touch.slot.b2' }, { k: 'b3', lbl: 'touch.slot.b3' },
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* As ações que o TRANSPORTE de toque consegue carregar.
|
|
67
|
+
*
|
|
68
|
+
* ⚠️ DEIXOU DE SER A LISTA DO `<select>`: as opções vêm agora de `ctx.acoesDoJogo()`, porque quem decide
|
|
69
|
+
* quais ações existem é o jogo. Esta lista continua a valer como o que ESTE transporte alcança — e é contra
|
|
70
|
+
* ela que o gate de `input/touch-bindings` confere se o despacho reconhece tudo o que se pode oferecer.
|
|
71
|
+
* As duas coisas eram a mesma por acidente enquanto só havia um jogo.
|
|
72
|
+
*/
|
|
73
|
+
export const TOUCH_ACTS = ['left', 'right', 'up', 'down', 'action2', 'action1', 'action3', 'action4', 'start'];
|
|
74
|
+
/** Funde o mapa persistido (JSON solto do localStorage) sobre TOUCH_DEFAULT. Mantido IDÊNTICO ao original:
|
|
75
|
+
* NÃO valida chaves/valores contra TOUCH_SLOTS/TOUCH_ACTS — um JSON malformado com chaves/valores estranhos
|
|
76
|
+
* passa como está (só falha se não for um objeto). Ver a nota "bug surfaced" no retorno da extração. */
|
|
77
|
+
export function normalizeTouchMap(stored) {
|
|
78
|
+
// ⚠️ O GUARDADO PASSA PELO TRADUTOR ANTES DA FUSÃO, e a ordem é o que importa: `Object.assign` deixa o
|
|
79
|
+
// guardado VENCER o padrão, então um `b0: 'jump'` de antes do ADR-0086 sobrescreveria o `b0: 'action2'`
|
|
80
|
+
// correto e o botão da tela deixaria de fazer nada — sem erro nenhum. Aqui o nome da ação está no VALOR,
|
|
81
|
+
// não na chave, e por isso precisa de um tradutor próprio. Ver `input/vocabulary-migration.ts`.
|
|
82
|
+
const migrado = migrarMapaDeToque(stored && typeof stored === 'object' ? stored : null);
|
|
83
|
+
return Object.assign({}, TOUCH_DEFAULT, migrado || {});
|
|
84
|
+
}
|
|
85
|
+
/** UNUSED hoje — grep em game.js (`\bpadKind\b`) só acha a própria definição, nenhum chamador. Extraído porque
|
|
86
|
+
* a fronteira explicitamente listou; NÃO apagado (relatado, não conserto). Lê navigator.getGamepads() direto,
|
|
87
|
+
* no mesmo padrão de ui/settings-audio.ts / core/i18n.ts (acesso direto a navigator, sem DI) — mesmo assim,
|
|
88
|
+
* é semanticamente "ler o controle", território do agente de input/gamepad; ver a nota de fronteira no retorno. */
|
|
89
|
+
export function padKind() {
|
|
90
|
+
let kind = 'kb';
|
|
91
|
+
const pads = navigator.getGamepads ? navigator.getGamepads() : [];
|
|
92
|
+
for (const gp of pads) {
|
|
93
|
+
if (!gp)
|
|
94
|
+
continue;
|
|
95
|
+
kind = gp.mapping === 'standard' ? 'x' : 'd';
|
|
96
|
+
if (kind === 'x')
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
return kind;
|
|
100
|
+
}
|
|
101
|
+
export function initTouch(ctx) {
|
|
102
|
+
let padDesign = ctx.store.get(KEYS.padDesign, 'generic') || 'generic';
|
|
103
|
+
let padBtnMm = ctx.store.getNum(KEYS.padBtnMm, 12.5);
|
|
104
|
+
let padGapMm = ctx.store.getNum(KEYS.padGapMm, 3);
|
|
105
|
+
let padStickMm = ctx.store.getNum(KEYS.padStickMm, 18);
|
|
106
|
+
let padTravelMm = ctx.store.getNum(KEYS.padTravelMm, 4.5);
|
|
107
|
+
let padDpadMm = ctx.store.getNum(KEYS.padDpadMm, 12);
|
|
108
|
+
let padDir = ctx.store.get(KEYS.padDir, 'stick') || 'stick';
|
|
109
|
+
let _stickTravelPx = 42, _stickDeadPx = 12; // valores de arranque; applyPadPhysical() atualiza de verdade
|
|
110
|
+
const touchMap = normalizeTouchMap(ctx.store.getJSON(KEYS.touchmap, null));
|
|
111
|
+
function renderTouchMap() {
|
|
112
|
+
const el = ctx.$('#touchmap-list');
|
|
113
|
+
if (!el)
|
|
114
|
+
return;
|
|
115
|
+
el.innerHTML = TOUCH_SLOTS.map((s) => `<div class="ctrl-row"><label for="tm-${s.k}">${t(s.lbl)}</label><select id="tm-${s.k}" class="vol" data-slot="${s.k}">` +
|
|
116
|
+
// ⚠️ A OPÇÃO NASCE VAZIA e o `rotulo` entra logo abaixo por `textContent` (issue #106). Ele é a PALAVRA
|
|
117
|
+
// do jogo — `acoesDoJogo()` sai do preset —, e um jogo vive noutro repositório (ADR-0083), então este
|
|
118
|
+
// texto não é revisto por esta árvore. `value="${acao}"` fica: `acao` é o nome ABSTRATO, e a engine
|
|
119
|
+
// enumera-o em `core/actions`; é dela e não do jogo.
|
|
120
|
+
ctx.acoesDoJogo().map(({ acao }) => `<option value="${acao}"${touchMap[s.k] === acao ? ' selected' : ''}></option>`).join('') +
|
|
121
|
+
`</select></div>`).join('');
|
|
122
|
+
// As palavras do jogo, por `textContent` — que escapa por construção. A ordem casa porque é a mesma lista.
|
|
123
|
+
for (const sel of el.querySelectorAll('select[data-slot]')) {
|
|
124
|
+
const palavras = ctx.acoesDoJogo();
|
|
125
|
+
for (let i = 0; i < sel.options.length && i < palavras.length; i++) {
|
|
126
|
+
sel.options[i].textContent = palavras[i].rotulo;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
el.querySelectorAll('select[data-slot]').forEach((sel) => {
|
|
130
|
+
sel.addEventListener('change', () => {
|
|
131
|
+
const slot = sel.dataset.slot || '';
|
|
132
|
+
touchMap[slot] = sel.value;
|
|
133
|
+
ctx.store.setJSON(KEYS.touchmap, touchMap);
|
|
134
|
+
const label = sel.previousElementSibling ? sel.previousElementSibling.textContent : null;
|
|
135
|
+
// ⚠️ A palavra falada é a MESMA que a lida: sai da mesma lista que acabou de montar o `<option>`.
|
|
136
|
+
// Antes vinham de tabelas diferentes e nada obrigava as duas a concordar.
|
|
137
|
+
const escolhida = ctx.acoesDoJogo().find((x) => x.acao === sel.value);
|
|
138
|
+
ctx.srSay(t('sr.touch.slotSet', { slot: label || t('touch.slot.fallback'), acao: escolhida ? escolhida.rotulo : sel.value }));
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function openTouchCfg() {
|
|
143
|
+
const ov = ctx.$('#touchcfg');
|
|
144
|
+
if (!ov)
|
|
145
|
+
return;
|
|
146
|
+
renderTouchMap();
|
|
147
|
+
ov.hidden = false;
|
|
148
|
+
ctx.frontOverlay(ov);
|
|
149
|
+
const f = ov.querySelector('select,button');
|
|
150
|
+
if (f)
|
|
151
|
+
f.focus();
|
|
152
|
+
}
|
|
153
|
+
function closeTouchCfg() {
|
|
154
|
+
const ov = ctx.$('#touchcfg');
|
|
155
|
+
if (!ov)
|
|
156
|
+
return;
|
|
157
|
+
ov.hidden = true;
|
|
158
|
+
const b = ctx.$('#opt-touchcfg');
|
|
159
|
+
if (b)
|
|
160
|
+
b.focus();
|
|
161
|
+
}
|
|
162
|
+
function hideTouchControls(_reason) {
|
|
163
|
+
const tc = ctx.$('#touch-controls');
|
|
164
|
+
if (tc && !tc.hidden)
|
|
165
|
+
tc.hidden = true;
|
|
166
|
+
ctx.$('body')?.classList.remove('touch-mode');
|
|
167
|
+
setMinimapCorner(false);
|
|
168
|
+
}
|
|
169
|
+
function showTouchControls() {
|
|
170
|
+
// MENU ativo = sem controle virtual: dá pra tocar direto nos botões da tela. QUEM decide é o jogo.
|
|
171
|
+
if (!ctx.padAllowed())
|
|
172
|
+
return;
|
|
173
|
+
const tc = ctx.$('#touch-controls');
|
|
174
|
+
if (tc)
|
|
175
|
+
tc.hidden = false;
|
|
176
|
+
ctx.$('body')?.classList.add('touch-mode');
|
|
177
|
+
setMinimapCorner(true);
|
|
178
|
+
ctx.onTouchControlsShown?.();
|
|
179
|
+
}
|
|
180
|
+
function applyDirStyle() {
|
|
181
|
+
const st = ctx.$('#touch-stick'), cr = ctx.$('#touch-cross');
|
|
182
|
+
if (st)
|
|
183
|
+
st.hidden = padDir === 'cross';
|
|
184
|
+
if (cr)
|
|
185
|
+
cr.hidden = padDir !== 'cross';
|
|
186
|
+
const sel = ctx.$('#pad-dir');
|
|
187
|
+
if (sel && sel.value !== padDir)
|
|
188
|
+
sel.value = padDir;
|
|
189
|
+
}
|
|
190
|
+
function applyPadPhysical() {
|
|
191
|
+
const vp = ctx.viewport();
|
|
192
|
+
const r = padPxPerMm(ctx.isMobile(), vp.w, vp.h);
|
|
193
|
+
const px = computePadPhysicalPx({ btn: padBtnMm, gap: padGapMm, stick: padStickMm, travel: padTravelMm, dpad: padDpadMm }, r);
|
|
194
|
+
_stickTravelPx = px.stickTravelPx;
|
|
195
|
+
_stickDeadPx = px.stickDeadPx;
|
|
196
|
+
const S = ctx.root.style;
|
|
197
|
+
S.setProperty('--pad-btn', px.btnPx.toFixed(1) + 'px');
|
|
198
|
+
S.setProperty('--pad-diam', px.diamPx.toFixed(1) + 'px');
|
|
199
|
+
S.setProperty('--stick-knob', px.knobPx.toFixed(1) + 'px');
|
|
200
|
+
S.setProperty('--stick-base', px.basePx.toFixed(1) + 'px');
|
|
201
|
+
S.setProperty('--dpad-arm-l', px.armPx.toFixed(1) + 'px');
|
|
202
|
+
S.setProperty('--dpad-arm-w', px.armWPx.toFixed(1) + 'px');
|
|
203
|
+
S.setProperty('--dpad-span', px.spanPx.toFixed(1) + 'px');
|
|
204
|
+
const fmt = (n) => n.toFixed(1).replace('.', ',');
|
|
205
|
+
const lbl = { crianca: 'mão de criança', adulto: 'mão de adulto', inter: 'intermediário' };
|
|
206
|
+
const upd = (valId, tagId, mm, lo, hi, slId) => {
|
|
207
|
+
const v = ctx.$(valId);
|
|
208
|
+
if (v)
|
|
209
|
+
v.textContent = fmt(mm) + ' mm';
|
|
210
|
+
const t = ctx.$(tagId);
|
|
211
|
+
if (t) {
|
|
212
|
+
const w = padHandTag(mm, lo, hi);
|
|
213
|
+
t.dataset.who = w;
|
|
214
|
+
t.textContent = lbl[w];
|
|
215
|
+
}
|
|
216
|
+
const s = ctx.$(slId);
|
|
217
|
+
if (s && parseFloat(s.value) !== mm)
|
|
218
|
+
s.value = String(mm);
|
|
219
|
+
};
|
|
220
|
+
upd('#pad-size-val', '#pad-size-tag', padBtnMm, 12.5, 13, '#pad-size');
|
|
221
|
+
upd('#pad-gap-val', '#pad-gap-tag', padGapMm, 3, 4.5, '#pad-gap');
|
|
222
|
+
upd('#pad-stick-val', '#pad-stick-tag', padStickMm, 17, 19, '#pad-stick');
|
|
223
|
+
upd('#pad-travel-val', '#pad-travel-tag', padTravelMm, 4, 5.5, '#pad-travel');
|
|
224
|
+
upd('#pad-dpad-val', '#pad-dpad-tag', padDpadMm, 12, 14, '#pad-dpad');
|
|
225
|
+
}
|
|
226
|
+
function setPadMm(o) {
|
|
227
|
+
if (o.btn != null)
|
|
228
|
+
padBtnMm = o.btn;
|
|
229
|
+
if (o.gap != null)
|
|
230
|
+
padGapMm = o.gap;
|
|
231
|
+
if (o.stick != null)
|
|
232
|
+
padStickMm = o.stick;
|
|
233
|
+
if (o.travel != null)
|
|
234
|
+
padTravelMm = o.travel;
|
|
235
|
+
if (o.dpad != null)
|
|
236
|
+
padDpadMm = o.dpad;
|
|
237
|
+
ctx.store.set(KEYS.padBtnMm, padBtnMm);
|
|
238
|
+
ctx.store.set(KEYS.padGapMm, padGapMm);
|
|
239
|
+
ctx.store.set(KEYS.padStickMm, padStickMm);
|
|
240
|
+
ctx.store.set(KEYS.padTravelMm, padTravelMm);
|
|
241
|
+
ctx.store.set(KEYS.padDpadMm, padDpadMm);
|
|
242
|
+
applyPadPhysical();
|
|
243
|
+
}
|
|
244
|
+
function applyPadDesign(d) {
|
|
245
|
+
if (d && PAD_DESIGNS[d])
|
|
246
|
+
padDesign = d;
|
|
247
|
+
ctx.store.set(KEYS.padDesign, padDesign);
|
|
248
|
+
const set = PAD_DESIGNS[padDesign] || PAD_DESIGNS.generic;
|
|
249
|
+
const diamond = ctx.$('#pad-diamond');
|
|
250
|
+
if (diamond) {
|
|
251
|
+
diamond.querySelectorAll('.pad-b').forEach((b) => {
|
|
252
|
+
const s = set[b.dataset.btn || ''];
|
|
253
|
+
if (s) {
|
|
254
|
+
b.textContent = s[0];
|
|
255
|
+
b.style.background = s[1];
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
ctx.onPadDesignApplied?.();
|
|
260
|
+
return padDesign;
|
|
261
|
+
}
|
|
262
|
+
// --- wiring: #touchcfg (painel "Botões de tela touch") + o botão que o abre/fecha (#opt-touchcfg no painel
|
|
263
|
+
// de Movimento) — todo elemento abaixo mora dentro (ou controla) o overlay #touchcfg. ---
|
|
264
|
+
const touchCfgBtn = ctx.$('#opt-touchcfg');
|
|
265
|
+
if (touchCfgBtn)
|
|
266
|
+
touchCfgBtn.addEventListener('click', openTouchCfg);
|
|
267
|
+
const touchCfgClose = ctx.$('#touchcfg-close');
|
|
268
|
+
if (touchCfgClose)
|
|
269
|
+
touchCfgClose.addEventListener('click', closeTouchCfg);
|
|
270
|
+
const padDirSel = ctx.$('#pad-dir');
|
|
271
|
+
if (padDirSel) {
|
|
272
|
+
padDirSel.value = padDir;
|
|
273
|
+
padDirSel.addEventListener('change', () => {
|
|
274
|
+
padDir = padDirSel.value;
|
|
275
|
+
ctx.store.set(KEYS.padDir, padDir);
|
|
276
|
+
applyDirStyle();
|
|
277
|
+
ctx.srSay(t('sr.touch.dirSet', { tipo: t(padDir === 'cross' ? 'touch.dir.cross' : 'touch.dir.stick') }));
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const padSizeEl = ctx.$('#pad-size');
|
|
281
|
+
if (padSizeEl)
|
|
282
|
+
padSizeEl.addEventListener('input', () => setPadMm({ btn: parseFloat(padSizeEl.value) }));
|
|
283
|
+
const padGapEl = ctx.$('#pad-gap');
|
|
284
|
+
if (padGapEl)
|
|
285
|
+
padGapEl.addEventListener('input', () => setPadMm({ gap: parseFloat(padGapEl.value) }));
|
|
286
|
+
const padStickEl = ctx.$('#pad-stick');
|
|
287
|
+
if (padStickEl)
|
|
288
|
+
padStickEl.addEventListener('input', () => setPadMm({ stick: parseFloat(padStickEl.value) }));
|
|
289
|
+
const padTravelEl = ctx.$('#pad-travel');
|
|
290
|
+
if (padTravelEl)
|
|
291
|
+
padTravelEl.addEventListener('input', () => setPadMm({ travel: parseFloat(padTravelEl.value) }));
|
|
292
|
+
const padDpadEl = ctx.$('#pad-dpad');
|
|
293
|
+
if (padDpadEl)
|
|
294
|
+
padDpadEl.addEventListener('input', () => setPadMm({ dpad: parseFloat(padDpadEl.value) }));
|
|
295
|
+
const padPresetChild = ctx.$('#pad-preset-child');
|
|
296
|
+
if (padPresetChild)
|
|
297
|
+
padPresetChild.addEventListener('click', () => { setPadMm({ btn: 12, gap: 2.5, stick: 16.5, travel: 4, dpad: 11.5 }); ctx.srSay(t('sr.touch.presetChild')); });
|
|
298
|
+
const padPresetAdult = ctx.$('#pad-preset-adult');
|
|
299
|
+
if (padPresetAdult)
|
|
300
|
+
padPresetAdult.addEventListener('click', () => { setPadMm({ btn: 14, gap: 4.5, stick: 20, travel: 5.5, dpad: 14 }); ctx.srSay(t('sr.touch.presetAdult')); });
|
|
301
|
+
addEventListener('resize', applyPadPhysical); // recalcula os px ao girar/redimensionar; os mm são fixos
|
|
302
|
+
// estado inicial (equivalente aos `applyPadDesign(padDesign); applyPadPhysical(); applyDirStyle();` de boot no game.js)
|
|
303
|
+
applyPadDesign();
|
|
304
|
+
applyPadPhysical();
|
|
305
|
+
applyDirStyle();
|
|
306
|
+
return {
|
|
307
|
+
renderTouchMap, openTouchCfg, closeTouchCfg, hideTouchControls, showTouchControls,
|
|
308
|
+
applyDirStyle, applyPadPhysical, setPadMm, applyPadDesign,
|
|
309
|
+
getPadDesign: () => padDesign,
|
|
310
|
+
getTouchMap: () => touchMap,
|
|
311
|
+
getStickTravelPx: () => _stickTravelPx,
|
|
312
|
+
getStickDeadPx: () => _stickDeadPx,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type Action } from '../core/actions.js';
|
|
3
|
+
/**
|
|
4
|
+
* QUANTOS LUGARES CADA TRANSPORTE REAL TEM, e de onde cada número vem. Estavam medidos e viviam só no teste;
|
|
5
|
+
* um número que só existe num teste não chega a criança nenhuma.
|
|
6
|
+
*
|
|
7
|
+
* · `gamepad: 17` — a Gamepad API «standard» declara dezassete botões. Não é escolha nossa.
|
|
8
|
+
* · `toque: 9` — `TOUCH_DEFAULT` (input/devices) nomeia nove slots, e são nove desde sempre.
|
|
9
|
+
* · `teclado: ACTIONS.length` — ⚠️ e este é o que precisou de decisão.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ O NÚMERO DO TECLADO NÃO É O NÚMERO DE TECLAS, e também não é o número de ligações do esquema atual. Não
|
|
12
|
+
* é o de teclas porque cem teclas não são cem lugares que uma criança encontra e lembra; não é o do esquema
|
|
13
|
+
* porque o esquema cresce, e o limite passaria a ser a configuração e não o aparelho.
|
|
14
|
+
*
|
|
15
|
+
* É `ACTIONS.length` porque o que limita um teclado, na prática deste produto, é O VOCABULÁRIO QUE A ENGINE
|
|
16
|
+
* SABE NOMEAR: um transporte que cobre todas as posições que existem não tem como ser o curto. Derivado e não
|
|
17
|
+
* escrito à mão de propósito — quando o vocabulário crescer (foi de nove para catorze em 2026-09-06), este
|
|
18
|
+
* número cresce com ele e nunca passa a mentir.
|
|
19
|
+
*/
|
|
20
|
+
export declare const LUGARES: Readonly<{
|
|
21
|
+
gamepad: 17;
|
|
22
|
+
toque: 9;
|
|
23
|
+
teclado: 14;
|
|
24
|
+
}>;
|
|
25
|
+
/** Como se descobre que cada transporte está aqui AGORA. Injetado: nenhuma destas perguntas é pura. */
|
|
26
|
+
export interface Disponibilidade {
|
|
27
|
+
gamepad: () => boolean;
|
|
28
|
+
toque: () => boolean;
|
|
29
|
+
teclado: () => boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Os três transportes que esta engine sabe oferecer hoje.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ A DETECÇÃO DO TECLADO É IMPRECISA E ISSO É ACEITÁVEL — mas só porque a tela que consome isto INFORMA em
|
|
35
|
+
* vez de RECUSAR. Não há API que diga «há um teclado físico ligado»; o que o projeto já usa para a mesma
|
|
36
|
+
* pergunta é `pointer:coarse && hover:none` (o `isCoarsePointer` do `game/session`), e um tablet COM teclado
|
|
37
|
+
* responde «toque» a isso. Se a tela barrasse, esse tablet levaria uma recusa falsa num jogo que ele joga.
|
|
38
|
+
* Como ela apenas diz o que falta e deixa continuar, o erro custa uma frase a mais e nunca uma porta fechada.
|
|
39
|
+
*/
|
|
40
|
+
export declare function transportesPadrao(d: Disponibilidade): Transport[];
|
|
41
|
+
/** Um transporte, como ele se declara. Zero significado: quantos lugares, e se está aqui agora. */
|
|
42
|
+
export interface Transport {
|
|
43
|
+
/** Identificador estável, para preferências salvas. Nunca chega a uma pessoa. */
|
|
44
|
+
readonly id: string;
|
|
45
|
+
/** Quantos lugares ATRIBUÍVEIS. É o número que a aritmética da garantia usa. */
|
|
46
|
+
readonly slots: number;
|
|
47
|
+
/**
|
|
48
|
+
* Está disponível NESTE aparelho, AGORA? Função e não valor: um controle é ligado no meio da partida, e
|
|
49
|
+
* uma tela de toque aparece quando a criança gira o tablet.
|
|
50
|
+
*/
|
|
51
|
+
readonly available: () => boolean;
|
|
52
|
+
}
|
|
53
|
+
/** Este transporte carrega este conjunto de ações? Aritmética, como o ADR-0079 §3 a descreve. */
|
|
54
|
+
export declare function carries(t: Transport, acoes: readonly Action[]): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Os transportes DISPONÍVEIS que carregam este conjunto.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ Disponibilidade e capacidade são conferidas nesta ordem de propósito: um transporte que caberia mas não
|
|
59
|
+
* está ligado não é resposta para uma criança que está à frente do aparelho agora.
|
|
60
|
+
*/
|
|
61
|
+
export declare function carriedBy(lista: readonly Transport[], acoes: readonly Action[]): Transport[];
|
|
62
|
+
/**
|
|
63
|
+
* A garantia do ADR-0079 §3, como pergunta de sim ou não.
|
|
64
|
+
*
|
|
65
|
+
* ⚠️ CONJUNTO VAZIO DE AÇÕES DEVOLVE `false`, e não `true` por vacuidade. Um jogo que não declara ação
|
|
66
|
+
* nenhuma não é um jogo que qualquer transporte serve — é um jogo que ninguém consegue jogar, e
|
|
67
|
+
* `actionSetProblems` já o reprova. Devolver `true` aqui esconderia esse defeito atrás desta função.
|
|
68
|
+
*/
|
|
69
|
+
export declare function reachable(lista: readonly Transport[], acoes: readonly Action[]): boolean;
|
|
70
|
+
/** O que a tela de seleção precisa dizer, e o que ela precisa saber para o dizer. */
|
|
71
|
+
export interface Alcance {
|
|
72
|
+
/** Verdadeiro = pelo menos um transporte disponível carrega o conjunto. */
|
|
73
|
+
readonly ok: boolean;
|
|
74
|
+
/** Quantas ações o jogo pede. */
|
|
75
|
+
readonly pedidas: number;
|
|
76
|
+
/** Os que serviriam se estivessem ligados — a informação acionável: «ligue um controle». */
|
|
77
|
+
readonly serviriamSeLigados: readonly string[];
|
|
78
|
+
/** Os disponíveis que NÃO cabem, com quantos lugares têm. Para a frase dizer o número. */
|
|
79
|
+
readonly curtos: readonly {
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly slots: number;
|
|
82
|
+
}[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Mede o alcance ANTES de a criança começar.
|
|
86
|
+
*
|
|
87
|
+
* ⚠️ Devolve DADO e não texto. A frase é da interface e tem de passar por `t()`; devolver português daqui
|
|
88
|
+
* repetiria o defeito que o `PADWIZ_STEPS` acabou de deixar de cometer.
|
|
89
|
+
*/
|
|
90
|
+
export declare function alcance(lista: readonly Transport[], acoes: readonly Action[]): Alcance;
|