@the-inclusionist/engine 7.0.1 → 8.0.0-rc.2
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/README.md +5 -3
- package/app/css/style.css +22 -5
- package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-ar.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-br.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-ca.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-cl.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-co.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-mx.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-us-modern.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-us-trad.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +64 -2
- package/dist-pkg/boot/create-game.d.ts +107 -4
- package/dist-pkg/boot/create-game.js +337 -18
- package/dist-pkg/core/constants.d.ts +0 -28
- package/dist-pkg/core/constants.js +34 -17
- package/dist-pkg/core/contract.d.ts +99 -0
- package/dist-pkg/core/contract.js +78 -0
- package/dist-pkg/core/entity.d.ts +47 -4
- package/dist-pkg/core/layers.d.ts +18 -0
- package/dist-pkg/core/layers.js +18 -0
- package/dist-pkg/core/rng.js +14 -4
- package/dist-pkg/core/route.d.ts +42 -0
- package/dist-pkg/core/route.js +158 -0
- package/dist-pkg/core/state.d.ts +10 -1
- package/dist-pkg/core/state.js +13 -0
- package/dist-pkg/educational/adaptive-engine.d.ts +65 -0
- package/dist-pkg/educational/adaptive-engine.js +117 -0
- package/dist-pkg/educational/segment-bar.d.ts +96 -0
- package/dist-pkg/educational/segment-bar.js +89 -0
- package/dist-pkg/i18n/en.js +44 -0
- package/dist-pkg/i18n/es.js +44 -0
- package/dist-pkg/i18n/pt.js +60 -0
- package/dist-pkg/input/default-bindings.d.ts +40 -0
- package/dist-pkg/input/default-bindings.js +151 -9
- package/dist-pkg/input/gamepad.d.ts +33 -12
- package/dist-pkg/input/gamepad.js +103 -16
- package/dist-pkg/input/keyboard-runtime.d.ts +6 -8
- package/dist-pkg/input/keyboard-runtime.js +24 -5
- package/dist-pkg/input/keyboard.d.ts +42 -0
- package/dist-pkg/input/keyboard.js +87 -17
- package/dist-pkg/input/keydown.d.ts +40 -2
- package/dist-pkg/input/keydown.js +29 -6
- package/dist-pkg/input/latch-edge.d.ts +28 -0
- package/dist-pkg/input/latch-edge.js +45 -0
- package/dist-pkg/input/latch-scope.d.ts +70 -0
- package/dist-pkg/input/latch-scope.js +110 -0
- package/dist-pkg/input/latch-store.d.ts +45 -0
- package/dist-pkg/input/latch-store.js +74 -0
- package/dist-pkg/input/latch-sync.d.ts +47 -0
- package/dist-pkg/input/latch-sync.js +43 -0
- package/dist-pkg/input/origem-sintetica.d.ts +44 -0
- package/dist-pkg/input/origem-sintetica.js +60 -0
- package/dist-pkg/input/pad-defaults.d.ts +22 -0
- package/dist-pkg/input/pad-defaults.js +34 -0
- package/dist-pkg/input/pointer.d.ts +61 -0
- package/dist-pkg/input/pointer.js +71 -0
- package/dist-pkg/input/state.d.ts +86 -1
- package/dist-pkg/input/state.js +128 -1
- package/dist-pkg/input/touch-bindings.d.ts +21 -2
- package/dist-pkg/input/touch-bindings.js +15 -3
- package/dist-pkg/input/touch.js +9 -1
- package/dist-pkg/input/transporte-em-uso.d.ts +101 -0
- package/dist-pkg/input/transporte-em-uso.js +130 -0
- package/dist-pkg/input/transports.d.ts +88 -2
- package/dist-pkg/input/transports.js +60 -5
- package/dist-pkg/input/vocabulary-migration.d.ts +18 -7
- package/dist-pkg/platform/audio-earcons.d.ts +29 -2
- package/dist-pkg/platform/audio-earcons.js +10 -0
- package/dist-pkg/platform/audio-nav.d.ts +1 -1
- package/dist-pkg/platform/audio-sonar.d.ts +149 -9
- package/dist-pkg/platform/audio-sonar.js +232 -21
- package/dist-pkg/platform/guide-intensity.d.ts +40 -0
- package/dist-pkg/platform/guide-intensity.js +73 -0
- package/dist-pkg/platform/pesados-catalogo.d.ts +14 -0
- package/dist-pkg/platform/pesados-catalogo.js +177 -0
- package/dist-pkg/platform/pesados.d.ts +31 -0
- package/dist-pkg/platform/pesados.js +75 -0
- package/dist-pkg/platform/storage.d.ts +30 -0
- package/dist-pkg/platform/storage.js +35 -0
- package/dist-pkg/platform/tts.js +16 -1
- package/dist-pkg/platform/voice-plan.d.ts +109 -0
- package/dist-pkg/platform/voice-plan.js +156 -0
- package/dist-pkg/platform/vozes-prontas.d.ts +28 -0
- package/dist-pkg/platform/vozes-prontas.js +61 -0
- package/dist-pkg/render/draw.d.ts +1 -1
- package/dist-pkg/render/draw.js +15 -5
- package/dist-pkg/render/viz-axes.d.ts +17 -0
- package/dist-pkg/render/viz-axes.js +19 -0
- package/dist-pkg/render/viz-setters.d.ts +34 -2
- package/dist-pkg/render/viz-setters.js +189 -33
- package/dist-pkg/render/wheelchair-sprites.d.ts +11 -3
- package/dist-pkg/render/wheelchair-sprites.js +10 -4
- package/dist-pkg/ui/dom.js +20 -2
- package/dist-pkg/ui/fonts.d.ts +71 -1
- package/dist-pkg/ui/fonts.js +111 -8
- package/dist-pkg/ui/latch-refusal.d.ts +32 -0
- package/dist-pkg/ui/latch-refusal.js +60 -0
- package/dist-pkg/ui/layout.d.ts +32 -0
- package/dist-pkg/ui/layout.js +64 -1
- package/dist-pkg/ui/motion-scene.d.ts +41 -0
- package/dist-pkg/ui/motion-scene.js +76 -0
- package/dist-pkg/ui/panel-shell.d.ts +53 -0
- package/dist-pkg/ui/panel-shell.js +103 -0
- package/dist-pkg/ui/pause-icons.d.ts +168 -20
- package/dist-pkg/ui/pause-icons.js +315 -45
- package/dist-pkg/ui/reach-notice.js +8 -0
- package/dist-pkg/ui/settings-audio.d.ts +14 -1
- package/dist-pkg/ui/settings-audio.js +43 -3
- package/dist-pkg/ui/settings-controls.d.ts +66 -6
- package/dist-pkg/ui/settings-controls.js +179 -17
- package/dist-pkg/ui/settings-empathy.d.ts +15 -0
- package/dist-pkg/ui/settings-empathy.js +2 -0
- package/dist-pkg/ui/settings-motion.d.ts +25 -19
- package/dist-pkg/ui/settings-motion.js +32 -19
- package/dist-pkg/ui/settings-motor.d.ts +101 -3
- package/dist-pkg/ui/settings-motor.js +136 -9
- package/dist-pkg/ui/settings-typo.d.ts +49 -5
- package/dist-pkg/ui/settings-typo.js +74 -23
- package/dist-pkg/ui/settings-visual.d.ts +21 -1
- package/dist-pkg/ui/settings-visual.js +48 -4
- package/dist-pkg/ui/shell.d.ts +13 -3
- package/dist-pkg/ui/shell.js +3 -4
- package/dist-pkg/ui/simulation-refusal.d.ts +32 -0
- package/dist-pkg/ui/simulation-refusal.js +57 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +48 -0
- package/dist-pkg/ui/visual-axes-panel.js +95 -0
- package/dist-pkg/ui/webcam.js +6 -1
- package/docs/CREDITS.md +22 -1
- package/docs/LICENSES.md +170 -132
- package/package.json +25 -4
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
|
@@ -10,7 +10,17 @@ import { t } from '../core/i18n.js';
|
|
|
10
10
|
import { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
|
|
11
11
|
import { migrarMapaDeControle } from './vocabulary-migration.js';
|
|
12
12
|
import { GAMEPAD_STANDARD } from './default-bindings.js';
|
|
13
|
+
import { tabelaDoPad } from './pad-defaults.js';
|
|
13
14
|
import { padCur, padPrevAct, padPrevStart, PAD_DEAD } from './state.js';
|
|
15
|
+
// ⚠️ O `oneButton` ENTRA POR IMPORT, e não pelo `ctx` — ao contrário de `input/keydown`, que o recebe por
|
|
16
|
+
// getter. A diferença não é de gosto: o `keydown` foi extraído quando o `oneButton` era um `let` do
|
|
17
|
+
// `game.js`, e a regra da casa manda o que o JOGO reatribui entrar por getter. Hoje ele é `core/state`,
|
|
18
|
+
// ou seja da ENGINE — e um getter obrigaria cada consumidor a lembrar-se de o passar.
|
|
19
|
+
//
|
|
20
|
+
// ⚠️ E ESQUECER É EXATAMENTE O DEFEITO QUE ISTO CONSERTA (issue #120). Um campo opcional no ctx faria a
|
|
21
|
+
// acomodação existir só nos jogos onde alguém se lembrou dela, que é o argumento M3 do ADR-0077 com outro
|
|
22
|
+
// substantivo. Binding vivo de `core/state`: não há por onde falhar.
|
|
23
|
+
import * as estadoDoJogo from '../core/state.js';
|
|
14
24
|
import * as store from '../platform/storage.js';
|
|
15
25
|
function bindingAt(map, key) {
|
|
16
26
|
const v = map[key];
|
|
@@ -63,7 +73,7 @@ export function bindActive(gp, bd) {
|
|
|
63
73
|
/** Ações do frame para este gamepad. `custom` = mapa salvo pelo wizard para este `gp.id` (null/`_skip` = usa o
|
|
64
74
|
* mapa PADRÃO da Gamepad API "standard": 0=pulo · 1=especial · 2/5/7=correr · 3=troca · 9=START). Direções
|
|
65
75
|
* custom caem de volta em stdDirs quando o binding do usuário não está ativo (D-pad/stick continuam vivos). */
|
|
66
|
-
export function padActions(gp, custom) {
|
|
76
|
+
export function padActions(gp, custom, tabela = GAMEPAD_STANDARD) {
|
|
67
77
|
if (custom && !custom._skip) {
|
|
68
78
|
const A = (k) => bindActive(gp, bindingAt(custom, k));
|
|
69
79
|
const sd = stdDirs(gp);
|
|
@@ -82,7 +92,12 @@ export function padActions(gp, custom) {
|
|
|
82
92
|
// correr, enquanto a tabela declara R1 como `rightShoulder` e R2 como `rightTrigger`. O ADR-0086 registrou
|
|
83
93
|
// esta mudança como o asterisco do seu «zero movimento»: nenhum VERBO muda de botão, mas `run` perde dois
|
|
84
94
|
// dos seus três. Quem usava R1 para correr sente — e é o preço de os quatro ombros existirem.
|
|
85
|
-
|
|
95
|
+
// 📌 A TABELA CHEGA POR PARÂMETRO desde o ADR-0115: é a fábrica da engine COM o padrão deste jogo por cima,
|
|
96
|
+
// resolvida em `input/pad-defaults`. O padrão da assinatura é o da engine, então quem chamava com dois
|
|
97
|
+
// argumentos continua a ler exactamente o que lia.
|
|
98
|
+
// ⚠️ E ela só decide neste ramo, que é o certo: o ramo de cima é o mapa que a CRIANÇA gravou no assistente,
|
|
99
|
+
// e o padrão de um jogo não se sobrepõe a uma escolha dela.
|
|
100
|
+
const B = tabela;
|
|
86
101
|
const at = (a) => { const i = B[a]; return typeof i === 'number' ? b(i) : false; };
|
|
87
102
|
return {
|
|
88
103
|
left: sd.left, right: sd.right, up: sd.up, down: sd.down,
|
|
@@ -117,6 +132,56 @@ export function padActions(gp, custom) {
|
|
|
117
132
|
* posição não tem o que mapear nela — e perguntar produziria um passo mudo, ou pior, um passo a dizer
|
|
118
133
|
* `action7` em voz alta.
|
|
119
134
|
*/
|
|
135
|
+
/**
|
|
136
|
+
* AS POSIÇÕES QUE O MODO DE UM BOTÃO NÃO CORTA. Pausar é a SAÍDA, não uma jogada.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ Cortar o START prenderia a criança dentro da partida — é o mesmo raciocínio que põe «a saída
|
|
139
|
+
* primeiro» no ADR-0044 e que fez a armadilha de foco existir no ADR-0090. Uma acomodação que tranca não
|
|
140
|
+
* é acomodação. Os derivados (`_start`, `_pause`) também passam: eles descrevem o que a raiz faz com
|
|
141
|
+
* estas duas posições, não uma terceira.
|
|
142
|
+
*/
|
|
143
|
+
const FORA_DO_CORTE = new Set(['start', 'select', '_start', '_pause']);
|
|
144
|
+
/**
|
|
145
|
+
* O MODO DE UM BOTÃO, APLICADO AO CONTROLE — a metade que faltava da empatia motora (issue #120).
|
|
146
|
+
*
|
|
147
|
+
* ⚠️ ELE VALIA SÓ NO TECLADO. `input/keydown.ts:407` solta todas as outras teclas de jogo quando uma nova
|
|
148
|
+
* chega com o modo ligado; `pollPads` não tinha equivalente nenhum, e `grep oneButton` neste ficheiro
|
|
149
|
+
* devolvia zero. Uma criança que ligasse o modo e tivesse um controle na mão **não estava no modo** — sem
|
|
150
|
+
* erro, sem aviso, sem sintoma, porque as definições continuavam a dizer que estava ligado.
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ AS DIREÇÕES CONTAM, e é isso que torna a regra fiel ao teclado: lá, `isGameKeyCode` inclui as teclas
|
|
153
|
+
* de `p.ctrl`, que são as quatro direções — andar e pular não coexistem. Um filtro que poupasse as
|
|
154
|
+
* direções seria mais confortável e estaria a simular outra deficiência.
|
|
155
|
+
*
|
|
156
|
+
* ⚠️ E A ESCOLHA DE QUEM SOBREVIVE É DIFERENTE DA DO TECLADO, POR NECESSIDADE. No teclado a chegada nova
|
|
157
|
+
* ganha, porque HÁ uma chegada: o evento diz qual é. Um controle é lido por SONDAGEM — o que chega é um
|
|
158
|
+
* retrato, sem ordem. Então mantém-se a que já valia, e só quando ela solta é que a próxima assume. É o
|
|
159
|
+
* que impede o botão de correr de ser cortado porque o polegar encostou noutro, e é a mesma leitura de
|
|
160
|
+
* «segurar» que o ADR-0077 dá.
|
|
161
|
+
*
|
|
162
|
+
* A POLÍTICA é a mesma do `keydown`; a IMPLEMENTAÇÃO não pode ser partilhada hoje porque as formas do
|
|
163
|
+
* estado diferem — lá é um `Set` de códigos de tecla, aqui é um retrato de booleanos por posição. Unificar
|
|
164
|
+
* as duas é trabalho à parte, e escrevê-lo aqui é a alternativa a fingir que não há duas.
|
|
165
|
+
*/
|
|
166
|
+
export function umBotaoPorVez(
|
|
167
|
+
// ⚠️ O ANTERIOR É TIPADO PELO QUE ESTA FUNÇÃO LÊ, e não por `PadActions`: o `padPrevAct[gi]` do laço é
|
|
168
|
+
// `PadState`, mais frouxo, e exigir a forma completa obrigaria o chamador a um molde que não descreve o
|
|
169
|
+
// que se passa aqui — só se pergunta «esta chave estava em baixo?».
|
|
170
|
+
anterior, atual, ligado) {
|
|
171
|
+
if (!ligado)
|
|
172
|
+
return atual;
|
|
173
|
+
const cortaveis = Object.keys(atual).filter((k) => !FORA_DO_CORTE.has(k));
|
|
174
|
+
const ativas = cortaveis.filter((k) => atual[k] === true);
|
|
175
|
+
if (ativas.length <= 1)
|
|
176
|
+
return atual;
|
|
177
|
+
// A que já valia tem prioridade; sem nenhuma, a primeira do retrato assume.
|
|
178
|
+
const mantida = ativas.find((k) => anterior[k] === true) ?? ativas[0];
|
|
179
|
+
const saida = { ...atual };
|
|
180
|
+
for (const k of ativas)
|
|
181
|
+
if (k !== mantida)
|
|
182
|
+
saida[k] = false;
|
|
183
|
+
return saida;
|
|
184
|
+
}
|
|
120
185
|
export const PADWIZ_ORDER = [
|
|
121
186
|
// Direções primeiro: são o que a criança encontra sem pensar, e acertar as quatro dá confiança para as
|
|
122
187
|
// outras dez.
|
|
@@ -161,13 +226,17 @@ export function initGamepad(ctx) {
|
|
|
161
226
|
}
|
|
162
227
|
return _padMaps.get(id) ?? null;
|
|
163
228
|
}
|
|
164
|
-
function actionsFor(gp) { return padActions(gp, padMapFor(gp.id)); }
|
|
229
|
+
function actionsFor(gp, tabela) { return padActions(gp, padMapFor(gp.id), tabela); }
|
|
165
230
|
// ----- wizard: anúncio + demo animada (DOM-facing, thin) -----
|
|
166
|
-
|
|
231
|
+
// ⚠️ O PARÂMETRO CHAMAVA-SE `t`, E ERA ELE QUE FECHAVA A PORTA. Dentro desta função o `t` do
|
|
232
|
+
// `core/i18n` estava sombreado, então traduzir uma frase aqui era impossível sem primeiro reparar no
|
|
233
|
+
// sombreamento — e não há erro nenhum a apontá-lo. Foi assim que cinco frases em português cru ficaram a
|
|
234
|
+
// falar dentro do motor (#123): não por decisão, por um nome.
|
|
235
|
+
function wizSay(frase) {
|
|
167
236
|
const el = ctx.$('#padwiz-prompt');
|
|
168
237
|
if (el)
|
|
169
|
-
el.textContent =
|
|
170
|
-
ctx.srSay(
|
|
238
|
+
el.textContent = frase;
|
|
239
|
+
ctx.srSay(frase);
|
|
171
240
|
}
|
|
172
241
|
function wizDemo(k) {
|
|
173
242
|
const d = ctx.$('#padwiz-demo');
|
|
@@ -224,11 +293,12 @@ export function initGamepad(ctx) {
|
|
|
224
293
|
return; // fechou ao avançar
|
|
225
294
|
const acao = PADWIZ_ORDER[padWiz.step];
|
|
226
295
|
const rotulo = ctx.rotuloDaAcao(acao);
|
|
227
|
-
wizSay((padWiz.step + 1
|
|
296
|
+
wizSay(t('pad.wiz.step', { n: padWiz.step + 1, total: PADWIZ_ORDER.length, acao: rotulo }));
|
|
228
297
|
wizDemo(acao); // demonstração animada do que a ação FAZ
|
|
229
298
|
const pr = ctx.$('#padwiz-progress');
|
|
299
|
+
// O travessão da lista vazia fica cru de propósito: é pontuação, não idioma.
|
|
230
300
|
if (pr)
|
|
231
|
-
pr.textContent = '
|
|
301
|
+
pr.textContent = t('pad.wiz.mapped', { lista: Object.keys(padWiz.map).join(' · ') || '—' });
|
|
232
302
|
}
|
|
233
303
|
function wizBind(bd) {
|
|
234
304
|
if (!padWiz)
|
|
@@ -246,7 +316,7 @@ export function initGamepad(ctx) {
|
|
|
246
316
|
ov.hidden = false;
|
|
247
317
|
ctx.frontOverlay(ov);
|
|
248
318
|
padWiz = { gi: -1, id: '', step: -1, base: null, map: {}, release: false, baseWait: false, axTrack: null, timer: null };
|
|
249
|
-
wizSay('
|
|
319
|
+
wizSay(t('pad.wiz.pressAny'));
|
|
250
320
|
wizDemo(null);
|
|
251
321
|
const pr = ctx.$('#padwiz-progress');
|
|
252
322
|
if (pr)
|
|
@@ -261,7 +331,7 @@ export function initGamepad(ctx) {
|
|
|
261
331
|
ov.hidden = false;
|
|
262
332
|
ctx.frontOverlay(ov);
|
|
263
333
|
padWiz = { gi: gp.index, id: gp.id, step: -1, base: null, map: {}, release: false, baseWait: true, axTrack: null, timer: null };
|
|
264
|
-
wizSay('
|
|
334
|
+
wizSay(t('pad.wiz.detected', { id: gp.id }));
|
|
265
335
|
wizDemo(null);
|
|
266
336
|
const pr = ctx.$('#padwiz-progress');
|
|
267
337
|
if (pr)
|
|
@@ -316,7 +386,7 @@ export function initGamepad(ctx) {
|
|
|
316
386
|
padWiz.gi = gp.index;
|
|
317
387
|
padWiz.id = gp.id;
|
|
318
388
|
padWiz.baseWait = true;
|
|
319
|
-
wizSay('
|
|
389
|
+
wizSay(t('pad.wiz.releaseAll', { id: gp.id }));
|
|
320
390
|
break;
|
|
321
391
|
}
|
|
322
392
|
}
|
|
@@ -405,8 +475,18 @@ export function initGamepad(ctx) {
|
|
|
405
475
|
openPadWizFor(gp);
|
|
406
476
|
return;
|
|
407
477
|
}
|
|
408
|
-
const cur = actionsFor(gp);
|
|
409
478
|
const prev = padPrevAct[gi] || {};
|
|
479
|
+
// ⚠️ O ASSENTO SUBIU PARA AQUI, e a razão é o ADR-0115: a tabela de botões deste jogo é declarada POR
|
|
480
|
+
// ASSENTO, e ela é lida dentro do `actionsFor`. Enquanto o `owner` só se resolvia lá em baixo, por ramo,
|
|
481
|
+
// a leitura acontecia antes de se saber de quem era o controle — e um padrão por assento chegava tarde.
|
|
482
|
+
// 📌 Os ramos abaixo passaram a usar esta constante em vez de recalcularem a mesma linha três vezes.
|
|
483
|
+
const players = ctx.getPlayers();
|
|
484
|
+
const owner = players.findIndex((p) => p.pad === gi);
|
|
485
|
+
// ⚠️ E O CONTROLE AINDA NÃO ATRIBUÍDO (`owner < 0`) LÊ O ASSENTO 0, e não «nenhum»: ele está a produzir
|
|
486
|
+
// arestas na tela do título, e um mapa vazio ali deixaria a criança sem como escolher o próprio jogo.
|
|
487
|
+
// A EMPATIA MOTORA APLICADA AO CONTROLE (issue #120). Sem esta linha, uma criança com o modo de
|
|
488
|
+
// um botão ligado e um pad na mão NÃO ESTAVA no modo — e nada em lado nenhum o dizia.
|
|
489
|
+
const cur = umBotaoPorVez(prev, actionsFor(gp, tabelaDoPad(ctx.getNumPlayers(), owner < 0 ? 0 : owner)), estadoDoJogo.oneButton);
|
|
410
490
|
if (ctx.isTouchMode() && (cur.left || cur.right || cur.up || cur.down || cur.action2 || cur.action1 || cur.action4 || cur.action3 || cur._start)) {
|
|
411
491
|
ctx.hideTouchControls(); // botão físico usado -> some o gamepad virtual (mesma regra do teclado)
|
|
412
492
|
}
|
|
@@ -429,11 +509,9 @@ export function initGamepad(ctx) {
|
|
|
429
509
|
// de propósito: numa cena que este módulo não conheça (um mapa, uma tela de resultados), o controle
|
|
430
510
|
// deve navegar como no título — que é o comportamento seguro — em vez de não fazer nada.
|
|
431
511
|
const rodando = ctx.mundoRodando(), pausado = ctx.menuDePausa();
|
|
432
|
-
const players = ctx.getPlayers();
|
|
433
512
|
if (!rodando && !pausado) {
|
|
434
513
|
const k = { yes: edge('action2') || startEdge, no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
|
|
435
514
|
const any = k.yes || k.no || k.up || k.down || k.left || k.right;
|
|
436
|
-
const owner = players.findIndex((p) => p.pad === gi);
|
|
437
515
|
if (ctx.getNumPlayers() > 1 && owner > 0) {
|
|
438
516
|
if (any)
|
|
439
517
|
ctx.srSay(t('sr.title.waitP1'));
|
|
@@ -444,7 +522,6 @@ export function initGamepad(ctx) {
|
|
|
444
522
|
continue;
|
|
445
523
|
}
|
|
446
524
|
if (pausado) {
|
|
447
|
-
const owner = players.findIndex((p) => p.pad === gi);
|
|
448
525
|
const pi = owner < 0 ? 0 : owner;
|
|
449
526
|
if (pauseEdge) {
|
|
450
527
|
ctx.retomar();
|
|
@@ -464,7 +541,6 @@ export function initGamepad(ctx) {
|
|
|
464
541
|
continue;
|
|
465
542
|
}
|
|
466
543
|
if (rodando) {
|
|
467
|
-
const owner = players.findIndex((p) => p.pad === gi);
|
|
468
544
|
// ===================== O MODO `accessibility` (ADR-0044, item 7) =====================
|
|
469
545
|
// Com o jogo ANDANDO, o direcional deste jogador dirige a BARRA RÁPIDA e não o personagem. Vem antes
|
|
470
546
|
// de tudo o que é de jogo, porque enquanto o modo está ligado nada mais deste controle é de jogo.
|
|
@@ -519,10 +595,21 @@ export function initGamepad(ctx) {
|
|
|
519
595
|
}
|
|
520
596
|
// A tabela e a guarda do Fácil vêm de input/edges.ts, as MESMAS que keydown e touch usam. Antes eram
|
|
521
597
|
// seis `if` à mão aqui, seis lá e seis no toque — e o do toque tinha esquecido o `!p.easy`.
|
|
598
|
+
let algumaAresta = false;
|
|
522
599
|
for (const [act, flag] of EDGE_BY_ACTION) {
|
|
600
|
+
if (edge(act))
|
|
601
|
+
algumaAresta = true;
|
|
523
602
|
if (edgeAllowed(act, p.easy) && edge(act))
|
|
524
603
|
p[flag] = true;
|
|
525
604
|
}
|
|
605
|
+
// 📌 A ARESTA DO CONTROLE, e ela conta MESMO QUANDO O FÁCIL A FILTRA (ADR-0113 cláusula 4): a
|
|
606
|
+
// criança carregou no botão — que a regra do Modo Fácil não levante a bandeira do jogo não muda
|
|
607
|
+
// o facto de o aparelho em uso ser este. Ler a mesma condição do `p[flag]` faria uma criança em
|
|
608
|
+
// Modo Fácil ficar com a alternância do teclado enquanto joga no controle.
|
|
609
|
+
// ⚠️ E É AQUI, no ramo de JOGO, e não nos de menu: `naBarraDe`, o título e a pausa são navegação, e
|
|
610
|
+
// a pergunta que isto alimenta — que alternância vale AGORA — é sobre jogar.
|
|
611
|
+
if (algumaAresta)
|
|
612
|
+
ctx.arestaDoJogador(owner, 'gamepad');
|
|
526
613
|
}
|
|
527
614
|
}
|
|
528
615
|
}
|
|
@@ -21,16 +21,14 @@ export interface KeyboardRuntimeCtx {
|
|
|
21
21
|
/** The live players array (core/state.ts's `players`, mutated in place — never reassigned). */
|
|
22
22
|
getPlayers(): KeyboardRuntimePlayer[];
|
|
23
23
|
}
|
|
24
|
-
/** Result of the old `applyControls()` mutation, as a value: `controls` + its per-action aliases (game.js's
|
|
25
|
-
* KJUMP/KLEFT/KRIGHT/KUP/KDOWN/KRUN) + the flattened `GAME_KEYS` list. */
|
|
26
24
|
export interface ControlsState {
|
|
27
25
|
controls: KeyScheme;
|
|
28
|
-
action2: string[];
|
|
29
|
-
left: string[];
|
|
30
|
-
right: string[];
|
|
31
|
-
up: string[];
|
|
32
|
-
down: string[];
|
|
33
|
-
action1: string[];
|
|
26
|
+
action2: readonly string[];
|
|
27
|
+
left: readonly string[];
|
|
28
|
+
right: readonly string[];
|
|
29
|
+
up: readonly string[];
|
|
30
|
+
down: readonly string[];
|
|
31
|
+
action1: readonly string[];
|
|
34
32
|
gameKeys: string[];
|
|
35
33
|
}
|
|
36
34
|
export interface KeyboardRuntime {
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
// being reassigned here: imported bindings can't be reassigned from outside their own module, so the game.js
|
|
8
8
|
// wrapper is the one that copies the returned fields onto its own `let`s (same trap `core/state.ts` documents
|
|
9
9
|
// for `coins`/`players`).
|
|
10
|
+
import { ACTIONS } from '../core/actions.js';
|
|
11
|
+
/** Result of the old `applyControls()` mutation, as a value: `controls` + its per-action aliases (game.js's
|
|
12
|
+
* KJUMP/KLEFT/KRIGHT/KUP/KDOWN/KRUN) + the flattened `GAME_KEYS` list. */
|
|
13
|
+
/** A lista que uma posição SEM ALCANCE devolve. Congelada e partilhada: ninguém deve escrever nela. */
|
|
14
|
+
const VAZIO = Object.freeze([]);
|
|
10
15
|
// ---------------------------------------------------------------------------------------------
|
|
11
16
|
// Pure logic (no ctx, no `document` — directly testable with plain KeyScheme fixtures)
|
|
12
17
|
// ---------------------------------------------------------------------------------------------
|
|
@@ -14,8 +19,12 @@
|
|
|
14
19
|
* (mirrors ui/settings-controls.ts's keyUsedByOther: a Map built over the scheme, not a re-scan per query). */
|
|
15
20
|
function buildActionIndex(scheme) {
|
|
16
21
|
const index = new Map();
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
// ⚠️ O LAÇO PASSOU A SER SOBRE `ACTIONS` E NÃO SOBRE AS CHAVES DO OBJETO (issue #118). São a mesma lista
|
|
23
|
+
// agora que o `KeyScheme` é fechado — mas percorrer `ACTIONS` diz QUAL é a lista, e um esquema que ganhe
|
|
24
|
+
// uma chave a mais por engano deixa de a ver. E o `?? []` é o que trata a AUSÊNCIA DECLARADA: um `null`
|
|
25
|
+
// não é um esquema partido, é um teclado que não alcança aquela posição.
|
|
26
|
+
for (const action of ACTIONS) {
|
|
27
|
+
for (const code of scheme[action] ?? []) {
|
|
19
28
|
if (!index.has(code))
|
|
20
29
|
index.set(code, action);
|
|
21
30
|
}
|
|
@@ -65,12 +74,22 @@ export function initKeyboardRuntime(ctx) {
|
|
|
65
74
|
function computeControlsState() {
|
|
66
75
|
const kb = ctx.getKB();
|
|
67
76
|
const controls = kb.solo; // alias do P1 — SEMPRE kb.solo, mesmo com numPlayers>1 (comportamento original; ver relato)
|
|
68
|
-
|
|
77
|
+
// Os apelidos são LISTAS, nunca `null`: quem os lê faz `.includes(code)` sem perguntar. Uma posição que o
|
|
78
|
+
// esquema não alcança vira lista vazia aqui — «não alcança» e «alcança com zero teclas» valem o mesmo
|
|
79
|
+
// para quem só pergunta se a tecla está lá.
|
|
80
|
+
//
|
|
81
|
+
// ⚠️ E DEVOLVE A MESMA REFERÊNCIA, não uma cópia. A primeira versão fazia `[...]` e um caso reprovou por
|
|
82
|
+
// identidade — corretamente: o apelido é o alias do P1, e um teste que afirma «é o mesmo array» está a
|
|
83
|
+
// afirmar que ninguém interpôs uma cópia entre o esquema vivo e quem o lê. Copiar aqui não custaria nada
|
|
84
|
+
// hoje e passaria a custar no dia em que alguém mutasse a lista no lugar.
|
|
85
|
+
const lista = (a) => controls[a] ?? VAZIO;
|
|
86
|
+
const action2 = lista('action2'), left = lista('left'), right = lista('right');
|
|
87
|
+
const up = lista('up'), down = lista('down'), action1 = lista('action1');
|
|
69
88
|
const gameKeySet = new Set();
|
|
70
89
|
ctx.getPlayers().forEach((_, i) => {
|
|
71
90
|
const scheme = kbFor(i);
|
|
72
|
-
for (const action
|
|
73
|
-
for (const code of scheme[action])
|
|
91
|
+
for (const action of ACTIONS)
|
|
92
|
+
for (const code of scheme[action] ?? [])
|
|
74
93
|
gameKeySet.add(code);
|
|
75
94
|
});
|
|
76
95
|
const gameKeys = gameKeySet.size ? [...gameKeySet] : [...action2, ...left, ...right, ...up, ...down];
|
|
@@ -20,7 +20,43 @@ export type KBDefaults = {
|
|
|
20
20
|
p4: KeyScheme[];
|
|
21
21
|
};
|
|
22
22
|
export declare const KB_SCHEMES4: KeyScheme[];
|
|
23
|
+
/**
|
|
24
|
+
* ⚠️ AS DUAS TABELAS DE TECLADO PASSARAM A SER UMA (issue #118). O solo e a dupla já não são escritos aqui:
|
|
25
|
+
* são CÓPIAS VIVAS do que `input/default-bindings` declara, que é onde o ADR-0096 pôs a decisão.
|
|
26
|
+
*
|
|
27
|
+
* Antes eram duas listas paralelas com oito posições cada, e as oito «coincidiam» — menos uma. A `Space` do
|
|
28
|
+
* jogador 1 em dupla estava numa e não na outra, e o custo era concreto: `ui/webcam.ts` sintetiza `Space`
|
|
29
|
+
* para «olhar para cima = pular», então entrar um segundo jogador tirava o PULO de quem joga com os olhos e
|
|
30
|
+
* deixava o andar. Nada errava em voz alta. Derivar em vez de repetir torna essa divergência impossível de
|
|
31
|
+
* voltar a existir, em vez de a apanhar depois de acontecer.
|
|
32
|
+
*/
|
|
23
33
|
export declare const KB_DEFAULTS: KBDefaults;
|
|
34
|
+
/**
|
|
35
|
+
* O PADRÃO QUE O JOGO QUER, por número de jogadores e por assento (ADR-0115). Parcial: o que ele não disser
|
|
36
|
+
* fica como a fábrica da engine o deixou.
|
|
37
|
+
*/
|
|
38
|
+
export type MapeamentoDoTeclado = (jogadores: number, assento: number) => Partial<KeyScheme> | null;
|
|
39
|
+
/**
|
|
40
|
+
* REGISTA O PADRÃO DO JOGO. Chamado uma vez pelo arranque (`boot/create-game`), a partir da declaração.
|
|
41
|
+
*
|
|
42
|
+
* 🔴 REGISTO E NÃO PARÂMETRO, e a razão é um defeito medido em vez de uma preferência. Há DOIS sítios que
|
|
43
|
+
* materializam padrões — `loadKB` e `resetKB` — e o segundo é chamado pelo painel de controles, que não tem a
|
|
44
|
+
* declaração do jogo à mão. Um parâmetro que o painel não passasse faria «restaurar padrões» devolver o mapa
|
|
45
|
+
* da ENGINE por cima do mapa do JOGO: a criança carrega no botão esperando voltar ao que o jogo lhe deu, e
|
|
46
|
+
* volta para outra coisa — num jogo cujo autor escolheu o layout por uma razão de acessibilidade, ela perde
|
|
47
|
+
* essa razão e nada o diz.
|
|
48
|
+
*
|
|
49
|
+
* 📌 É a mesma forma que o `kb` deste ficheiro já tem, e pela mesma justificação: o dono é evidente, e as
|
|
50
|
+
* funções que o gerem vivem todas aqui.
|
|
51
|
+
*/
|
|
52
|
+
export declare function registrarMapeamentoDoTeclado(f: MapeamentoDoTeclado | null): void;
|
|
53
|
+
/**
|
|
54
|
+
* A FÁBRICA COM O PADRÃO DO JOGO POR CIMA — a **única** resolução, usada pelo `loadKB` E pelo `resetKB`.
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ Uma função só, e é o ponto inteiro: enquanto eram duas cópias do `JSON.parse(JSON.stringify(...))`, a do
|
|
57
|
+
* `resetKB` não conhecia o jogo e a diferença só aparecia quando uma criança carregava em «restaurar».
|
|
58
|
+
*/
|
|
59
|
+
export declare function fabricaComOJogo(): KBDefaults;
|
|
24
60
|
export declare function loadKB(): KBDefaults;
|
|
25
61
|
export declare function saveKB(kb: KBDefaults): void;
|
|
26
62
|
/**
|
|
@@ -42,4 +78,10 @@ export declare function initKB(): KBDefaults;
|
|
|
42
78
|
/** Troca o mapa inteiro. Só o "restaurar padrões" do painel de controles precisa disto — remapear uma tecla
|
|
43
79
|
* MUTA o objeto, e reatribuir por engano faria as referências vivas apontarem para o mapa antigo. */
|
|
44
80
|
export declare function setKB(next: KBDefaults): void;
|
|
81
|
+
/**
|
|
82
|
+
* «RESTAURAR PADRÕES» — e o padrão para onde ela volta é o DO JOGO, não o da engine (ADR-0115).
|
|
83
|
+
*
|
|
84
|
+
* 🔴 Esta linha era `JSON.parse(JSON.stringify(KB_DEFAULTS))`, e com o campo do jogo a existir isso passaria
|
|
85
|
+
* a apagar em silêncio o mapeamento que o jogo escolheu. A criança espera voltar ao que o jogo lhe deu.
|
|
86
|
+
*/
|
|
45
87
|
export declare function resetKB(): KBDefaults;
|
|
@@ -6,34 +6,98 @@
|
|
|
6
6
|
// e o ADR-0086 §2 diz qual é para a plataforma. Esquemas por contagem de jogadores (solo/p2/p3/p4).
|
|
7
7
|
// A INSTÂNCIA atual (KB) e o remap ficam no composition root — aqui só config/load/save/reset.
|
|
8
8
|
import * as store from '../platform/storage.js';
|
|
9
|
+
// ⚠️ A DECLARAÇÃO DOS ESQUEMAS MORA LÁ (ADR-0096), e este ficheiro passou a derivá-los em vez de os repetir
|
|
10
|
+
// — é a união que a issue #118 pede. `default-bindings` é folha (só importa `core/actions`), então não há
|
|
11
|
+
// ciclo: quem depende é o ficheiro de persistência, e não o contrário.
|
|
12
|
+
import { KEYBOARD_SOLO, KEYBOARD_DUO } from './default-bindings.js';
|
|
9
13
|
const CKEY = 'inclusionist.kbcontrols.v3';
|
|
14
|
+
/**
|
|
15
|
+
* AS SEIS POSIÇÕES QUE UM TECLADO PARTIDO NÃO ALCANÇA, declaradas como ausência (issue #118, decisão do Dev).
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ `null` AQUI É UMA AFIRMAÇÃO, e é o que torna o `KeyScheme` fechado útil em vez de burocrático: quando o
|
|
18
|
+
* teclado é repartido por três ou quatro crianças, não há lugar físico para ombros, gatilhos, start e select
|
|
19
|
+
* de cada uma — o bloco de cada jogador tem oito teclas e acabou. Inventar teclas para preencher seria dar a
|
|
20
|
+
* cada criança um alcance que ela não tem, e o `ui/reach-notice` (#112) diria a coisa errada.
|
|
21
|
+
*
|
|
22
|
+
* O que `null` compra: o aviso de alcance pode dizer, ANTES de a criança começar, quais das ações do jogo o
|
|
23
|
+
* controlo dela não alcança — e o jogo pode decidir não usar essas posições no modo de quatro.
|
|
24
|
+
*/
|
|
25
|
+
const SEM_ALCANCE_NO_TECLADO_PARTIDO = Object.freeze({
|
|
26
|
+
leftShoulder: null, leftTrigger: null, rightShoulder: null, rightTrigger: null, start: null, select: null,
|
|
27
|
+
});
|
|
10
28
|
// 4 esquemas base p/ 3–4 jogadores (modos 3 e 4 têm esquemas SEPARADOS, p3 e p4, editáveis por jogador)
|
|
11
29
|
export const KB_SCHEMES4 = [
|
|
12
|
-
{ left: ['KeyA'], right: ['KeyD'], up: ['KeyW'], down: ['KeyS'], action1: ['KeyZ'], action2: ['KeyX'], action4: ['KeyC'], action3: ['KeyV'] },
|
|
13
|
-
{ left: ['KeyJ'], right: ['KeyL'], up: ['KeyI'], down: ['KeyK'], action1: ['KeyM'], action2: ['Comma'], action4: ['Period'], action3: ['Semicolon', 'Slash'] },
|
|
14
|
-
{ left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Home'], action2: ['End'], action4: ['PageUp'], action3: ['PageDown'] },
|
|
15
|
-
{ left: ['Numpad4'], right: ['Numpad6'], up: ['Numpad8'], down: ['Numpad5'], action1: ['Numpad2'], action2: ['Numpad0'], action4: ['Numpad3'], action3: ['NumpadDecimal'] },
|
|
30
|
+
{ left: ['KeyA'], right: ['KeyD'], up: ['KeyW'], down: ['KeyS'], action1: ['KeyZ'], action2: ['KeyX'], action4: ['KeyC'], action3: ['KeyV'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
31
|
+
{ left: ['KeyJ'], right: ['KeyL'], up: ['KeyI'], down: ['KeyK'], action1: ['KeyM'], action2: ['Comma'], action4: ['Period'], action3: ['Semicolon', 'Slash'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
32
|
+
{ left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Home'], action2: ['End'], action4: ['PageUp'], action3: ['PageDown'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
33
|
+
{ left: ['Numpad4'], right: ['Numpad6'], up: ['Numpad8'], down: ['Numpad5'], action1: ['Numpad2'], action2: ['Numpad0'], action4: ['Numpad3'], action3: ['NumpadDecimal'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
16
34
|
];
|
|
35
|
+
/**
|
|
36
|
+
* Cópia PROFUNDA e MUTÁVEL de uma tabela declarada. O remapeamento escreve dentro do esquema vivo, então ele
|
|
37
|
+
* não pode partilhar objeto com a tabela de `input/default-bindings`, que é congelada e é a declaração.
|
|
38
|
+
*/
|
|
39
|
+
const vivo = (t) => JSON.parse(JSON.stringify(t));
|
|
40
|
+
/**
|
|
41
|
+
* ⚠️ AS DUAS TABELAS DE TECLADO PASSARAM A SER UMA (issue #118). O solo e a dupla já não são escritos aqui:
|
|
42
|
+
* são CÓPIAS VIVAS do que `input/default-bindings` declara, que é onde o ADR-0096 pôs a decisão.
|
|
43
|
+
*
|
|
44
|
+
* Antes eram duas listas paralelas com oito posições cada, e as oito «coincidiam» — menos uma. A `Space` do
|
|
45
|
+
* jogador 1 em dupla estava numa e não na outra, e o custo era concreto: `ui/webcam.ts` sintetiza `Space`
|
|
46
|
+
* para «olhar para cima = pular», então entrar um segundo jogador tirava o PULO de quem joga com os olhos e
|
|
47
|
+
* deixava o andar. Nada errava em voz alta. Derivar em vez de repetir torna essa divergência impossível de
|
|
48
|
+
* voltar a existir, em vez de a apanhar depois de acontecer.
|
|
49
|
+
*/
|
|
17
50
|
export const KB_DEFAULTS = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{ left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Numpad8'], action2: ['Numpad5'], action4: ['Numpad9'], action3: ['Numpad6'] }],
|
|
23
|
-
p3: JSON.parse(JSON.stringify(KB_SCHEMES4.slice(0, 3))), // modo 3 jogadores (independente do 4)
|
|
24
|
-
p4: JSON.parse(JSON.stringify(KB_SCHEMES4)), // modo 4 jogadores
|
|
51
|
+
solo: vivo(KEYBOARD_SOLO),
|
|
52
|
+
p2: KEYBOARD_DUO.map(vivo),
|
|
53
|
+
p3: KB_SCHEMES4.slice(0, 3).map(vivo), // modo 3 jogadores (independente do 4)
|
|
54
|
+
p4: KB_SCHEMES4.map(vivo), // modo 4 jogadores
|
|
25
55
|
};
|
|
26
56
|
// dado salvo (parcial): sobrepõe os defaults; p34 é o formato ANTIGO (migra p/ p3+p4).
|
|
27
57
|
// A FORMA vem de `vocabulary-migration`, que é quem a traduz — declarar aqui outra vez seria a
|
|
28
58
|
// cópia que o `core/entity` passou o mês a eliminar.
|
|
29
59
|
import { migrarSalvo } from './vocabulary-migration.js';
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
let mapeamentoDoJogo = null;
|
|
61
|
+
/**
|
|
62
|
+
* REGISTA O PADRÃO DO JOGO. Chamado uma vez pelo arranque (`boot/create-game`), a partir da declaração.
|
|
63
|
+
*
|
|
64
|
+
* 🔴 REGISTO E NÃO PARÂMETRO, e a razão é um defeito medido em vez de uma preferência. Há DOIS sítios que
|
|
65
|
+
* materializam padrões — `loadKB` e `resetKB` — e o segundo é chamado pelo painel de controles, que não tem a
|
|
66
|
+
* declaração do jogo à mão. Um parâmetro que o painel não passasse faria «restaurar padrões» devolver o mapa
|
|
67
|
+
* da ENGINE por cima do mapa do JOGO: a criança carrega no botão esperando voltar ao que o jogo lhe deu, e
|
|
68
|
+
* volta para outra coisa — num jogo cujo autor escolheu o layout por uma razão de acessibilidade, ela perde
|
|
69
|
+
* essa razão e nada o diz.
|
|
70
|
+
*
|
|
71
|
+
* 📌 É a mesma forma que o `kb` deste ficheiro já tem, e pela mesma justificação: o dono é evidente, e as
|
|
72
|
+
* funções que o gerem vivem todas aqui.
|
|
73
|
+
*/
|
|
74
|
+
export function registrarMapeamentoDoTeclado(f) { mapeamentoDoJogo = f; }
|
|
75
|
+
/**
|
|
76
|
+
* A FÁBRICA COM O PADRÃO DO JOGO POR CIMA — a **única** resolução, usada pelo `loadKB` E pelo `resetKB`.
|
|
77
|
+
*
|
|
78
|
+
* ⚠️ Uma função só, e é o ponto inteiro: enquanto eram duas cópias do `JSON.parse(JSON.stringify(...))`, a do
|
|
79
|
+
* `resetKB` não conhecia o jogo e a diferença só aparecia quando uma criança carregava em «restaurar».
|
|
80
|
+
*/
|
|
81
|
+
export function fabricaComOJogo() {
|
|
82
|
+
const d = JSON.parse(JSON.stringify(KB_DEFAULTS));
|
|
83
|
+
if (!mapeamentoDoJogo)
|
|
84
|
+
return d;
|
|
85
|
+
const aplicar = (alvo, jogadores, assento) => {
|
|
86
|
+
const parcial = mapeamentoDoJogo(jogadores, assento);
|
|
87
|
+
if (parcial)
|
|
88
|
+
Object.assign(alvo, parcial);
|
|
89
|
+
};
|
|
90
|
+
aplicar(d.solo, 1, 0);
|
|
91
|
+
d.p2.forEach((esq, i) => aplicar(esq, 2, i));
|
|
92
|
+
d.p3.forEach((esq, i) => aplicar(esq, 3, i));
|
|
93
|
+
d.p4.forEach((esq, i) => aplicar(esq, 4, i));
|
|
94
|
+
return d;
|
|
95
|
+
}
|
|
34
96
|
// carrega os esquemas salvos SOBRE os defaults (com migração do dado antigo p34 → p3+p4)
|
|
35
97
|
export function loadKB() {
|
|
36
|
-
|
|
98
|
+
// ⚠️ A PRECEDÊNCIA É ESTA E ESTÁ ESCRITA UMA VEZ: fábrica da engine → padrão do JOGO → remapeamento da
|
|
99
|
+
// CRIANÇA. O que a criança gravou vem sempre por último, porque é a única das três que ela escolheu.
|
|
100
|
+
const d = fabricaComOJogo();
|
|
37
101
|
// ⚠️ O DADO SALVO ATRAVESSA O TRADUTOR ANTES DE TOCAR NOS PADRÕES. Sem esta linha, um esquema gravado com
|
|
38
102
|
// as chaves antigas (`run`, `jump`, `swap`, `especial`) seria fundido sobre defaults que já usam
|
|
39
103
|
// `action1`..`action4`: o objeto ficaria com AS DUAS famílias de chaves, os transportes leriam só as novas,
|
|
@@ -77,4 +141,10 @@ export function initKB() { kb = loadKB(); return kb; }
|
|
|
77
141
|
/** Troca o mapa inteiro. Só o "restaurar padrões" do painel de controles precisa disto — remapear uma tecla
|
|
78
142
|
* MUTA o objeto, e reatribuir por engano faria as referências vivas apontarem para o mapa antigo. */
|
|
79
143
|
export function setKB(next) { kb = next; }
|
|
80
|
-
|
|
144
|
+
/**
|
|
145
|
+
* «RESTAURAR PADRÕES» — e o padrão para onde ela volta é o DO JOGO, não o da engine (ADR-0115).
|
|
146
|
+
*
|
|
147
|
+
* 🔴 Esta linha era `JSON.parse(JSON.stringify(KB_DEFAULTS))`, e com o campo do jogo a existir isso passaria
|
|
148
|
+
* a apagar em silêncio o mapeamento que o jogo escolheu. A criança espera voltar ao que o jogo lhe deu.
|
|
149
|
+
*/
|
|
150
|
+
export function resetKB() { store.remove(CKEY); return fabricaComOJogo(); }
|
|
@@ -9,6 +9,14 @@ export interface KeydownEventLike {
|
|
|
9
9
|
altKey: boolean;
|
|
10
10
|
ctrlKey: boolean;
|
|
11
11
|
preventDefault(): void;
|
|
12
|
+
/**
|
|
13
|
+
* O navegador viu a pessoa carregar? (ADR-0109) — OPCIONAL, e a opcionalidade é a decisão.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Torná-lo obrigatório partiria todos os duplos de teste que já existem, e partiria-os por uma razão
|
|
16
|
+
* falsa: eles descrevem a decisão de teclado, que não depende disto. O que a ausência significa está
|
|
17
|
+
* escrito no `input/origem-sintetica` — «não afirmei nada», cuja resposta é `undefined` e não `'teclado'`.
|
|
18
|
+
*/
|
|
19
|
+
isTrusted?: boolean;
|
|
12
20
|
}
|
|
13
21
|
/** `keyup` só precisa do código. */
|
|
14
22
|
export interface KeyupEventLike {
|
|
@@ -174,6 +182,7 @@ export declare function isEasyShortcut(code: string, s: KeydownSnapshot): boolea
|
|
|
174
182
|
export declare function titleNavOf(code: string, s: KeydownSnapshot, action2: boolean): TitleNav;
|
|
175
183
|
import type { EventTargetLike } from './touch-bindings.js';
|
|
176
184
|
import type { DomQuery } from '../core/dom-query.js';
|
|
185
|
+
import type { Transporte } from './transporte-em-uso.js';
|
|
177
186
|
export { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
178
187
|
/**
|
|
179
188
|
* Quem é o dono do modal que esta tecla comanda? Devolve a POSIÇÃO no array, ou -1.
|
|
@@ -219,8 +228,37 @@ export interface KeydownCtx {
|
|
|
219
228
|
getPlayers: () => readonly KeydownPlayer[];
|
|
220
229
|
/** `kbRuntime.controlsState()` — memorizado do lado de lá; uma chamada por tecla, como no original. */
|
|
221
230
|
getControls: () => ControlsSnapshot;
|
|
222
|
-
/**
|
|
223
|
-
|
|
231
|
+
/**
|
|
232
|
+
* O PAR DE `input/state`, E NÃO O CONJUNTO CRU (ADR-0109) — o mesmo corte que o `input/touch-bindings` fez.
|
|
233
|
+
*
|
|
234
|
+
* ⚠️ `ReadonlySet` e não `Set`, e a diferença É a razão de o par existir: LER o conjunto nunca foi o
|
|
235
|
+
* problema; ESCREVER nele apagava a origem. Com o tipo assim, uma escrita crua deixa de compilar — o crivo
|
|
236
|
+
* do inventário passa a ter o compilador do lado dele, em vez de ser a única coisa a segurar a linha.
|
|
237
|
+
*/
|
|
238
|
+
readonly heldKeys: ReadonlySet<string>;
|
|
239
|
+
/** Uma tecla foi segurada, e sabe-se por quem. */
|
|
240
|
+
marcarTecla: (code: string, origem: Transporte) => void;
|
|
241
|
+
/**
|
|
242
|
+
* Uma tecla foi segurada e NÃO se sabe por quem — o evento sintético que ninguém assinou.
|
|
243
|
+
*
|
|
244
|
+
* ⚠️ Está no ctx a par das outras duas de propósito: se fosse importada, um consumidor não teria como ver
|
|
245
|
+
* que ela existe, e é justamente ele quem produz os eventos que caem aqui.
|
|
246
|
+
*/
|
|
247
|
+
marcarTeclaSemOrigem: (code: string) => void;
|
|
248
|
+
/**
|
|
249
|
+
* ESTA ARESTA É DESTE JOGADOR, E VEIO DAQUI (ADR-0113 cláusula 4, issue #127) — `input/state.arestaDoJogador`.
|
|
250
|
+
*
|
|
251
|
+
* 🔴 CAMPO OBRIGATÓRIO, e a medição é a razão: em 2026-09-09 o autómato do ADR-0109 tinha ZERO alimentadores
|
|
252
|
+
* em produção, logo `entradaDe(i).emUso` respondia `teclado` a toda a gente — para sempre, e sem erro
|
|
253
|
+
* nenhum. Com isso, a recusa da cláusula 3 nunca dispara: a criança que joga por webcam consegue desligar a
|
|
254
|
+
* alternância de que a entrada dela depende, e nada o diz.
|
|
255
|
+
*
|
|
256
|
+
* ⚠️ E É AQUI QUE ELE VALE, e não no teclado que já é o padrão: o evento sintético que a webcam despacha
|
|
257
|
+
* chega carimbado (`input/origem-sintetica`), então é por esta linha que `olhos`/`rosto`/`gestos`/`fala`
|
|
258
|
+
* passam a ser o transporte em uso. Uma tecla premida a sério devolve o teclado, que é a regra 3 do ADR-0109.
|
|
259
|
+
*/
|
|
260
|
+
arestaDoJogador: (jogador: number, origem: Transporte) => void;
|
|
261
|
+
soltarTecla: (code: string) => void;
|
|
224
262
|
/** `let oneButton` do game.js (empatia motora) → getter. */
|
|
225
263
|
isOneButton: () => boolean;
|
|
226
264
|
actionOf: (code: string, playerIndex: number) => string | null;
|
|
@@ -113,7 +113,9 @@ export function isJumpKey(code, s) {
|
|
|
113
113
|
* jogador. NÃO inclui os atalhos do Fácil (o original também não: por isso `easyKey` é somado à parte). */
|
|
114
114
|
export function isGameKeyCode(code, s) {
|
|
115
115
|
return s.controls.gameKeys.includes(code)
|
|
116
|
-
|
|
116
|
+
// `arr &&` porque uma posição sem alcance é `null` desde a #118 — e `null.includes` seria uma exceção
|
|
117
|
+
// no caminho do teclado, ou seja, o jogo a parar de responder a qualquer tecla.
|
|
118
|
+
|| s.players.some((p) => !!p.ctrl && Object.values(p.ctrl).some((arr) => !!arr && arr.includes(code)));
|
|
117
119
|
}
|
|
118
120
|
/** Os atalhos de acessibilidade do Fácil só existem SOLO (`numPlayers<=1`) e só para o Jogador 1. */
|
|
119
121
|
export function isEasyShortcut(code, s) {
|
|
@@ -138,6 +140,10 @@ export function titleNavOf(code, s, action2) {
|
|
|
138
140
|
}
|
|
139
141
|
/** Alguma intenção foi expressa? Definição única em input/edges; aqui só o nome que este módulo sempre teve. */
|
|
140
142
|
import { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
143
|
+
// ⚠️ IMPORTADA E NÃO INJECTADA, ao contrário dos três escritores logo abaixo, e a linha que separa os dois é
|
|
144
|
+
// esta: `origemDoEvento` é uma função PURA do evento — não toca estado nenhum que o cartucho possua. Os
|
|
145
|
+
// escritores tocam o `input/state`, que é mutado in-place e partilhado, e é por isso que continuam a entrar.
|
|
146
|
+
import { origemDoEvento } from './origem-sintetica.js';
|
|
141
147
|
export { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
142
148
|
/**
|
|
143
149
|
* Quem é o dono do modal que esta tecla comanda? Devolve a POSIÇÃO no array, ou -1.
|
|
@@ -287,7 +293,7 @@ export function initKeydown(ctx) {
|
|
|
287
293
|
};
|
|
288
294
|
}
|
|
289
295
|
/** A metade IMPURA: pega a decisão pronta e a carimba no mundo. */
|
|
290
|
-
function apply(d, code) {
|
|
296
|
+
function apply(d, code, origem) {
|
|
291
297
|
switch (d.kind) {
|
|
292
298
|
case 'overlay':
|
|
293
299
|
if (d.closeId)
|
|
@@ -354,8 +360,23 @@ export function initKeydown(ctx) {
|
|
|
354
360
|
p[edge] = true;
|
|
355
361
|
}
|
|
356
362
|
for (const k of d.releaseKeys)
|
|
357
|
-
ctx.
|
|
358
|
-
|
|
363
|
+
ctx.soltarTecla(k);
|
|
364
|
+
// ⚠️ A ORIGEM CHEGA DO EVENTO E NÃO É INVENTADA AQUI (ADR-0109). `origem` é `undefined` só para o
|
|
365
|
+
// evento sintético que ninguém assinou — e nesse caso a porta estreita APAGA a entrada anterior, em
|
|
366
|
+
// vez de deixar a tecla herdar de quem a segurou da última vez. Ver `marcarTeclaSemOrigem`.
|
|
367
|
+
if (origem)
|
|
368
|
+
ctx.marcarTecla(code, origem);
|
|
369
|
+
else
|
|
370
|
+
ctx.marcarTeclaSemOrigem(code);
|
|
371
|
+
// 📌 A ARESTA ALIMENTA O AUTÓMATO NO MESMO PONTO E SOB A MESMA CONDIÇÃO em que a origem é gravada na
|
|
372
|
+
// tecla — origem desconhecida não é aresta de aparelho nenhum, e inventar-lhe `teclado` faria uma
|
|
373
|
+
// tecla do toque desligar a alternância de quem joga por olhar, sem erro e no meio da partida.
|
|
374
|
+
// ⚠️ Tecla genérica (sem dono) conta para o jogador 1, que é a mesma convenção do `ui/menu-nav`: quem
|
|
375
|
+
// carrega numa tecla que não é de assento nenhum está a jogar no primeiro assento.
|
|
376
|
+
if (origem) {
|
|
377
|
+
const dono = ctx.whichPlayer(code);
|
|
378
|
+
ctx.arestaDoJogador(dono < 0 ? 0 : dono, origem);
|
|
379
|
+
}
|
|
359
380
|
return;
|
|
360
381
|
}
|
|
361
382
|
}
|
|
@@ -370,9 +391,11 @@ export function initKeydown(ctx) {
|
|
|
370
391
|
const d = decideKeydown(e, snapshot());
|
|
371
392
|
if (d.preventDefault)
|
|
372
393
|
e.preventDefault();
|
|
373
|
-
apply(d, e.code);
|
|
394
|
+
apply(d, e.code, origemDoEvento(e));
|
|
374
395
|
}
|
|
375
|
-
|
|
396
|
+
// ⚠️ SOLTA NOS DOIS. Um `keys.delete` cru deixava a origem para trás, e um mapa que descreve teclas que já
|
|
397
|
+
// ninguém segura responde à alternância com o aparelho errado — sem erro, e só na aresta seguinte.
|
|
398
|
+
function onKeyup(e) { ctx.soltarTecla(e.code); }
|
|
376
399
|
function attach() {
|
|
377
400
|
ctx.win.addEventListener('keydown', onKeydown);
|
|
378
401
|
ctx.win.addEventListener('keyup', onKeyup);
|