@the-inclusionist/engine 7.0.1 → 8.0.0-rc.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/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/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +43 -2
- package/dist-pkg/boot/create-game.d.ts +76 -2
- package/dist-pkg/boot/create-game.js +271 -13
- package/dist-pkg/core/constants.d.ts +0 -28
- package/dist-pkg/core/constants.js +34 -17
- package/dist-pkg/core/contract.d.ts +39 -0
- package/dist-pkg/core/contract.js +36 -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 +33 -0
- package/dist-pkg/i18n/es.js +33 -0
- package/dist-pkg/i18n/pt.js +46 -0
- package/dist-pkg/input/default-bindings.d.ts +40 -0
- package/dist-pkg/input/default-bindings.js +143 -9
- package/dist-pkg/input/gamepad.d.ts +18 -11
- package/dist-pkg/input/gamepad.js +75 -9
- 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 +10 -0
- package/dist-pkg/input/keyboard.js +41 -11
- package/dist-pkg/input/keydown.d.ts +27 -2
- package/dist-pkg/input/keydown.js +20 -6
- 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/origem-sintetica.d.ts +44 -0
- package/dist-pkg/input/origem-sintetica.js +60 -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 +11 -2
- package/dist-pkg/input/touch-bindings.js +9 -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/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 +90 -0
- package/dist-pkg/platform/voice-plan.js +137 -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 +47 -1
- package/dist-pkg/ui/fonts.js +47 -9
- 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 +134 -20
- package/dist-pkg/ui/pause-icons.js +307 -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 +81 -3
- package/dist-pkg/ui/settings-motor.js +118 -9
- package/dist-pkg/ui/settings-typo.d.ts +32 -2
- package/dist-pkg/ui/settings-typo.js +64 -18
- 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 +18 -0
- package/docs/LICENSES.md +12 -0
- package/package.json +24 -4
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
package/dist-pkg/i18n/pt.js
CHANGED
|
@@ -168,6 +168,10 @@ const pt = {
|
|
|
168
168
|
'touch.dir.cross': 'cruz (D-pad)',
|
|
169
169
|
'touch.dir.stick': 'analógico',
|
|
170
170
|
'sr.touch.slotSet': '{slot}: {acao}.',
|
|
171
|
+
// ⚠️ SEM O NOME DA POSIÇÃO, de propósito: é uma que este jogo não nomeia, e o id interno (`action3`)
|
|
172
|
+
// não pode chegar a uma criança (ADR-0074). O anúncio EXISTE na mesma — quem navega por ouvido precisa de
|
|
173
|
+
// saber que a escolha aterrou. Gémeo do `sr.ctrl.keyTakenHereUnnamed`, que resolveu o mesmo em `7742ac0`.
|
|
174
|
+
'sr.touch.slotSetUnnamed': '{slot}: definido.',
|
|
171
175
|
'sr.touch.dirSet': 'Direcional: {tipo}.',
|
|
172
176
|
'sr.touch.presetChild': 'Controles no tamanho de mão de criança (6 a 12 anos).',
|
|
173
177
|
'sr.touch.presetAdult': 'Controles no tamanho de mão de adulto.',
|
|
@@ -182,9 +186,17 @@ const pt = {
|
|
|
182
186
|
'act.especial': 'Especial',
|
|
183
187
|
'key.space': 'Espaço',
|
|
184
188
|
'ctrl.change': 'Alterar',
|
|
189
|
+
'ctrl.pressing': 'Pressione…',
|
|
190
|
+
'ctrl.editingYours': 'Editando o seu controle — modo {modo}.',
|
|
191
|
+
'ctrl.mode.one': '1 jogador',
|
|
192
|
+
'ctrl.mode.many': '{n} jogadores',
|
|
185
193
|
'ctrl.changeKeyAria': 'Alterar tecla de {acao} do Jogador {n}',
|
|
186
194
|
'sr.ctrl.pressNewKey': 'Pressione a nova tecla para {acao} do Jogador {n}, ou Esc para cancelar.',
|
|
187
195
|
'sr.ctrl.keyTaken': 'Essa tecla já é do Jogador {n}. Escolha outra, ou Esc para cancelar.',
|
|
196
|
+
'sr.ctrl.keyTakenHere': 'Essa tecla já é de {acao}. Escolha outra, ou Esc para cancelar.',
|
|
197
|
+
// ⚠️ SEM O NOME DA POSIÇÃO, de propósito: ela é uma que este jogo não nomeia, e o id interno (`action2`)
|
|
198
|
+
// não pode chegar a uma criança (ADR-0074). A frase diz a verdade que interessa — a tecla está ocupada.
|
|
199
|
+
'sr.ctrl.keyTakenHereUnnamed': 'Essa tecla já está em uso neste controle. Escolha outra, ou Esc para cancelar.',
|
|
188
200
|
'sr.ctrl.reset': 'Controles restaurados ao padrão.',
|
|
189
201
|
// Painel de áudio (#audio) — saídas por jogador, som/narração, bengala, motores de voz.
|
|
190
202
|
'audio.playerN': 'Jogador {n}',
|
|
@@ -274,6 +286,11 @@ const pt = {
|
|
|
274
286
|
'pad.glyph.circle': 'bola',
|
|
275
287
|
'pad.glyph.square': 'quadrado',
|
|
276
288
|
'pad.glyph.triangle': 'triângulo',
|
|
289
|
+
'pad.wiz.step': '{n} de {total} — aperte: {acao}',
|
|
290
|
+
'pad.wiz.mapped': 'Mapeados: {lista}',
|
|
291
|
+
'pad.wiz.pressAny': 'Aperte QUALQUER botão no controle que deseja mapear.',
|
|
292
|
+
'pad.wiz.detected': 'Controle novo detectado: {id}. O jogo pausou para você configurá-lo. SOLTE tudo para começar.',
|
|
293
|
+
'pad.wiz.releaseAll': 'Controle: {id}. Agora SOLTE tudo.',
|
|
277
294
|
'menu.legendSpoken': 'Botão {sim} para confirmar, botão {nao} para voltar.',
|
|
278
295
|
'sr.menu.fractionsIntro': 'Soma e subtração de frações: escolha a notação e o tipo.',
|
|
279
296
|
'sr.menu.keepOneNotation': 'Deixe ao menos uma notação ligada.',
|
|
@@ -302,6 +319,21 @@ const pt = {
|
|
|
302
319
|
// conclui que o jogo está partido. Estas frases existem para ela saber ANTES, e o que fazer.
|
|
303
320
|
'reach.titulo': 'Este jogo usa {pedidas} ações.',
|
|
304
321
|
'reach.curto': 'O {transporte} tem {lugares} lugares — não chegam para todas.',
|
|
322
|
+
// ⚠️ Diz os DOIS números, porque é a diferença entre eles que a criança (ou quem a acompanha) pode
|
|
323
|
+
// resolver. «Falta alguma coisa» manda procurar sem dizer o quê; «segura 2 e este jogo pede 3» diz que a
|
|
324
|
+
// saída é outro controle, e diz porquê.
|
|
325
|
+
'reach.naoSegura': 'O {transporte} segura {segura} botões de cada vez, e este jogo pede {pedidas} ao mesmo tempo.',
|
|
326
|
+
// ⚠️ A RECUSA DA SIMULAÇÃO (#104 / ADR-0076). Ela diz um FACTO SOBRE A DEMONSTRAÇÃO e o caminho de volta —
|
|
327
|
+
// nunca «desligue isso». Quem ligou o alto contraste ligou-o porque precisa dele para ver.
|
|
328
|
+
'eixo.tema.titulo': 'Tema (contraste)',
|
|
329
|
+
'eixo.tema.padrao': 'Tema padrão',
|
|
330
|
+
'eixo.correcao.titulo': 'Correção de cor',
|
|
331
|
+
'eixo.correcao.tricro': 'Visão tricromática',
|
|
332
|
+
'viz.escolher': 'Selecionar',
|
|
333
|
+
'viz.escolhido': '✓ Selecionado',
|
|
334
|
+
'sim.indisponivel.tema': 'Para ver a simulação, o tema precisa estar no padrão: por cima do alto contraste ela mostraria o que o tema faz, e não o que a deficiência faz.',
|
|
335
|
+
'sim.indisponivel.correcao': 'Para ver a simulação, a correção de cor precisa estar em visão tricromática: de uma tela já corrigida ela não mostraria nem a deficiência nem a correção.',
|
|
336
|
+
'sim.indisponivel.ambos': 'Para ver a simulação, o tema e a correção de cor precisam estar no padrão: por cima de um ajuste, a demonstração mostra o ajuste e não a deficiência.',
|
|
305
337
|
'reach.ligue': 'Ligue {saida} e você joga com todas.',
|
|
306
338
|
// Quando NADA resolveria: mandar ligar um controle seria mandar procurar o que não conserta.
|
|
307
339
|
'reach.semSaida': 'Nenhum controle deste aparelho alcança todas. Dá para jogar assim mesmo, mas algumas ações vão ficar sem lugar.',
|
|
@@ -379,6 +411,8 @@ const pt = {
|
|
|
379
411
|
'sr.quiz.wellDone': 'Muito bem! {palavra}. {n} de 3.',
|
|
380
412
|
'sr.quiz.correctSoFar': 'Acertou! {n} de 3 para ganhar a moeda.',
|
|
381
413
|
'menu.restoreDefaults': 'Restaurar padrões deste menu',
|
|
414
|
+
'menu.close': 'Fechar',
|
|
415
|
+
'menu.typo': 'Tipografia',
|
|
382
416
|
'sr.audio.reset': 'Acessibilidade auditiva restaurada aos padrões. Os outros menus não mudaram.',
|
|
383
417
|
'sr.empathy.reset': 'Modo empatia restaurado aos padrões: simulações desligadas. Correções de daltonismo e os outros menus não mudaram.',
|
|
384
418
|
'sr.motor.reset': 'Modo Fácil e movimento por alternância restaurados aos padrões. O controle pelos olhos e o mapeamento de teclas continuam como estavam.',
|
|
@@ -488,6 +522,13 @@ const pt = {
|
|
|
488
522
|
'sr.motor.easyOff': 'Modo Fácil desligado.',
|
|
489
523
|
'sr.motor.toggleMoveOn': 'Movimento por alternância ligado: toque a direção para andar sem segurar; toque de novo para parar; segure para ir mais rápido. O pulo não interrompe a caminhada.',
|
|
490
524
|
'sr.motor.toggleMoveOff': 'Movimento por alternância desligado.',
|
|
525
|
+
// ⚠️ O MOTIVO É UM FACTO SOBRE O APARELHO, NUNCA UMA REPREENSÃO (ADR-0113 cláusula 3). Estas frases
|
|
526
|
+
// dizem por que o botão não responde; «não desligue isto» repreenderia uma criança por mexer num ajuste
|
|
527
|
+
// de que ela depende. QUATRO chaves e não uma com `{aparelho}`: «os gestos» é plural e «o olhar» não.
|
|
528
|
+
'alt.exigida.olhos': 'O controle por olhar precisa das teclas de alternância para funcionar.',
|
|
529
|
+
'alt.exigida.rosto': 'O controle por rosto precisa das teclas de alternância para funcionar.',
|
|
530
|
+
'alt.exigida.gestos': 'Os gestos precisam das teclas de alternância para funcionar.',
|
|
531
|
+
'alt.exigida.fala': 'O comando de voz precisa das teclas de alternância para funcionar.',
|
|
491
532
|
'sr.motor.toggleRunOn': 'Alternância do correr ligada.',
|
|
492
533
|
'sr.motor.toggleRunOff': 'Alternância do correr desligada.',
|
|
493
534
|
'sr.physics.runLatchOn': 'Correndo.',
|
|
@@ -601,18 +642,23 @@ const pt = {
|
|
|
601
642
|
// Os NOMES das fontes (Atkinson Hyperlegible, Lexend, …) NÃO entram aqui: são nomes próprios e ficam no
|
|
602
643
|
// catálogo, como os nomes dos motores de voz. O que traduz é o nome do GRUPO e a descrição — que é o texto
|
|
603
644
|
// que explica à criança (ou a quem a acompanha) POR QUE aquela fonte existe na lista.
|
|
645
|
+
'font.grupo.rotulo': 'Família de letra',
|
|
604
646
|
'font.group.sans': 'Sem serifa',
|
|
605
647
|
'font.group.serif': 'Serifada',
|
|
606
648
|
'font.group.hand': 'Manuscrita',
|
|
649
|
+
'font.group.arcade': 'Arcade',
|
|
607
650
|
'font.desc.atkinson': 'feita pelo Braille Institute para pessoas com baixa visão (padrão do jogo)',
|
|
608
651
|
'font.desc.lexend': 'feita para reduzir stress visual e atender pessoas disléxicas (ativa o espaçamento extra)',
|
|
609
652
|
'font.desc.quattro': 'criada para diminuir a fadiga visual de quem passa muito tempo na tela',
|
|
610
653
|
'font.desc.andika': 'baseada na Sassoon; fruto de pesquisa sobre como crianças leem e escrevem',
|
|
654
|
+
'font.desc.opendyslexic': 'Letras com a base mais pesada, para não virarem de cabeça para baixo ao ler.',
|
|
655
|
+
'font.desc.fondamento': 'Caligráfica de pena, para as atividades de escrita à mão.',
|
|
611
656
|
'font.desc.greatvibes': 'caligráfica inglesa',
|
|
612
657
|
'font.desc.pinyon': 'caligráfica inglesa',
|
|
613
658
|
'font.desc.ufcook': 'blackletter alemã',
|
|
614
659
|
'font.desc.ufmag': 'blackletter alemã',
|
|
615
660
|
'font.desc.comicneue': 'bola e bastão (alfabetização)',
|
|
661
|
+
'font.desc.pressstart': 'pixel de 8 bits (HUD e título do jogo)',
|
|
616
662
|
'font.desc.learningcurve': 'cursiva inglesa',
|
|
617
663
|
'font.desc.kindergarten': 'cursiva brasileira',
|
|
618
664
|
'font.off.pending': 'licença a confirmar — ainda não embarcada',
|
|
@@ -7,6 +7,35 @@ export type Binding<T> = T | null;
|
|
|
7
7
|
* layout ABNT2/US e é a razão de nunca se usar `key` aqui.
|
|
8
8
|
*/
|
|
9
9
|
export declare const KEYBOARD_SOLO: Readonly<Record<Action, Binding<readonly string[]>>>;
|
|
10
|
+
/**
|
|
11
|
+
* Teclado, esquema de DOIS JOGADORES — as quatorze posições para cada um (ADR-0096).
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ O JOGADOR 1 AQUI NÃO É O `KEYBOARD_SOLO`, e a diferença é UMA e obrigatória: as SETAS saem dele. No solo
|
|
14
|
+
* elas são um segundo caminho para o direcional; em dupla são o direcional DO OUTRO. Deixá-las nos dois faria
|
|
15
|
+
* os dois bonecos andarem juntos — um defeito que não dá erro em lado nenhum e que só se vê jogando a dois.
|
|
16
|
+
* `conflitosEntreTabelas` existe por causa desta linha.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ E A GEOMETRIA DO JOGADOR 2 É A MESMA DO JOGADOR 1, TRANSPOSTA PARA O TECLADO NUMÉRICO — o que faz a
|
|
19
|
+
* memória muscular atravessar de um lado da mesa para o outro:
|
|
20
|
+
*
|
|
21
|
+
* 7 8 / * ombros na linha DE CIMA
|
|
22
|
+
* Y U I O 7 8 9 + gatilhos nas PONTAS da linha das ações
|
|
23
|
+
* J K 5 6
|
|
24
|
+
*
|
|
25
|
+
* action1 U ↔ Numpad8 (cima-esquerda) action4 I ↔ Numpad9 (cima-direita)
|
|
26
|
+
* action2 J ↔ Numpad5 (baixo-esquerda) action3 K ↔ Numpad6 (baixo-direita)
|
|
27
|
+
*
|
|
28
|
+
* O bloco `U I / J K` e o bloco `8 9 / 5 6` têm a MESMA forma, então a rotação de 45° que o cabeçalho deste
|
|
29
|
+
* ficheiro descreve para o Xbox vale igual para o jogador 2. Não é coincidência de teclado: é o que torna o
|
|
30
|
+
* padrão ensinável uma vez só.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ `Digit7`/`Digit8` (jogador 1) e `Numpad7` (jogador 2) SÃO TECLAS DIFERENTES, e é por isso que a
|
|
33
|
+
* especificação do Dev diz «alphanumeric» e «numeric» em tantas palavras. `KeyboardEvent.code` distingue-as
|
|
34
|
+
* sempre; `key` não — com Num Lock desligado o numérico chega como `ArrowUp`/`Home`, e um esquema lido por
|
|
35
|
+
* `key` juntaria o direcional do jogador 2 com as ações dele. Mais uma razão de este ficheiro só falar
|
|
36
|
+
* `code`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const KEYBOARD_DUO: readonly Readonly<Record<Action, Binding<readonly string[]>>>[];
|
|
10
39
|
/**
|
|
11
40
|
* Gamepad, mapa PADRÃO da Gamepad API (`mapping: "standard"`), que é o que um controle de Xbox reporta.
|
|
12
41
|
* O número é o índice em `gamepad.buttons`.
|
|
@@ -20,5 +49,16 @@ export declare const GAMEPAD_STANDARD: Readonly<Record<Action, Binding<number>>>
|
|
|
20
49
|
* ação dupla intermitente que ninguém consegue reproduzir de propósito.
|
|
21
50
|
*/
|
|
22
51
|
export declare function bindingProblems<T>(tabela: Readonly<Record<Action, Binding<T | readonly T[]>>>): string[];
|
|
52
|
+
/**
|
|
53
|
+
* As teclas que DUAS OU MAIS tabelas reclamam para si. VAZIA quer dizer que os jogadores não se atropelam.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ ISTO É UM PROBLEMA DIFERENTE DO `bindingProblems`, E FOI POR ISSO QUE PRECISOU DE FUNÇÃO PRÓPRIA: aquele
|
|
56
|
+
* olha UMA tabela e apanha a mesma tecla em duas ações; este olha DUAS tabelas e apanha a mesma tecla em dois
|
|
57
|
+
* JOGADORES. Cada esquema do `KEYBOARD_DUO` passa no primeiro sozinho — e as setas, se ficassem nos dois,
|
|
58
|
+
* fariam os dois bonecos andarem juntos sem que nada reprovasse.
|
|
59
|
+
*
|
|
60
|
+
* A mensagem nomeia os dois donos, porque «tecla repetida» manda procurar o que a função já sabe.
|
|
61
|
+
*/
|
|
62
|
+
export declare function conflitosEntreTabelas<T>(tabelas: readonly Readonly<Record<Action, Binding<T | readonly T[]>>>[]): string[];
|
|
23
63
|
/** As ações que este transporte NÃO alcança. É o que uma tela de seleção precisa dizer ANTES de a criança começar. */
|
|
24
64
|
export declare function unreachable<T>(tabela: Readonly<Record<Action, Binding<T | readonly T[]>>>): Action[];
|
|
@@ -6,10 +6,20 @@
|
|
|
6
6
|
// físicos — L1, R2, X, A — são BINDING e vivem no transporte, não no vocabulário. Este é o ficheiro onde eles
|
|
7
7
|
// vivem. Um transporte novo (fala, olhar, toque) traz a sua tabela e não toca em `core/actions`.
|
|
8
8
|
//
|
|
9
|
-
// ⚠️
|
|
10
|
-
//
|
|
11
|
-
// escrita
|
|
12
|
-
//
|
|
9
|
+
// ⚠️ METADE DISTO ESTÁ LIGADA, e a linha que dizia o contrário morreu com a issue #103 (fechada em 06/09).
|
|
10
|
+
// Ela dizia «NADA AQUI ESTÁ LIGADO AINDA … a migração é a issue #103», e era verdade no dia em que foi
|
|
11
|
+
// escrita. Hoje:
|
|
12
|
+
//
|
|
13
|
+
// · `GAMEPAD_STANDARD` — LIGADO. `input/gamepad.ts:129` lê os índices desta tabela em vez de literais, e a
|
|
14
|
+
// ligação apanhou uma discordância real: `action1` corria em X, R1 e R2 enquanto a tabela declarava R1 e
|
|
15
|
+
// R2 como ombro e gatilho.
|
|
16
|
+
// · `KEYBOARD_SOLO` / `KEYBOARD_DUO` — NÃO LIGADOS. O jogo continua a usar o `KB_DEFAULTS` de
|
|
17
|
+
// `input/keyboard.ts`, que declara OITO das quatorze posições. Unir as duas é a issue #118, e o que
|
|
18
|
+
// impede a união hoje não é trabalho: é decidir se `KeyScheme` deixa de ser um `Record` aberto, e onde
|
|
19
|
+
// moram as seis posições que faltam num teclado dividido por quatro.
|
|
20
|
+
//
|
|
21
|
+
// ⚠️ ENQUANTO AS DUAS EXISTIREM, ELAS NÃO PODEM DIVERGIR EM SILÊNCIO, e `tests/teclado-duas-tabelas` é quem
|
|
22
|
+
// o garante. Nasceu vermelho: a `Space` do jogador 1 em dupla estava numa e não na outra.
|
|
13
23
|
//
|
|
14
24
|
// ========================= A SIMETRIA DO TECLADO, QUE NÃO É DECORAÇÃO =========================
|
|
15
25
|
// O padrão que o Dev especificou apoia-se num bloco do QWERTY:
|
|
@@ -28,11 +38,18 @@
|
|
|
28
38
|
// O gate deste ficheiro reprova tecla repetida, então o erro não teria passado de qualquer forma; o que a
|
|
29
39
|
// simetria deu foi a tecla CERTA em vez de só a notícia de que havia uma errada.
|
|
30
40
|
import { ACTIONS } from '../core/actions.js';
|
|
31
|
-
// ⚠️
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
41
|
+
// ⚠️ A `Space` ESTÁ EM `action2`, E ISTO É A HISTÓRIA DE COMO LÁ FOI PARAR — porque este bloco dizia o
|
|
42
|
+
// contrário e sobreviveu à decisão que o revogou.
|
|
43
|
+
//
|
|
44
|
+
// Ela ficou de fora da primeira versão desta tabela, e a razão estava certa: a barra é o segundo atalho do
|
|
45
|
+
// pulo desde sempre, a especificação do padrão não a mencionava, e pô-la em `action2` por conta própria
|
|
46
|
+
// seria decidir que o pulo mora ali. Se o preset da plataforma puser o pulo noutra posição, a barra segue o
|
|
47
|
+
// verbo errado — pior do que ela não existir.
|
|
48
|
+
//
|
|
49
|
+
// O Dev decidiu (ADR-0086 §2) que o pulo mora em `action2`, e a barra voltou. O texto acima continuou a
|
|
50
|
+
// dizer «fica de fora» durante todo esse tempo, ao lado de duas linhas que a declaram. Corrigido em
|
|
51
|
+
// 2026-09-07, na mesma passagem da issue #118 — que trata exactamente desta classe de erro: um comentário
|
|
52
|
+
// que descreve um estado que já passou.
|
|
36
53
|
/**
|
|
37
54
|
* Teclado, esquema SOLO. Códigos de `KeyboardEvent.code` — físicos, não a letra impressa, que muda com o
|
|
38
55
|
* layout ABNT2/US e é a razão de nunca se usar `key` aqui.
|
|
@@ -58,6 +75,92 @@ export const KEYBOARD_SOLO = {
|
|
|
58
75
|
start: ['KeyH', 'Enter'],
|
|
59
76
|
select: ['KeyF'],
|
|
60
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Teclado, esquema de DOIS JOGADORES — as quatorze posições para cada um (ADR-0096).
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ O JOGADOR 1 AQUI NÃO É O `KEYBOARD_SOLO`, e a diferença é UMA e obrigatória: as SETAS saem dele. No solo
|
|
82
|
+
* elas são um segundo caminho para o direcional; em dupla são o direcional DO OUTRO. Deixá-las nos dois faria
|
|
83
|
+
* os dois bonecos andarem juntos — um defeito que não dá erro em lado nenhum e que só se vê jogando a dois.
|
|
84
|
+
* `conflitosEntreTabelas` existe por causa desta linha.
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ E A GEOMETRIA DO JOGADOR 2 É A MESMA DO JOGADOR 1, TRANSPOSTA PARA O TECLADO NUMÉRICO — o que faz a
|
|
87
|
+
* memória muscular atravessar de um lado da mesa para o outro:
|
|
88
|
+
*
|
|
89
|
+
* 7 8 / * ombros na linha DE CIMA
|
|
90
|
+
* Y U I O 7 8 9 + gatilhos nas PONTAS da linha das ações
|
|
91
|
+
* J K 5 6
|
|
92
|
+
*
|
|
93
|
+
* action1 U ↔ Numpad8 (cima-esquerda) action4 I ↔ Numpad9 (cima-direita)
|
|
94
|
+
* action2 J ↔ Numpad5 (baixo-esquerda) action3 K ↔ Numpad6 (baixo-direita)
|
|
95
|
+
*
|
|
96
|
+
* O bloco `U I / J K` e o bloco `8 9 / 5 6` têm a MESMA forma, então a rotação de 45° que o cabeçalho deste
|
|
97
|
+
* ficheiro descreve para o Xbox vale igual para o jogador 2. Não é coincidência de teclado: é o que torna o
|
|
98
|
+
* padrão ensinável uma vez só.
|
|
99
|
+
*
|
|
100
|
+
* ⚠️ `Digit7`/`Digit8` (jogador 1) e `Numpad7` (jogador 2) SÃO TECLAS DIFERENTES, e é por isso que a
|
|
101
|
+
* especificação do Dev diz «alphanumeric» e «numeric» em tantas palavras. `KeyboardEvent.code` distingue-as
|
|
102
|
+
* sempre; `key` não — com Num Lock desligado o numérico chega como `ArrowUp`/`Home`, e um esquema lido por
|
|
103
|
+
* `key` juntaria o direcional do jogador 2 com as ações dele. Mais uma razão de este ficheiro só falar
|
|
104
|
+
* `code`.
|
|
105
|
+
*/
|
|
106
|
+
export const KEYBOARD_DUO = Object.freeze([
|
|
107
|
+
{
|
|
108
|
+
// JOGADOR 1 — a mão esquerda anda (WASD), a direita age (UIJK). Sem as setas: são do jogador 2.
|
|
109
|
+
up: ['KeyW'],
|
|
110
|
+
down: ['KeyS'],
|
|
111
|
+
left: ['KeyA'],
|
|
112
|
+
right: ['KeyD'],
|
|
113
|
+
action1: ['KeyU'],
|
|
114
|
+
action2: ['KeyJ', 'Space'],
|
|
115
|
+
action3: ['KeyK'],
|
|
116
|
+
action4: ['KeyI'],
|
|
117
|
+
leftShoulder: ['Digit7'],
|
|
118
|
+
leftTrigger: ['KeyY'],
|
|
119
|
+
rightShoulder: ['Digit8'],
|
|
120
|
+
rightTrigger: ['KeyO'],
|
|
121
|
+
start: ['KeyH', 'Enter'],
|
|
122
|
+
select: ['KeyF'],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
// JOGADOR 2 — as setas andam, o teclado numérico age. `Enter` fica com o jogador 1; o `NumpadEnter` não
|
|
126
|
+
// entra aqui de propósito, porque `ui/menu-nav` já o usa como CONFIRMAR em qualquer menu (KEY_YES) e
|
|
127
|
+
// `input/keydown` registra, verbatim, que ele NÃO pausa. Dar-lhe um terceiro trabalho seria sobrepor.
|
|
128
|
+
up: ['ArrowUp'],
|
|
129
|
+
down: ['ArrowDown'],
|
|
130
|
+
left: ['ArrowLeft'],
|
|
131
|
+
right: ['ArrowRight'],
|
|
132
|
+
action1: ['Numpad8'],
|
|
133
|
+
action2: ['Numpad5'],
|
|
134
|
+
action3: ['Numpad6'],
|
|
135
|
+
action4: ['Numpad9'],
|
|
136
|
+
leftShoulder: ['NumpadDivide'], // a tecla `/` do bloco numérico
|
|
137
|
+
leftTrigger: ['Numpad7'],
|
|
138
|
+
rightShoulder: ['NumpadMultiply'], // a tecla `*`
|
|
139
|
+
rightTrigger: ['NumpadAdd'], // a tecla `+`
|
|
140
|
+
/**
|
|
141
|
+
* ⚠️ `ShiftRight` É A PORTA DE SAÍDA, E ELA FALTAVA (#122).
|
|
142
|
+
*
|
|
143
|
+
* Um Chromebook não tem bloco numérico, e um Chromebook é o hardware que o pilar 1 nomeia. Medido em
|
|
144
|
+
* 2026-09-07: das catorze posições deste assento, **DEZ só se alcançam pelo numpad** — as oito ações
|
|
145
|
+
* mais o `start` e o `select`. A criança da segunda cadeira anda pelas setas, não age em nada, e **não
|
|
146
|
+
* consegue abrir o menu para consertar**, porque a tecla que abre o menu está no mesmo bloco que falta.
|
|
147
|
+
*
|
|
148
|
+
* Isso é o que a auditoria chamou de «um padrão do qual a criança não escapa», e o conserto que ela pede
|
|
149
|
+
* é textual: *«tornar o próprio padrão remapeável, e não trocar as teclas que ele escolheu».* As teclas
|
|
150
|
+
* ficam — o layout do numpad é melhor onde ele existe, é um bloco físico sob uma mão, e não tira nada ao
|
|
151
|
+
* primeiro jogador. O que entra é UMA porta que todo teclado tem, para o remapeamento ser alcançável.
|
|
152
|
+
*
|
|
153
|
+
* ⚠️ E É SÓ NO `start`, de propósito: da pausa alcança-se a tela de remapeamento, e de lá TODAS as outras
|
|
154
|
+
* treze posições. Uma porta chega para escapar; cada padrão a mais é uma tecla tirada do bolo comum, e
|
|
155
|
+
* este teclado já é repartido por duas crianças.
|
|
156
|
+
*
|
|
157
|
+
* `ShiftRight` porque existe em todo o teclado, fica ao lado do bloco de setas (a mesma mão que já as
|
|
158
|
+
* usa), não é tecla de texto — e está LIVRE nas quatro tabelas, o que foi medido e não suposto.
|
|
159
|
+
*/
|
|
160
|
+
start: ['Numpad1', 'ShiftRight'],
|
|
161
|
+
select: ['Numpad0'],
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
61
164
|
/**
|
|
62
165
|
* Gamepad, mapa PADRÃO da Gamepad API (`mapping: "standard"`), que é o que um controle de Xbox reporta.
|
|
63
166
|
* O número é o índice em `gamepad.buttons`.
|
|
@@ -116,6 +219,37 @@ export function bindingProblems(tabela) {
|
|
|
116
219
|
}
|
|
117
220
|
return p;
|
|
118
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* As teclas que DUAS OU MAIS tabelas reclamam para si. VAZIA quer dizer que os jogadores não se atropelam.
|
|
224
|
+
*
|
|
225
|
+
* ⚠️ ISTO É UM PROBLEMA DIFERENTE DO `bindingProblems`, E FOI POR ISSO QUE PRECISOU DE FUNÇÃO PRÓPRIA: aquele
|
|
226
|
+
* olha UMA tabela e apanha a mesma tecla em duas ações; este olha DUAS tabelas e apanha a mesma tecla em dois
|
|
227
|
+
* JOGADORES. Cada esquema do `KEYBOARD_DUO` passa no primeiro sozinho — e as setas, se ficassem nos dois,
|
|
228
|
+
* fariam os dois bonecos andarem juntos sem que nada reprovasse.
|
|
229
|
+
*
|
|
230
|
+
* A mensagem nomeia os dois donos, porque «tecla repetida» manda procurar o que a função já sabe.
|
|
231
|
+
*/
|
|
232
|
+
export function conflitosEntreTabelas(tabelas) {
|
|
233
|
+
const p = [];
|
|
234
|
+
const dono = new Map();
|
|
235
|
+
tabelas.forEach((tabela, i) => {
|
|
236
|
+
for (const acao of ACTIONS) {
|
|
237
|
+
const v = tabela[acao];
|
|
238
|
+
if (v === null || v === undefined)
|
|
239
|
+
continue;
|
|
240
|
+
for (const item of (Array.isArray(v) ? v : [v])) {
|
|
241
|
+
const chave = String(item);
|
|
242
|
+
const aqui = `p${i + 1}.${acao}`;
|
|
243
|
+
const anterior = dono.get(chave);
|
|
244
|
+
if (anterior)
|
|
245
|
+
p.push(`cross: ${chave} is claimed by both ${anterior} and ${aqui}`);
|
|
246
|
+
else
|
|
247
|
+
dono.set(chave, aqui);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
return p;
|
|
252
|
+
}
|
|
119
253
|
/** As ações que este transporte NÃO alcança. É o que uma tela de seleção precisa dizer ANTES de a criança começar. */
|
|
120
254
|
export function unreachable(tabela) {
|
|
121
255
|
return ACTIONS.filter((a) => tabela[a] === null || tabela[a] === undefined);
|
|
@@ -56,21 +56,28 @@ export declare function bindActive(gp: PadLike, bd: PadBinding | null | undefine
|
|
|
56
56
|
* custom caem de volta em stdDirs quando o binding do usuário não está ativo (D-pad/stick continuam vivos). */
|
|
57
57
|
export declare function padActions(gp: PadLike, custom: PadMap | null): PadActions;
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
59
|
+
* O MODO DE UM BOTÃO, APLICADO AO CONTROLE — a metade que faltava da empatia motora (issue #120).
|
|
60
60
|
*
|
|
61
|
-
* ⚠️
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* ⚠️ ELE VALIA SÓ NO TECLADO. `input/keydown.ts:407` solta todas as outras teclas de jogo quando uma nova
|
|
62
|
+
* chega com o modo ligado; `pollPads` não tinha equivalente nenhum, e `grep oneButton` neste ficheiro
|
|
63
|
+
* devolvia zero. Uma criança que ligasse o modo e tivesse um controle na mão **não estava no modo** — sem
|
|
64
|
+
* erro, sem aviso, sem sintoma, porque as definições continuavam a dizer que estava ligado.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
66
|
+
* ⚠️ AS DIREÇÕES CONTAM, e é isso que torna a regra fiel ao teclado: lá, `isGameKeyCode` inclui as teclas
|
|
67
|
+
* de `p.ctrl`, que são as quatro direções — andar e pular não coexistem. Um filtro que poupasse as
|
|
68
|
+
* direções seria mais confortável e estaria a simular outra deficiência.
|
|
69
69
|
*
|
|
70
|
-
* ⚠️ E
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* ⚠️ E A ESCOLHA DE QUEM SOBREVIVE É DIFERENTE DA DO TECLADO, POR NECESSIDADE. No teclado a chegada nova
|
|
71
|
+
* ganha, porque HÁ uma chegada: o evento diz qual é. Um controle é lido por SONDAGEM — o que chega é um
|
|
72
|
+
* retrato, sem ordem. Então mantém-se a que já valia, e só quando ela solta é que a próxima assume. É o
|
|
73
|
+
* que impede o botão de correr de ser cortado porque o polegar encostou noutro, e é a mesma leitura de
|
|
74
|
+
* «segurar» que o ADR-0077 dá.
|
|
75
|
+
*
|
|
76
|
+
* A POLÍTICA é a mesma do `keydown`; a IMPLEMENTAÇÃO não pode ser partilhada hoje porque as formas do
|
|
77
|
+
* estado diferem — lá é um `Set` de códigos de tecla, aqui é um retrato de booleanos por posição. Unificar
|
|
78
|
+
* as duas é trabalho à parte, e escrevê-lo aqui é a alternativa a fingir que não há duas.
|
|
73
79
|
*/
|
|
80
|
+
export declare function umBotaoPorVez(anterior: Readonly<Record<string, boolean | undefined>>, atual: PadActions, ligado: boolean): PadActions;
|
|
74
81
|
export declare const PADWIZ_ORDER: readonly string[];
|
|
75
82
|
export interface WizAnimDef {
|
|
76
83
|
seq?: string[];
|
|
@@ -11,6 +11,15 @@ 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
13
|
import { padCur, padPrevAct, padPrevStart, PAD_DEAD } from './state.js';
|
|
14
|
+
// ⚠️ O `oneButton` ENTRA POR IMPORT, e não pelo `ctx` — ao contrário de `input/keydown`, que o recebe por
|
|
15
|
+
// getter. A diferença não é de gosto: o `keydown` foi extraído quando o `oneButton` era um `let` do
|
|
16
|
+
// `game.js`, e a regra da casa manda o que o JOGO reatribui entrar por getter. Hoje ele é `core/state`,
|
|
17
|
+
// ou seja da ENGINE — e um getter obrigaria cada consumidor a lembrar-se de o passar.
|
|
18
|
+
//
|
|
19
|
+
// ⚠️ E ESQUECER É EXATAMENTE O DEFEITO QUE ISTO CONSERTA (issue #120). Um campo opcional no ctx faria a
|
|
20
|
+
// acomodação existir só nos jogos onde alguém se lembrou dela, que é o argumento M3 do ADR-0077 com outro
|
|
21
|
+
// substantivo. Binding vivo de `core/state`: não há por onde falhar.
|
|
22
|
+
import * as estadoDoJogo from '../core/state.js';
|
|
14
23
|
import * as store from '../platform/storage.js';
|
|
15
24
|
function bindingAt(map, key) {
|
|
16
25
|
const v = map[key];
|
|
@@ -117,6 +126,56 @@ export function padActions(gp, custom) {
|
|
|
117
126
|
* posição não tem o que mapear nela — e perguntar produziria um passo mudo, ou pior, um passo a dizer
|
|
118
127
|
* `action7` em voz alta.
|
|
119
128
|
*/
|
|
129
|
+
/**
|
|
130
|
+
* AS POSIÇÕES QUE O MODO DE UM BOTÃO NÃO CORTA. Pausar é a SAÍDA, não uma jogada.
|
|
131
|
+
*
|
|
132
|
+
* ⚠️ Cortar o START prenderia a criança dentro da partida — é o mesmo raciocínio que põe «a saída
|
|
133
|
+
* primeiro» no ADR-0044 e que fez a armadilha de foco existir no ADR-0090. Uma acomodação que tranca não
|
|
134
|
+
* é acomodação. Os derivados (`_start`, `_pause`) também passam: eles descrevem o que a raiz faz com
|
|
135
|
+
* estas duas posições, não uma terceira.
|
|
136
|
+
*/
|
|
137
|
+
const FORA_DO_CORTE = new Set(['start', 'select', '_start', '_pause']);
|
|
138
|
+
/**
|
|
139
|
+
* O MODO DE UM BOTÃO, APLICADO AO CONTROLE — a metade que faltava da empatia motora (issue #120).
|
|
140
|
+
*
|
|
141
|
+
* ⚠️ ELE VALIA SÓ NO TECLADO. `input/keydown.ts:407` solta todas as outras teclas de jogo quando uma nova
|
|
142
|
+
* chega com o modo ligado; `pollPads` não tinha equivalente nenhum, e `grep oneButton` neste ficheiro
|
|
143
|
+
* devolvia zero. Uma criança que ligasse o modo e tivesse um controle na mão **não estava no modo** — sem
|
|
144
|
+
* erro, sem aviso, sem sintoma, porque as definições continuavam a dizer que estava ligado.
|
|
145
|
+
*
|
|
146
|
+
* ⚠️ AS DIREÇÕES CONTAM, e é isso que torna a regra fiel ao teclado: lá, `isGameKeyCode` inclui as teclas
|
|
147
|
+
* de `p.ctrl`, que são as quatro direções — andar e pular não coexistem. Um filtro que poupasse as
|
|
148
|
+
* direções seria mais confortável e estaria a simular outra deficiência.
|
|
149
|
+
*
|
|
150
|
+
* ⚠️ E A ESCOLHA DE QUEM SOBREVIVE É DIFERENTE DA DO TECLADO, POR NECESSIDADE. No teclado a chegada nova
|
|
151
|
+
* ganha, porque HÁ uma chegada: o evento diz qual é. Um controle é lido por SONDAGEM — o que chega é um
|
|
152
|
+
* retrato, sem ordem. Então mantém-se a que já valia, e só quando ela solta é que a próxima assume. É o
|
|
153
|
+
* que impede o botão de correr de ser cortado porque o polegar encostou noutro, e é a mesma leitura de
|
|
154
|
+
* «segurar» que o ADR-0077 dá.
|
|
155
|
+
*
|
|
156
|
+
* A POLÍTICA é a mesma do `keydown`; a IMPLEMENTAÇÃO não pode ser partilhada hoje porque as formas do
|
|
157
|
+
* estado diferem — lá é um `Set` de códigos de tecla, aqui é um retrato de booleanos por posição. Unificar
|
|
158
|
+
* as duas é trabalho à parte, e escrevê-lo aqui é a alternativa a fingir que não há duas.
|
|
159
|
+
*/
|
|
160
|
+
export function umBotaoPorVez(
|
|
161
|
+
// ⚠️ O ANTERIOR É TIPADO PELO QUE ESTA FUNÇÃO LÊ, e não por `PadActions`: o `padPrevAct[gi]` do laço é
|
|
162
|
+
// `PadState`, mais frouxo, e exigir a forma completa obrigaria o chamador a um molde que não descreve o
|
|
163
|
+
// que se passa aqui — só se pergunta «esta chave estava em baixo?».
|
|
164
|
+
anterior, atual, ligado) {
|
|
165
|
+
if (!ligado)
|
|
166
|
+
return atual;
|
|
167
|
+
const cortaveis = Object.keys(atual).filter((k) => !FORA_DO_CORTE.has(k));
|
|
168
|
+
const ativas = cortaveis.filter((k) => atual[k] === true);
|
|
169
|
+
if (ativas.length <= 1)
|
|
170
|
+
return atual;
|
|
171
|
+
// A que já valia tem prioridade; sem nenhuma, a primeira do retrato assume.
|
|
172
|
+
const mantida = ativas.find((k) => anterior[k] === true) ?? ativas[0];
|
|
173
|
+
const saida = { ...atual };
|
|
174
|
+
for (const k of ativas)
|
|
175
|
+
if (k !== mantida)
|
|
176
|
+
saida[k] = false;
|
|
177
|
+
return saida;
|
|
178
|
+
}
|
|
120
179
|
export const PADWIZ_ORDER = [
|
|
121
180
|
// Direções primeiro: são o que a criança encontra sem pensar, e acertar as quatro dá confiança para as
|
|
122
181
|
// outras dez.
|
|
@@ -163,11 +222,15 @@ export function initGamepad(ctx) {
|
|
|
163
222
|
}
|
|
164
223
|
function actionsFor(gp) { return padActions(gp, padMapFor(gp.id)); }
|
|
165
224
|
// ----- wizard: anúncio + demo animada (DOM-facing, thin) -----
|
|
166
|
-
|
|
225
|
+
// ⚠️ O PARÂMETRO CHAMAVA-SE `t`, E ERA ELE QUE FECHAVA A PORTA. Dentro desta função o `t` do
|
|
226
|
+
// `core/i18n` estava sombreado, então traduzir uma frase aqui era impossível sem primeiro reparar no
|
|
227
|
+
// sombreamento — e não há erro nenhum a apontá-lo. Foi assim que cinco frases em português cru ficaram a
|
|
228
|
+
// falar dentro do motor (#123): não por decisão, por um nome.
|
|
229
|
+
function wizSay(frase) {
|
|
167
230
|
const el = ctx.$('#padwiz-prompt');
|
|
168
231
|
if (el)
|
|
169
|
-
el.textContent =
|
|
170
|
-
ctx.srSay(
|
|
232
|
+
el.textContent = frase;
|
|
233
|
+
ctx.srSay(frase);
|
|
171
234
|
}
|
|
172
235
|
function wizDemo(k) {
|
|
173
236
|
const d = ctx.$('#padwiz-demo');
|
|
@@ -224,11 +287,12 @@ export function initGamepad(ctx) {
|
|
|
224
287
|
return; // fechou ao avançar
|
|
225
288
|
const acao = PADWIZ_ORDER[padWiz.step];
|
|
226
289
|
const rotulo = ctx.rotuloDaAcao(acao);
|
|
227
|
-
wizSay((padWiz.step + 1
|
|
290
|
+
wizSay(t('pad.wiz.step', { n: padWiz.step + 1, total: PADWIZ_ORDER.length, acao: rotulo }));
|
|
228
291
|
wizDemo(acao); // demonstração animada do que a ação FAZ
|
|
229
292
|
const pr = ctx.$('#padwiz-progress');
|
|
293
|
+
// O travessão da lista vazia fica cru de propósito: é pontuação, não idioma.
|
|
230
294
|
if (pr)
|
|
231
|
-
pr.textContent = '
|
|
295
|
+
pr.textContent = t('pad.wiz.mapped', { lista: Object.keys(padWiz.map).join(' · ') || '—' });
|
|
232
296
|
}
|
|
233
297
|
function wizBind(bd) {
|
|
234
298
|
if (!padWiz)
|
|
@@ -246,7 +310,7 @@ export function initGamepad(ctx) {
|
|
|
246
310
|
ov.hidden = false;
|
|
247
311
|
ctx.frontOverlay(ov);
|
|
248
312
|
padWiz = { gi: -1, id: '', step: -1, base: null, map: {}, release: false, baseWait: false, axTrack: null, timer: null };
|
|
249
|
-
wizSay('
|
|
313
|
+
wizSay(t('pad.wiz.pressAny'));
|
|
250
314
|
wizDemo(null);
|
|
251
315
|
const pr = ctx.$('#padwiz-progress');
|
|
252
316
|
if (pr)
|
|
@@ -261,7 +325,7 @@ export function initGamepad(ctx) {
|
|
|
261
325
|
ov.hidden = false;
|
|
262
326
|
ctx.frontOverlay(ov);
|
|
263
327
|
padWiz = { gi: gp.index, id: gp.id, step: -1, base: null, map: {}, release: false, baseWait: true, axTrack: null, timer: null };
|
|
264
|
-
wizSay('
|
|
328
|
+
wizSay(t('pad.wiz.detected', { id: gp.id }));
|
|
265
329
|
wizDemo(null);
|
|
266
330
|
const pr = ctx.$('#padwiz-progress');
|
|
267
331
|
if (pr)
|
|
@@ -316,7 +380,7 @@ export function initGamepad(ctx) {
|
|
|
316
380
|
padWiz.gi = gp.index;
|
|
317
381
|
padWiz.id = gp.id;
|
|
318
382
|
padWiz.baseWait = true;
|
|
319
|
-
wizSay('
|
|
383
|
+
wizSay(t('pad.wiz.releaseAll', { id: gp.id }));
|
|
320
384
|
break;
|
|
321
385
|
}
|
|
322
386
|
}
|
|
@@ -405,8 +469,10 @@ export function initGamepad(ctx) {
|
|
|
405
469
|
openPadWizFor(gp);
|
|
406
470
|
return;
|
|
407
471
|
}
|
|
408
|
-
const cur = actionsFor(gp);
|
|
409
472
|
const prev = padPrevAct[gi] || {};
|
|
473
|
+
// ⚠️ A EMPATIA MOTORA APLICADA AO CONTROLE (issue #120). Sem esta linha, uma criança com o modo de
|
|
474
|
+
// um botão ligado e um pad na mão NÃO ESTAVA no modo — e nada em lado nenhum o dizia.
|
|
475
|
+
const cur = umBotaoPorVez(prev, actionsFor(gp), estadoDoJogo.oneButton);
|
|
410
476
|
if (ctx.isTouchMode() && (cur.left || cur.right || cur.up || cur.down || cur.action2 || cur.action1 || cur.action4 || cur.action3 || cur._start)) {
|
|
411
477
|
ctx.hideTouchControls(); // botão físico usado -> some o gamepad virtual (mesma regra do teclado)
|
|
412
478
|
}
|
|
@@ -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,6 +20,16 @@ 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;
|
|
24
34
|
export declare function loadKB(): KBDefaults;
|
|
25
35
|
export declare function saveKB(kb: KBDefaults): void;
|