@the-inclusionist/engine 8.0.0-rc.1 → 8.0.0

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.
Files changed (50) hide show
  1. package/app/public/vendor/fonts/playwrite-ar.woff2 +0 -0
  2. package/app/public/vendor/fonts/playwrite-br.woff2 +0 -0
  3. package/app/public/vendor/fonts/playwrite-ca.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-cl.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-co.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-mx.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-us-modern.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-us-trad.woff2 +0 -0
  9. package/app/public/vendor/fonts.css +21 -0
  10. package/dist-pkg/boot/create-game.d.ts +33 -4
  11. package/dist-pkg/boot/create-game.js +72 -11
  12. package/dist-pkg/core/contract.d.ts +60 -0
  13. package/dist-pkg/core/contract.js +42 -0
  14. package/dist-pkg/i18n/en.js +11 -0
  15. package/dist-pkg/i18n/es.js +11 -0
  16. package/dist-pkg/i18n/pt.js +14 -0
  17. package/dist-pkg/input/default-bindings.js +20 -12
  18. package/dist-pkg/input/gamepad.d.ts +15 -1
  19. package/dist-pkg/input/gamepad.js +30 -9
  20. package/dist-pkg/input/keyboard.d.ts +32 -0
  21. package/dist-pkg/input/keyboard.js +46 -6
  22. package/dist-pkg/input/keydown.d.ts +13 -0
  23. package/dist-pkg/input/keydown.js +9 -0
  24. package/dist-pkg/input/latch-edge.d.ts +28 -0
  25. package/dist-pkg/input/latch-edge.js +45 -0
  26. package/dist-pkg/input/latch-sync.d.ts +47 -0
  27. package/dist-pkg/input/latch-sync.js +43 -0
  28. package/dist-pkg/input/pad-defaults.d.ts +22 -0
  29. package/dist-pkg/input/pad-defaults.js +34 -0
  30. package/dist-pkg/input/touch-bindings.d.ts +10 -0
  31. package/dist-pkg/input/touch-bindings.js +6 -0
  32. package/dist-pkg/platform/pesados-catalogo.d.ts +14 -0
  33. package/dist-pkg/platform/pesados-catalogo.js +177 -0
  34. package/dist-pkg/platform/pesados.d.ts +31 -0
  35. package/dist-pkg/platform/pesados.js +75 -0
  36. package/dist-pkg/platform/voice-plan.d.ts +20 -1
  37. package/dist-pkg/platform/voice-plan.js +20 -1
  38. package/dist-pkg/platform/vozes-prontas.d.ts +28 -0
  39. package/dist-pkg/platform/vozes-prontas.js +61 -0
  40. package/dist-pkg/ui/fonts.d.ts +24 -0
  41. package/dist-pkg/ui/fonts.js +66 -1
  42. package/dist-pkg/ui/pause-icons.d.ts +36 -2
  43. package/dist-pkg/ui/pause-icons.js +9 -1
  44. package/dist-pkg/ui/settings-motor.d.ts +21 -1
  45. package/dist-pkg/ui/settings-motor.js +22 -4
  46. package/dist-pkg/ui/settings-typo.d.ts +18 -4
  47. package/dist-pkg/ui/settings-typo.js +16 -11
  48. package/docs/CREDITS.md +15 -12
  49. package/docs/LICENSES.md +170 -144
  50. package/package.json +2 -1
@@ -10,6 +10,7 @@ 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';
14
15
  // ⚠️ O `oneButton` ENTRA POR IMPORT, e não pelo `ctx` — ao contrário de `input/keydown`, que o recebe por
15
16
  // getter. A diferença não é de gosto: o `keydown` foi extraído quando o `oneButton` era um `let` do
@@ -72,7 +73,7 @@ export function bindActive(gp, bd) {
72
73
  /** Ações do frame para este gamepad. `custom` = mapa salvo pelo wizard para este `gp.id` (null/`_skip` = usa o
73
74
  * mapa PADRÃO da Gamepad API "standard": 0=pulo · 1=especial · 2/5/7=correr · 3=troca · 9=START). Direções
74
75
  * custom caem de volta em stdDirs quando o binding do usuário não está ativo (D-pad/stick continuam vivos). */
75
- export function padActions(gp, custom) {
76
+ export function padActions(gp, custom, tabela = GAMEPAD_STANDARD) {
76
77
  if (custom && !custom._skip) {
77
78
  const A = (k) => bindActive(gp, bindingAt(custom, k));
78
79
  const sd = stdDirs(gp);
@@ -91,7 +92,12 @@ export function padActions(gp, custom) {
91
92
  // correr, enquanto a tabela declara R1 como `rightShoulder` e R2 como `rightTrigger`. O ADR-0086 registrou
92
93
  // esta mudança como o asterisco do seu «zero movimento»: nenhum VERBO muda de botão, mas `run` perde dois
93
94
  // dos seus três. Quem usava R1 para correr sente — e é o preço de os quatro ombros existirem.
94
- const B = GAMEPAD_STANDARD;
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;
95
101
  const at = (a) => { const i = B[a]; return typeof i === 'number' ? b(i) : false; };
96
102
  return {
97
103
  left: sd.left, right: sd.right, up: sd.up, down: sd.down,
@@ -220,7 +226,7 @@ export function initGamepad(ctx) {
220
226
  }
221
227
  return _padMaps.get(id) ?? null;
222
228
  }
223
- function actionsFor(gp) { return padActions(gp, padMapFor(gp.id)); }
229
+ function actionsFor(gp, tabela) { return padActions(gp, padMapFor(gp.id), tabela); }
224
230
  // ----- wizard: anúncio + demo animada (DOM-facing, thin) -----
225
231
  // ⚠️ O PARÂMETRO CHAMAVA-SE `t`, E ERA ELE QUE FECHAVA A PORTA. Dentro desta função o `t` do
226
232
  // `core/i18n` estava sombreado, então traduzir uma frase aqui era impossível sem primeiro reparar no
@@ -470,9 +476,17 @@ export function initGamepad(ctx) {
470
476
  return;
471
477
  }
472
478
  const prev = padPrevAct[gi] || {};
473
- // ⚠️ A EMPATIA MOTORA APLICADA AO CONTROLE (issue #120). Sem esta linha, uma criança com o modo de
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
474
488
  // 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);
489
+ const cur = umBotaoPorVez(prev, actionsFor(gp, tabelaDoPad(ctx.getNumPlayers(), owner < 0 ? 0 : owner)), estadoDoJogo.oneButton);
476
490
  if (ctx.isTouchMode() && (cur.left || cur.right || cur.up || cur.down || cur.action2 || cur.action1 || cur.action4 || cur.action3 || cur._start)) {
477
491
  ctx.hideTouchControls(); // botão físico usado -> some o gamepad virtual (mesma regra do teclado)
478
492
  }
@@ -495,11 +509,9 @@ export function initGamepad(ctx) {
495
509
  // de propósito: numa cena que este módulo não conheça (um mapa, uma tela de resultados), o controle
496
510
  // deve navegar como no título — que é o comportamento seguro — em vez de não fazer nada.
497
511
  const rodando = ctx.mundoRodando(), pausado = ctx.menuDePausa();
498
- const players = ctx.getPlayers();
499
512
  if (!rodando && !pausado) {
500
513
  const k = { yes: edge('action2') || startEdge, no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
501
514
  const any = k.yes || k.no || k.up || k.down || k.left || k.right;
502
- const owner = players.findIndex((p) => p.pad === gi);
503
515
  if (ctx.getNumPlayers() > 1 && owner > 0) {
504
516
  if (any)
505
517
  ctx.srSay(t('sr.title.waitP1'));
@@ -510,7 +522,6 @@ export function initGamepad(ctx) {
510
522
  continue;
511
523
  }
512
524
  if (pausado) {
513
- const owner = players.findIndex((p) => p.pad === gi);
514
525
  const pi = owner < 0 ? 0 : owner;
515
526
  if (pauseEdge) {
516
527
  ctx.retomar();
@@ -530,7 +541,6 @@ export function initGamepad(ctx) {
530
541
  continue;
531
542
  }
532
543
  if (rodando) {
533
- const owner = players.findIndex((p) => p.pad === gi);
534
544
  // ===================== O MODO `accessibility` (ADR-0044, item 7) =====================
535
545
  // Com o jogo ANDANDO, o direcional deste jogador dirige a BARRA RÁPIDA e não o personagem. Vem antes
536
546
  // de tudo o que é de jogo, porque enquanto o modo está ligado nada mais deste controle é de jogo.
@@ -585,10 +595,21 @@ export function initGamepad(ctx) {
585
595
  }
586
596
  // A tabela e a guarda do Fácil vêm de input/edges.ts, as MESMAS que keydown e touch usam. Antes eram
587
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;
588
599
  for (const [act, flag] of EDGE_BY_ACTION) {
600
+ if (edge(act))
601
+ algumaAresta = true;
589
602
  if (edgeAllowed(act, p.easy) && edge(act))
590
603
  p[flag] = true;
591
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');
592
613
  }
593
614
  }
594
615
  }
@@ -31,6 +31,32 @@ export declare const KB_SCHEMES4: KeyScheme[];
31
31
  * voltar a existir, em vez de a apanhar depois de acontecer.
32
32
  */
33
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;
34
60
  export declare function loadKB(): KBDefaults;
35
61
  export declare function saveKB(kb: KBDefaults): void;
36
62
  /**
@@ -52,4 +78,10 @@ export declare function initKB(): KBDefaults;
52
78
  /** Troca o mapa inteiro. Só o "restaurar padrões" do painel de controles precisa disto — remapear uma tecla
53
79
  * MUTA o objeto, e reatribuir por engano faria as referências vivas apontarem para o mapa antigo. */
54
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
+ */
55
87
  export declare function resetKB(): KBDefaults;
@@ -57,13 +57,47 @@ export const KB_DEFAULTS = {
57
57
  // A FORMA vem de `vocabulary-migration`, que é quem a traduz — declarar aqui outra vez seria a
58
58
  // cópia que o `core/entity` passou o mês a eliminar.
59
59
  import { migrarSalvo } from './vocabulary-migration.js';
60
- // ⚠️ A MIGRAÇÃO DE VOCABULÁRIO MORA NOUTRO FICHEIRO, e a separação é deliberada:
61
- // `input/vocabulary-migration.ts` é o ÚNICO sítio da engine autorizado a dizer `jump`, porque traduzir o
62
- // nome antigo é a função dele. Deixá-la aqui punha o acoplamento num módulo que não é histórico, e o gate
63
- // `action-vocabulary-boundary` reprovou — corretamente. Ver o cabeçalho de lá para saber quando se apaga.
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
+ }
64
96
  // carrega os esquemas salvos SOBRE os defaults (com migração do dado antigo p34 → p3+p4)
65
97
  export function loadKB() {
66
- const d = JSON.parse(JSON.stringify(KB_DEFAULTS));
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();
67
101
  // ⚠️ O DADO SALVO ATRAVESSA O TRADUTOR ANTES DE TOCAR NOS PADRÕES. Sem esta linha, um esquema gravado com
68
102
  // as chaves antigas (`run`, `jump`, `swap`, `especial`) seria fundido sobre defaults que já usam
69
103
  // `action1`..`action4`: o objeto ficaria com AS DUAS famílias de chaves, os transportes leriam só as novas,
@@ -107,4 +141,10 @@ export function initKB() { kb = loadKB(); return kb; }
107
141
  /** Troca o mapa inteiro. Só o "restaurar padrões" do painel de controles precisa disto — remapear uma tecla
108
142
  * MUTA o objeto, e reatribuir por engano faria as referências vivas apontarem para o mapa antigo. */
109
143
  export function setKB(next) { kb = next; }
110
- export function resetKB() { store.remove(CKEY); return JSON.parse(JSON.stringify(KB_DEFAULTS)); }
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(); }
@@ -245,6 +245,19 @@ export interface KeydownCtx {
245
245
  * que ela existe, e é justamente ele quem produz os eventos que caem aqui.
246
246
  */
247
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;
248
261
  soltarTecla: (code: string) => void;
249
262
  /** `let oneButton` do game.js (empatia motora) → getter. */
250
263
  isOneButton: () => boolean;
@@ -368,6 +368,15 @@ export function initKeydown(ctx) {
368
368
  ctx.marcarTecla(code, origem);
369
369
  else
370
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
+ }
371
380
  return;
372
381
  }
373
382
  }
@@ -0,0 +1,28 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { type JogadorDaAlternancia } from './latch-sync.js';
3
+ import type { ArmazemDaAlternancia } from './latch-store.js';
4
+ import type { Transporte } from './transporte-em-uso.js';
5
+ export interface ArestaComAlternanciaOpts {
6
+ /**
7
+ * O armazenamento. Injectável **para o gate**, e com padrão para o consumidor não poder esquecê-lo.
8
+ *
9
+ * ⚠️ Padrão e não campo obrigatório, e a razão é a mesma do `ui/pause-icons`: um armazém injectado é uma
10
+ * coisa que um cartucho pode omitir, e omiti-la faria a criança perder a escolha guardada — em silêncio, e
11
+ * só naquele jogo.
12
+ */
13
+ readonly armazem?: ArmazemDaAlternancia;
14
+ /** O padrão de fábrica. `DEFAULTS.toggleMove`, e não `false` escrito à mão: há UMA fonte (ADR-0029). */
15
+ readonly padrao?: boolean;
16
+ }
17
+ /**
18
+ * DEVOLVE O `arestaDoJogador` QUE TAMBÉM RESOLVE A ALTERNÂNCIA — para passar a `initKeydown` e a
19
+ * `initTouchBindings` no lugar do cru.
20
+ *
21
+ * ⚠️ RESOLVE CONTRA `entradaDe(jogador).emUso` E NÃO CONTRA `origem`, e a distinção é de desenho e não de
22
+ * comportamento: hoje o `aposAresta` põe sempre `emUso = origem`, logo trocar uma pela outra é uma mutação
23
+ * EQUIVALENTE — está registada como tal no gate, em vez de fingir cobertura. O que a escolha compra é o
24
+ * futuro: quem decide que aparelho está em uso é o autómato, e o dia em que ele ganhar uma regra que RECUSE
25
+ * uma aresta (um falso positivo da webcam a ser filtrado, por exemplo) esta linha segue-o sem ser editada.
26
+ * Ler `origem` seria uma segunda resposta à pergunta que o `input/transporte-em-uso` existe para responder.
27
+ */
28
+ export declare function criarArestaComAlternancia(getPlayers: () => readonly (JogadorDaAlternancia | null | undefined)[], opts?: ArestaComAlternanciaOpts): (jogador: number, origem: Transporte) => void;
@@ -0,0 +1,45 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/latch-edge.ts — A ARESTA E A ALTERNÂNCIA, NUMA FUNÇÃO SÓ (ADR-0113, issue #127).
3
+ //
4
+ // ========================= POR QUE O PAR NÃO PODE SEPARAR-SE =========================
5
+ // Registar de que aparelho veio a aresta e resolver a alternância desse aparelho são duas metades de UM
6
+ // acontecimento: a criança mudou de controle. Feitas em sítios diferentes, a segunda pode faltar — e o
7
+ // resultado é a forma de defeito que este projecto já pagou várias vezes: o autómato sabe que ela pegou no
8
+ // controle, e o jogo continua a andar com a alternância do teclado. Sem erro, e só ela dá por isso.
9
+ //
10
+ // 📌 É o mesmo movimento do `consumir` no `ui/menu-nav` («uma função só para o par nunca se separar») e da
11
+ // escrita dupla no `ui/settings-motor`.
12
+ //
13
+ // ⚠️ E É UMA FÁBRICA, NÃO UM CAMPO NOVO NO CONTEXTO DE CADA MÓDULO DE ENTRADA. O `input/keydown` e o
14
+ // `input/touch-bindings` já recebem `arestaDoJogador(jogador, origem)`; esta função devolve uma com essa
15
+ // mesma assinatura, e por isso a fiação passa a estar certa sem nenhum dos dois saber que a alternância
16
+ // existe. Um segundo campo obrigatório em dois contextos seria mais superfície pública, mais uma coisa que um
17
+ // cartucho pode esquecer, e a mesma pergunta feita duas vezes.
18
+ import * as store from '../platform/storage.js';
19
+ import { DEFAULTS } from '../core/state.js';
20
+ import { arestaDoJogador, entradaDe } from './state.js';
21
+ import { sincronizarAlternancia } from './latch-sync.js';
22
+ /**
23
+ * DEVOLVE O `arestaDoJogador` QUE TAMBÉM RESOLVE A ALTERNÂNCIA — para passar a `initKeydown` e a
24
+ * `initTouchBindings` no lugar do cru.
25
+ *
26
+ * ⚠️ RESOLVE CONTRA `entradaDe(jogador).emUso` E NÃO CONTRA `origem`, e a distinção é de desenho e não de
27
+ * comportamento: hoje o `aposAresta` põe sempre `emUso = origem`, logo trocar uma pela outra é uma mutação
28
+ * EQUIVALENTE — está registada como tal no gate, em vez de fingir cobertura. O que a escolha compra é o
29
+ * futuro: quem decide que aparelho está em uso é o autómato, e o dia em que ele ganhar uma regra que RECUSE
30
+ * uma aresta (um falso positivo da webcam a ser filtrado, por exemplo) esta linha segue-o sem ser editada.
31
+ * Ler `origem` seria uma segunda resposta à pergunta que o `input/transporte-em-uso` existe para responder.
32
+ */
33
+ export function criarArestaComAlternancia(getPlayers, opts = {}) {
34
+ const armazem = opts.armazem ?? store;
35
+ const padrao = opts.padrao ?? DEFAULTS.toggleMove;
36
+ return (jogador, origem) => {
37
+ arestaDoJogador(jogador, origem);
38
+ // 📌 O JOGADOR PODE NÃO EXISTIR — uma tela em espera, um assento que ainda não entrou —, e isso não torna
39
+ // a aresta inválida: o transporte em uso é facto sobre a ENTRADA e fica registado à mesma. O que não
40
+ // acontece é a segunda metade, porque não há onde a escrever.
41
+ const p = getPlayers()[jogador];
42
+ if (p)
43
+ sincronizarAlternancia(p, armazem, jogador, entradaDe(jogador).emUso, padrao);
44
+ };
45
+ }
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { PlayerView } from '../core/entity.js';
3
+ import { type ArmazemDaAlternancia } from './latch-store.js';
4
+ /**
5
+ * O MÍNIMO DO JOGADOR que a alternância toca — dois campos, e nenhum deles é do contrato do jogo.
6
+ *
7
+ * 📌 `PlayerView` e não `PlayerBase`: este módulo não tem nada que fazer com sprites, física ou câmara, e um
8
+ * tipo largo aqui faria um gate precisar de um jogador inteiro de mentira para afirmar duas linhas.
9
+ *
10
+ * ⚠️ E ELE MUDOU DE CASA EM VEZ DE NASCER SEGUNDO. Esta linha existia, palavra por palavra, no
11
+ * `ui/settings-motor` — e escrevê-la aqui outra vez seria a segunda cópia de um tipo, que é o defeito que
12
+ * este repositório já pagou dezasseis vezes com o `DomQuery`. O painel passou a publicá-la por ALIAS, para o
13
+ * retrato de nomes não a ler como removida.
14
+ */
15
+ export type JogadorDaAlternancia = PlayerView<'toggleMove' | 'walkDir'>;
16
+ /**
17
+ * A base com que a alternância de MARCHA vive no armazenamento da criança.
18
+ *
19
+ * ⚠️ A IRMÃ (`togglerun`, a alternância de CORRER) NÃO ENTRA AQUI HOJE, e a ausência é declarada em vez de
20
+ * esquecida: `p.toggleRun` tem um leitor de RODADA no cartucho (`game/run-toggle`), com uma trava própria que
21
+ * diz se está a correr AGORA — o `walkDir = 0` daqui não é o gesto certo para ela, e inventar-lhe um sem o
22
+ * leitor à frente seria decidir por um jogo que não abri. O `input/latch-store` já aceita a base como
23
+ * argumento, então quando ela entrar entra sem uma segunda forma da mesma pergunta.
24
+ */
25
+ export declare const BASE_DA_MARCHA = "togglemove";
26
+ /**
27
+ * PÕE A ALTERNÂNCIA DE MARCHA NO JOGADOR, com a regra que a acompanha. Devolve se MUDOU alguma coisa.
28
+ *
29
+ * ⚠️ O `walkDir = 0` só corre quando a alternância CAI. Zerá-lo em toda chamada tiraria a direcção a quem
30
+ * está a andar, uma vez por aresta — que é o defeito ao contrário, e mais frequente.
31
+ *
32
+ * 📌 Devolver «mudou» não é conveniência: quem chama na aresta corre isto muitas vezes por segundo, e anunciar
33
+ * ou reflectir a cada chamada encheria o leitor de tela com a mesma frase.
34
+ */
35
+ export declare function aplicarAlternancia(p: JogadorDaAlternancia, ligada: boolean): boolean;
36
+ /**
37
+ * A ALTERNÂNCIA DESTE JOGADOR, RESOLVIDA PARA O TRANSPORTE EM USO E ESCRITA NELE. Devolve se mudou.
38
+ *
39
+ * É a cláusula 1 do ADR-0113 em código, e a propriedade que ela promete é NEGATIVA: chamar isto ao trocar de
40
+ * aparelho troca a resposta **sem escrever no armazenamento**. Um `sincronizar` que gravasse o valor resolvido
41
+ * apagaria, na primeira aresta, a escolha que a criança fez no outro controle.
42
+ *
43
+ * ⚠️ E NOS QUATRO ASSISTIDOS ELE RESPONDE `true` SEM CONSULTAR NADA — a regra vive no `latch-scope` e a razão
44
+ * está lá: em olhos, rosto, gestos e fala a alternância é o que faz a entrada funcionar, e herdar um `false`
45
+ * que a criança escolheu no teclado deixá-la-ia com um controle de olhar que não responde.
46
+ */
47
+ export declare function sincronizarAlternancia(p: JogadorDaAlternancia, armazem: ArmazemDaAlternancia, jogador: number, transporte: string, padrao: boolean): boolean;
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { alternanciaGuardada } from './latch-store.js';
3
+ /**
4
+ * A base com que a alternância de MARCHA vive no armazenamento da criança.
5
+ *
6
+ * ⚠️ A IRMÃ (`togglerun`, a alternância de CORRER) NÃO ENTRA AQUI HOJE, e a ausência é declarada em vez de
7
+ * esquecida: `p.toggleRun` tem um leitor de RODADA no cartucho (`game/run-toggle`), com uma trava própria que
8
+ * diz se está a correr AGORA — o `walkDir = 0` daqui não é o gesto certo para ela, e inventar-lhe um sem o
9
+ * leitor à frente seria decidir por um jogo que não abri. O `input/latch-store` já aceita a base como
10
+ * argumento, então quando ela entrar entra sem uma segunda forma da mesma pergunta.
11
+ */
12
+ export const BASE_DA_MARCHA = 'togglemove';
13
+ /**
14
+ * PÕE A ALTERNÂNCIA DE MARCHA NO JOGADOR, com a regra que a acompanha. Devolve se MUDOU alguma coisa.
15
+ *
16
+ * ⚠️ O `walkDir = 0` só corre quando a alternância CAI. Zerá-lo em toda chamada tiraria a direcção a quem
17
+ * está a andar, uma vez por aresta — que é o defeito ao contrário, e mais frequente.
18
+ *
19
+ * 📌 Devolver «mudou» não é conveniência: quem chama na aresta corre isto muitas vezes por segundo, e anunciar
20
+ * ou reflectir a cada chamada encheria o leitor de tela com a mesma frase.
21
+ */
22
+ export function aplicarAlternancia(p, ligada) {
23
+ if (p.toggleMove === ligada)
24
+ return false;
25
+ p.toggleMove = ligada;
26
+ if (!ligada)
27
+ p.walkDir = 0;
28
+ return true;
29
+ }
30
+ /**
31
+ * A ALTERNÂNCIA DESTE JOGADOR, RESOLVIDA PARA O TRANSPORTE EM USO E ESCRITA NELE. Devolve se mudou.
32
+ *
33
+ * É a cláusula 1 do ADR-0113 em código, e a propriedade que ela promete é NEGATIVA: chamar isto ao trocar de
34
+ * aparelho troca a resposta **sem escrever no armazenamento**. Um `sincronizar` que gravasse o valor resolvido
35
+ * apagaria, na primeira aresta, a escolha que a criança fez no outro controle.
36
+ *
37
+ * ⚠️ E NOS QUATRO ASSISTIDOS ELE RESPONDE `true` SEM CONSULTAR NADA — a regra vive no `latch-scope` e a razão
38
+ * está lá: em olhos, rosto, gestos e fala a alternância é o que faz a entrada funcionar, e herdar um `false`
39
+ * que a criança escolheu no teclado deixá-la-ia com um controle de olhar que não responde.
40
+ */
41
+ export function sincronizarAlternancia(p, armazem, jogador, transporte, padrao) {
42
+ return aplicarAlternancia(p, alternanciaGuardada(armazem, BASE_DA_MARCHA, jogador, transporte, padrao));
43
+ }
@@ -0,0 +1,22 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Action } from '../core/actions.js';
3
+ import { type Binding } from './default-bindings.js';
4
+ /** O que o jogo declara: só o que ele quer mudar. `null` num botão é «esta posição não existe neste jogo». */
5
+ export type MapeamentoDoPad = (jogadores: number, assento: number) => Partial<Record<Action, number | null>> | null;
6
+ export type TabelaDoPad = Readonly<Record<Action, Binding<number>>>;
7
+ /**
8
+ * REGISTA O PADRÃO DO JOGO. Chamado uma vez pelo arranque; `null` limpa (é o que um jogo sem opinião produz).
9
+ *
10
+ * ⚠️ LIMPA A MEMÓRIA, e sem esta linha o registo seria pior do que não existir: uma segunda montagem — outro
11
+ * jogo na mesma página, um teste a seguir a outro — leria a tabela do jogo anterior, e a leitura estaria
12
+ * certa em toda parte menos no valor.
13
+ */
14
+ export declare function registrarMapeamentoDoPad(f: MapeamentoDoPad | null): void;
15
+ /**
16
+ * A TABELA DE BOTÕES PARA ESTE ARRANJO E ESTE ASSENTO — fábrica da engine com o padrão do jogo por cima.
17
+ *
18
+ * 📌 MEMOIZADA porque isto é lido no laço de sondagem, uma vez por controle e por quadro: fundir dois objectos
19
+ * sessenta vezes por segundo por jogador é lixo que nenhuma criança vê e que o coletor paga. A chave é
20
+ * `arranjo:assento`, e o registo limpa-a — que é o único momento em que a resposta pode mudar.
21
+ */
22
+ export declare function tabelaDoPad(jogadores: number, assento: number): TabelaDoPad;
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { GAMEPAD_STANDARD } from './default-bindings.js';
3
+ let mapeamentoDoJogo = null;
4
+ const memo = new Map();
5
+ /**
6
+ * REGISTA O PADRÃO DO JOGO. Chamado uma vez pelo arranque; `null` limpa (é o que um jogo sem opinião produz).
7
+ *
8
+ * ⚠️ LIMPA A MEMÓRIA, e sem esta linha o registo seria pior do que não existir: uma segunda montagem — outro
9
+ * jogo na mesma página, um teste a seguir a outro — leria a tabela do jogo anterior, e a leitura estaria
10
+ * certa em toda parte menos no valor.
11
+ */
12
+ export function registrarMapeamentoDoPad(f) {
13
+ mapeamentoDoJogo = f;
14
+ memo.clear();
15
+ }
16
+ /**
17
+ * A TABELA DE BOTÕES PARA ESTE ARRANJO E ESTE ASSENTO — fábrica da engine com o padrão do jogo por cima.
18
+ *
19
+ * 📌 MEMOIZADA porque isto é lido no laço de sondagem, uma vez por controle e por quadro: fundir dois objectos
20
+ * sessenta vezes por segundo por jogador é lixo que nenhuma criança vê e que o coletor paga. A chave é
21
+ * `arranjo:assento`, e o registo limpa-a — que é o único momento em que a resposta pode mudar.
22
+ */
23
+ export function tabelaDoPad(jogadores, assento) {
24
+ if (!mapeamentoDoJogo)
25
+ return GAMEPAD_STANDARD;
26
+ const chave = `${jogadores}:${assento}`;
27
+ const guardada = memo.get(chave);
28
+ if (guardada)
29
+ return guardada;
30
+ const parcial = mapeamentoDoJogo(jogadores, assento);
31
+ const tabela = parcial ? Object.freeze({ ...GAMEPAD_STANDARD, ...parcial }) : GAMEPAD_STANDARD;
32
+ memo.set(chave, tabela);
33
+ return tabela;
34
+ }
@@ -174,6 +174,16 @@ export interface TouchBindingsCtx {
174
174
  /** o array vivo de jogadores. Getter: o main.js o repovoa a cada `restartGame`. */
175
175
  getPlayers: () => readonly TouchBindPlayer[];
176
176
  marcarTecla: (code: string, origem: Transporte) => void;
177
+ /**
178
+ * ESTA ARESTA É DESTE JOGADOR, E VEIO DO TOQUE (ADR-0113 cláusula 4, issue #127) —
179
+ * `input/state.arestaDoJogador`.
180
+ *
181
+ * 🔴 OBRIGATÓRIO, e é aqui que a troca de aparelho fica VISÍVEL: o toque é o transporte que a criança usa
182
+ * ao lado do teclado, e sem esta linha o autómato responde `teclado` mesmo com o dedo no ecrã — logo a
183
+ * alternância lida seria a do teclado, no aparelho errado. 📌 O `onTouchControlsShown` do cartucho
184
+ * (`main.ts:1695`) é o remendo que existe hoje exactamente para compensar esta falta.
185
+ */
186
+ arestaDoJogador: (jogador: number, origem: Transporte) => void;
177
187
  soltarTecla: (code: string) => void;
178
188
  /**
179
189
  * O conjunto para LER — a decisão pura pergunta que teclas já estão seguradas.
@@ -236,6 +236,12 @@ export function initTouchBindings(ctx) {
236
236
  const p = players[playerIndex];
237
237
  if (p)
238
238
  p[edge] = true;
239
+ // 📌 A ARESTA POR JOGADOR, ao lado da borda que ela levanta — e não uma vez por toque: o mesmo código
240
+ // pode pertencer a mais de um assento (`d.edges` é construído com `includes` sobre o esquema de cada
241
+ // um), e o transporte em uso é uma pergunta POR CRIANÇA. Marcar só o jogador 0 daria a alternância do
242
+ // primeiro assento a quem joga no segundo.
243
+ if (p)
244
+ ctx.arestaDoJogador(playerIndex, 'toque');
239
245
  }
240
246
  }
241
247
  if (d.hideTips)
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** Uma coisa pesada que a engine promete e que não cabe no pacote. */
3
+ export interface Pesado {
4
+ readonly id: string;
5
+ /** `null` = decidido que existe, mas ainda não há de onde vir. Ver `porQueNaoTemFonte`. */
6
+ readonly url: string | null;
7
+ /** Medido, não estimado — é o número que uma frase honesta usa antes de começar a descarga. */
8
+ readonly bytes?: number;
9
+ /** Obrigatório quando `url` é `null`: uma ausência sem razão escrita vira uma ausência esquecida. */
10
+ readonly porQueNaoTemFonte?: string;
11
+ }
12
+ /** O nome da Cache Storage. Versionado: mudar o conteúdo do catálogo não deve servir bytes velhos. */
13
+ export declare const CACHE_PESADOS = "incl-pesados-v1";
14
+ export declare const PESADOS: readonly Pesado[];