@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.
Files changed (136) hide show
  1. package/README.md +5 -3
  2. package/app/css/style.css +22 -5
  3. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  4. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  5. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-ar.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-br.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-ca.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-cl.woff2 +0 -0
  10. package/app/public/vendor/fonts/playwrite-co.woff2 +0 -0
  11. package/app/public/vendor/fonts/playwrite-mx.woff2 +0 -0
  12. package/app/public/vendor/fonts/playwrite-us-modern.woff2 +0 -0
  13. package/app/public/vendor/fonts/playwrite-us-trad.woff2 +0 -0
  14. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  15. package/app/public/vendor/fonts.css +64 -2
  16. package/dist-pkg/boot/create-game.d.ts +107 -4
  17. package/dist-pkg/boot/create-game.js +337 -18
  18. package/dist-pkg/core/constants.d.ts +0 -28
  19. package/dist-pkg/core/constants.js +34 -17
  20. package/dist-pkg/core/contract.d.ts +99 -0
  21. package/dist-pkg/core/contract.js +78 -0
  22. package/dist-pkg/core/entity.d.ts +47 -4
  23. package/dist-pkg/core/layers.d.ts +18 -0
  24. package/dist-pkg/core/layers.js +18 -0
  25. package/dist-pkg/core/rng.js +14 -4
  26. package/dist-pkg/core/route.d.ts +42 -0
  27. package/dist-pkg/core/route.js +158 -0
  28. package/dist-pkg/core/state.d.ts +10 -1
  29. package/dist-pkg/core/state.js +13 -0
  30. package/dist-pkg/educational/adaptive-engine.d.ts +65 -0
  31. package/dist-pkg/educational/adaptive-engine.js +117 -0
  32. package/dist-pkg/educational/segment-bar.d.ts +96 -0
  33. package/dist-pkg/educational/segment-bar.js +89 -0
  34. package/dist-pkg/i18n/en.js +44 -0
  35. package/dist-pkg/i18n/es.js +44 -0
  36. package/dist-pkg/i18n/pt.js +60 -0
  37. package/dist-pkg/input/default-bindings.d.ts +40 -0
  38. package/dist-pkg/input/default-bindings.js +151 -9
  39. package/dist-pkg/input/gamepad.d.ts +33 -12
  40. package/dist-pkg/input/gamepad.js +103 -16
  41. package/dist-pkg/input/keyboard-runtime.d.ts +6 -8
  42. package/dist-pkg/input/keyboard-runtime.js +24 -5
  43. package/dist-pkg/input/keyboard.d.ts +42 -0
  44. package/dist-pkg/input/keyboard.js +87 -17
  45. package/dist-pkg/input/keydown.d.ts +40 -2
  46. package/dist-pkg/input/keydown.js +29 -6
  47. package/dist-pkg/input/latch-edge.d.ts +28 -0
  48. package/dist-pkg/input/latch-edge.js +45 -0
  49. package/dist-pkg/input/latch-scope.d.ts +70 -0
  50. package/dist-pkg/input/latch-scope.js +110 -0
  51. package/dist-pkg/input/latch-store.d.ts +45 -0
  52. package/dist-pkg/input/latch-store.js +74 -0
  53. package/dist-pkg/input/latch-sync.d.ts +47 -0
  54. package/dist-pkg/input/latch-sync.js +43 -0
  55. package/dist-pkg/input/origem-sintetica.d.ts +44 -0
  56. package/dist-pkg/input/origem-sintetica.js +60 -0
  57. package/dist-pkg/input/pad-defaults.d.ts +22 -0
  58. package/dist-pkg/input/pad-defaults.js +34 -0
  59. package/dist-pkg/input/pointer.d.ts +61 -0
  60. package/dist-pkg/input/pointer.js +71 -0
  61. package/dist-pkg/input/state.d.ts +86 -1
  62. package/dist-pkg/input/state.js +128 -1
  63. package/dist-pkg/input/touch-bindings.d.ts +21 -2
  64. package/dist-pkg/input/touch-bindings.js +15 -3
  65. package/dist-pkg/input/touch.js +9 -1
  66. package/dist-pkg/input/transporte-em-uso.d.ts +101 -0
  67. package/dist-pkg/input/transporte-em-uso.js +130 -0
  68. package/dist-pkg/input/transports.d.ts +88 -2
  69. package/dist-pkg/input/transports.js +60 -5
  70. package/dist-pkg/input/vocabulary-migration.d.ts +18 -7
  71. package/dist-pkg/platform/audio-earcons.d.ts +29 -2
  72. package/dist-pkg/platform/audio-earcons.js +10 -0
  73. package/dist-pkg/platform/audio-nav.d.ts +1 -1
  74. package/dist-pkg/platform/audio-sonar.d.ts +149 -9
  75. package/dist-pkg/platform/audio-sonar.js +232 -21
  76. package/dist-pkg/platform/guide-intensity.d.ts +40 -0
  77. package/dist-pkg/platform/guide-intensity.js +73 -0
  78. package/dist-pkg/platform/pesados-catalogo.d.ts +14 -0
  79. package/dist-pkg/platform/pesados-catalogo.js +177 -0
  80. package/dist-pkg/platform/pesados.d.ts +31 -0
  81. package/dist-pkg/platform/pesados.js +75 -0
  82. package/dist-pkg/platform/storage.d.ts +30 -0
  83. package/dist-pkg/platform/storage.js +35 -0
  84. package/dist-pkg/platform/tts.js +16 -1
  85. package/dist-pkg/platform/voice-plan.d.ts +109 -0
  86. package/dist-pkg/platform/voice-plan.js +156 -0
  87. package/dist-pkg/platform/vozes-prontas.d.ts +28 -0
  88. package/dist-pkg/platform/vozes-prontas.js +61 -0
  89. package/dist-pkg/render/draw.d.ts +1 -1
  90. package/dist-pkg/render/draw.js +15 -5
  91. package/dist-pkg/render/viz-axes.d.ts +17 -0
  92. package/dist-pkg/render/viz-axes.js +19 -0
  93. package/dist-pkg/render/viz-setters.d.ts +34 -2
  94. package/dist-pkg/render/viz-setters.js +189 -33
  95. package/dist-pkg/render/wheelchair-sprites.d.ts +11 -3
  96. package/dist-pkg/render/wheelchair-sprites.js +10 -4
  97. package/dist-pkg/ui/dom.js +20 -2
  98. package/dist-pkg/ui/fonts.d.ts +71 -1
  99. package/dist-pkg/ui/fonts.js +111 -8
  100. package/dist-pkg/ui/latch-refusal.d.ts +32 -0
  101. package/dist-pkg/ui/latch-refusal.js +60 -0
  102. package/dist-pkg/ui/layout.d.ts +32 -0
  103. package/dist-pkg/ui/layout.js +64 -1
  104. package/dist-pkg/ui/motion-scene.d.ts +41 -0
  105. package/dist-pkg/ui/motion-scene.js +76 -0
  106. package/dist-pkg/ui/panel-shell.d.ts +53 -0
  107. package/dist-pkg/ui/panel-shell.js +103 -0
  108. package/dist-pkg/ui/pause-icons.d.ts +168 -20
  109. package/dist-pkg/ui/pause-icons.js +315 -45
  110. package/dist-pkg/ui/reach-notice.js +8 -0
  111. package/dist-pkg/ui/settings-audio.d.ts +14 -1
  112. package/dist-pkg/ui/settings-audio.js +43 -3
  113. package/dist-pkg/ui/settings-controls.d.ts +66 -6
  114. package/dist-pkg/ui/settings-controls.js +179 -17
  115. package/dist-pkg/ui/settings-empathy.d.ts +15 -0
  116. package/dist-pkg/ui/settings-empathy.js +2 -0
  117. package/dist-pkg/ui/settings-motion.d.ts +25 -19
  118. package/dist-pkg/ui/settings-motion.js +32 -19
  119. package/dist-pkg/ui/settings-motor.d.ts +101 -3
  120. package/dist-pkg/ui/settings-motor.js +136 -9
  121. package/dist-pkg/ui/settings-typo.d.ts +49 -5
  122. package/dist-pkg/ui/settings-typo.js +74 -23
  123. package/dist-pkg/ui/settings-visual.d.ts +21 -1
  124. package/dist-pkg/ui/settings-visual.js +48 -4
  125. package/dist-pkg/ui/shell.d.ts +13 -3
  126. package/dist-pkg/ui/shell.js +3 -4
  127. package/dist-pkg/ui/simulation-refusal.d.ts +32 -0
  128. package/dist-pkg/ui/simulation-refusal.js +57 -0
  129. package/dist-pkg/ui/visual-axes-panel.d.ts +48 -0
  130. package/dist-pkg/ui/visual-axes-panel.js +95 -0
  131. package/dist-pkg/ui/webcam.js +6 -1
  132. package/docs/CREDITS.md +22 -1
  133. package/docs/LICENSES.md +170 -132
  134. package/package.json +25 -4
  135. package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
  136. package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
@@ -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,70 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * Os transportes que emitem UM COMANDO DE CADA VEZ, e em que a alternância está sempre ligada.
4
+ *
5
+ * Os nomes são os que a barra rápida já usa para os ícones (`face`, `eyes`, `voice`), mais `gestos`, que é o
6
+ * quarto que o ADR-0104 §C nomeia. Ficam em português como o resto do vocabulário de transporte
7
+ * (`teclado`, `toque`) — `transportesPadrao` já mistura, e mudar isso é outra conversa.
8
+ */
9
+ export declare const UM_COMANDO_DE_CADA_VEZ: ReadonlySet<string>;
10
+ /**
11
+ * Neste transporte a alternância está sempre ligada?
12
+ *
13
+ * ⚠️ «Sempre ligada» e «ligada por omissão» são coisas diferentes, e a diferença é a que o ADR-0104 §C faz:
14
+ * um padrão pode ser mudado, e mudá-lo aqui deixaria o controle inutilizável. Por isso o valor guardado nem
15
+ * chega a ser lido nestes transportes — ver `alternanciaDe`.
16
+ */
17
+ export declare function alternanciaSempreLigada(transporte: string): boolean;
18
+ /**
19
+ * A opção deve ser OFERECIDA para este transporte?
20
+ *
21
+ * O contrário de `alternanciaSempreLigada`, e existe com nome próprio porque quem pergunta é outro: um
22
+ * chama para decidir o estado, o outro para decidir se desenha o botão. Um painel que desenhasse o botão e
23
+ * ignorasse o clique seria pior do que não o desenhar.
24
+ */
25
+ export declare function alternanciaEhEscolha(transporte: string): boolean;
26
+ /**
27
+ * A chave de armazenamento da alternância, agora com o transporte no nome.
28
+ *
29
+ * ⚠️ FUNÇÃO, e não concatenação no ponto de uso, pela razão que o `platform/storage` já escreveu sobre as
30
+ * chaves por jogador: «virar função aqui é o que impede que um deles escreva num nome torto». Não é
31
+ * hipótese — o `ui/settings-motor` reescrevia `'incl_togglerun_p' + i` à mão, com um comentário ao lado a
32
+ * dizer «== toggleRunP de platform/storage». Duas cópias de um nome mudam uma de cada vez.
33
+ *
34
+ * `base` é `togglemove` ou `togglerun`, os dois nomes que já existem no armazenamento da criança.
35
+ */
36
+ export declare function chaveDaAlternancia(base: string, jogador: number, transporte: string): string;
37
+ /**
38
+ * A chave ANTIGA, por jogador e sem transporte. Continua a ser lida, e nunca mais escrita.
39
+ *
40
+ * ⚠️ ELA HERDA PARA TODOS OS TRANSPORTES, e a escolha custa uma frase a explicar. O valor velho foi posto
41
+ * pela criança nalgum contexto, e não há como saber qual — a chave não o registava, que é o defeito. As
42
+ * saídas eram três: perder o ajuste dela, adivinhar um transporte, ou herdar para todos. Herdar para todos é
43
+ * a única que não tira nada a quem depende do ajuste, e o vazamento que ela mantém dura só até a criança
44
+ * mexer no assunto uma vez em cada aparelho. Perder o ajuste custaria mais, e a quem menos pode pagar.
45
+ *
46
+ * É também o padrão que este repositório já escolheu para este mesmo valor: o `KEYS.toggleMoveLegacy` existe
47
+ * desde a migração anterior, e a nota do `platform/storage` diz porquê — «a chave velha fica onde está: é
48
+ * dado da criança, não meu para apagar, e a sua permanência é o que torna um retorno possível».
49
+ */
50
+ export declare function chaveLegadaDaAlternancia(base: string, jogador: number): string;
51
+ /** O que se sabe ao resolver a alternância de um transporte. */
52
+ export interface LeituraDaAlternancia {
53
+ /** O que está guardado para ESTE transporte. `null` = nunca foi escrito. */
54
+ readonly doTransporte: boolean | null;
55
+ /** O que está guardado na chave antiga, sem transporte. `null` = nunca foi escrito. */
56
+ readonly doLegado: boolean | null;
57
+ /** O padrão de fábrica (`DEFAULTS.toggleMove` / `DEFAULTS.toggleRun`). */
58
+ readonly padrao: boolean;
59
+ }
60
+ /**
61
+ * A alternância deste transporte, resolvida.
62
+ *
63
+ * A ordem é: transporte de um comando → SEMPRE ligada, e nem se lê o resto · valor deste transporte · valor
64
+ * legado · padrão de fábrica.
65
+ *
66
+ * ⚠️ O TRANSPORTE DE UM COMANDO VEM PRIMEIRO, e não por atalho: se ele lesse o guardado primeiro, uma
67
+ * criança que tivesse desligado a alternância no teclado herdaria esse `false` pelo legado e ficaria com um
68
+ * controle de olhar que não responde — o pior defeito possível, no controle de quem tem menos alternativas.
69
+ */
70
+ export declare function alternanciaDe(transporte: string, l: LeituraDaAlternancia): boolean;
@@ -0,0 +1,110 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/latch-scope — A ALTERNÂNCIA É DE UM TRANSPORTE, e não da criança (ADR-0104 §C, issue #114).
3
+ //
4
+ // ========================= O DEFEITO QUE ISTO CONSERTA, E ELE NÃO TINHA NOME =========================
5
+ // «Segurar vira alternar» estava guardado POR JOGADOR — `incl_togglemove_p0` —, o que quer dizer: por
6
+ // pessoa, e para todos os aparelhos ao mesmo tempo. Uma criança que liga a alternância no controle de TELA,
7
+ // porque num botão virtual ninguém segura com conforto, liga-a também no teclado, onde segurar uma tecla é
8
+ // exactamente o que ela sabe fazer. Ela não pediu isso e nada lho diz.
9
+ //
10
+ // ⚠️ E O REPOSITÓRIO JÁ CONHECIA O DEFEITO SEM O NOMEAR: o `core/state` traz a nota «a alternância do botão
11
+ // de CORRER nasce desligada de FÁBRICA — e liga sozinha no controle de tela, que é CONTEXTO e não escolha».
12
+ // Contexto é precisamente a palavra: o valor depende do aparelho em que a criança está. Guardá-lo por pessoa
13
+ // obrigava a distinguir «ligou porque quis» de «ligou porque é toque» com uma marca à parte (o ADR-0029), e
14
+ // essa marca existia para compensar uma chave que estava no escopo errado.
15
+ //
16
+ // O mapeamento de teclas já se guarda por transporte, e sempre se guardou. Esta é a mesma coisa.
17
+ //
18
+ // ========================= E PARA QUATRO TRANSPORTES ELA NÃO É ESCOLHA NENHUMA =========================
19
+ // ⚠️ Olhos, rosto, gestos e fala emitem UM COMANDO DE CADA VEZ. Não há como olhar para a esquerda e para o
20
+ // botão de pular ao mesmo tempo; não há como dizer duas palavras em simultâneo. Neles a alternância não é
21
+ // preferência — é a única forma de o controle funcionar, e oferecê-la como opção seria oferecer a uma
22
+ // criança a escolha de um controle que não funciona.
23
+ //
24
+ // Isto resolve, de passagem, uma tensão que o ADR-0084 tinha contra a sua própria regra «valor salvo
25
+ // significa escolha»: a alternância a ligar-se sozinha na webcam era uma excepção àquela regra. Deixa de
26
+ // ser, porque nestes transportes ela nunca foi um valor salvo — é uma propriedade do transporte.
27
+ //
28
+ // ⚠️ OS QUATRO AINDA NÃO EXISTEM COMO TRANSPORTE, e a regra fica escrita à mesma. Medido em 2026-09-08: o
29
+ // `transportesPadrao` devolve três (gamepad, teclado, toque), e o `ui/webcam` sintetiza `KeyboardEvent` — do
30
+ // ponto de vista da engine, ele É o teclado. Os três ícones da barra rápida dizem-no: `face`, `eyes` e
31
+ // `voice` estão marcados `soon`. Escrever a regra agora custa nada e faz com que eles cheguem COBERTOS, em
32
+ // vez de chegarem a uma excepção que alguém terá de se lembrar de abrir.
33
+ //
34
+ // Módulo-folha: não importa nada, nem sequer o `platform/storage` cujas chaves ele monta.
35
+ /**
36
+ * Os transportes que emitem UM COMANDO DE CADA VEZ, e em que a alternância está sempre ligada.
37
+ *
38
+ * Os nomes são os que a barra rápida já usa para os ícones (`face`, `eyes`, `voice`), mais `gestos`, que é o
39
+ * quarto que o ADR-0104 §C nomeia. Ficam em português como o resto do vocabulário de transporte
40
+ * (`teclado`, `toque`) — `transportesPadrao` já mistura, e mudar isso é outra conversa.
41
+ */
42
+ export const UM_COMANDO_DE_CADA_VEZ = new Set(['olhos', 'rosto', 'gestos', 'fala']);
43
+ /**
44
+ * Neste transporte a alternância está sempre ligada?
45
+ *
46
+ * ⚠️ «Sempre ligada» e «ligada por omissão» são coisas diferentes, e a diferença é a que o ADR-0104 §C faz:
47
+ * um padrão pode ser mudado, e mudá-lo aqui deixaria o controle inutilizável. Por isso o valor guardado nem
48
+ * chega a ser lido nestes transportes — ver `alternanciaDe`.
49
+ */
50
+ export function alternanciaSempreLigada(transporte) {
51
+ return UM_COMANDO_DE_CADA_VEZ.has(transporte);
52
+ }
53
+ /**
54
+ * A opção deve ser OFERECIDA para este transporte?
55
+ *
56
+ * O contrário de `alternanciaSempreLigada`, e existe com nome próprio porque quem pergunta é outro: um
57
+ * chama para decidir o estado, o outro para decidir se desenha o botão. Um painel que desenhasse o botão e
58
+ * ignorasse o clique seria pior do que não o desenhar.
59
+ */
60
+ export function alternanciaEhEscolha(transporte) {
61
+ return !alternanciaSempreLigada(transporte);
62
+ }
63
+ /**
64
+ * A chave de armazenamento da alternância, agora com o transporte no nome.
65
+ *
66
+ * ⚠️ FUNÇÃO, e não concatenação no ponto de uso, pela razão que o `platform/storage` já escreveu sobre as
67
+ * chaves por jogador: «virar função aqui é o que impede que um deles escreva num nome torto». Não é
68
+ * hipótese — o `ui/settings-motor` reescrevia `'incl_togglerun_p' + i` à mão, com um comentário ao lado a
69
+ * dizer «== toggleRunP de platform/storage». Duas cópias de um nome mudam uma de cada vez.
70
+ *
71
+ * `base` é `togglemove` ou `togglerun`, os dois nomes que já existem no armazenamento da criança.
72
+ */
73
+ export function chaveDaAlternancia(base, jogador, transporte) {
74
+ return `incl_${base}_p${jogador}_${transporte}`;
75
+ }
76
+ /**
77
+ * A chave ANTIGA, por jogador e sem transporte. Continua a ser lida, e nunca mais escrita.
78
+ *
79
+ * ⚠️ ELA HERDA PARA TODOS OS TRANSPORTES, e a escolha custa uma frase a explicar. O valor velho foi posto
80
+ * pela criança nalgum contexto, e não há como saber qual — a chave não o registava, que é o defeito. As
81
+ * saídas eram três: perder o ajuste dela, adivinhar um transporte, ou herdar para todos. Herdar para todos é
82
+ * a única que não tira nada a quem depende do ajuste, e o vazamento que ela mantém dura só até a criança
83
+ * mexer no assunto uma vez em cada aparelho. Perder o ajuste custaria mais, e a quem menos pode pagar.
84
+ *
85
+ * É também o padrão que este repositório já escolheu para este mesmo valor: o `KEYS.toggleMoveLegacy` existe
86
+ * desde a migração anterior, e a nota do `platform/storage` diz porquê — «a chave velha fica onde está: é
87
+ * dado da criança, não meu para apagar, e a sua permanência é o que torna um retorno possível».
88
+ */
89
+ export function chaveLegadaDaAlternancia(base, jogador) {
90
+ return `incl_${base}_p${jogador}`;
91
+ }
92
+ /**
93
+ * A alternância deste transporte, resolvida.
94
+ *
95
+ * A ordem é: transporte de um comando → SEMPRE ligada, e nem se lê o resto · valor deste transporte · valor
96
+ * legado · padrão de fábrica.
97
+ *
98
+ * ⚠️ O TRANSPORTE DE UM COMANDO VEM PRIMEIRO, e não por atalho: se ele lesse o guardado primeiro, uma
99
+ * criança que tivesse desligado a alternância no teclado herdaria esse `false` pelo legado e ficaria com um
100
+ * controle de olhar que não responde — o pior defeito possível, no controle de quem tem menos alternativas.
101
+ */
102
+ export function alternanciaDe(transporte, l) {
103
+ if (alternanciaSempreLigada(transporte))
104
+ return true;
105
+ if (l.doTransporte !== null)
106
+ return l.doTransporte;
107
+ if (l.doLegado !== null)
108
+ return l.doLegado;
109
+ return l.padrao;
110
+ }
@@ -0,0 +1,45 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { type LeituraDaAlternancia } from './latch-scope.js';
3
+ /** O mínimo do `platform/storage` que isto precisa. Injectado, para o gate não precisar de um navegador. */
4
+ export interface ArmazemDaAlternancia {
5
+ /** ⚠️ O CRU, e não `getBool`. Ver `lerTriEstado`. */
6
+ get(chave: string, padrao?: null): string | null;
7
+ set(chave: string, valor: string): void;
8
+ }
9
+ /**
10
+ * TRÊS ESTADOS E NÃO DOIS: `true`, `false`, e NUNCA ESCRITO.
11
+ *
12
+ * 🔴 ESTA FUNÇÃO EXISTE PARA NÃO SE USAR `getBool`, e a diferença custa o ajuste de uma criança. O `getBool`
13
+ * colapsa «nunca escrito» em `false`. Com ele, o `doTransporte` de uma criança que nunca mexeu neste aparelho
14
+ * chegaria à regra como `false` — e a regra responde na PRIMEIRA linha que encontra um valor, logo devolveria
15
+ * `false` e **nunca consultaria a chave legada**, que é onde vive o ajuste que ela já tinha.
16
+ *
17
+ * ⚠️ É por isso que o `latch-scope` tipa os dois campos como `boolean | null` e tem um caso próprio a dizer
18
+ * que «`false` guardado é um VALOR, e não uma ausência». Este é o lado do armazenamento da mesma frase.
19
+ */
20
+ export declare function lerTriEstado(armazem: ArmazemDaAlternancia, chave: string): boolean | null;
21
+ /**
22
+ * A LEITURA COMPLETA que a regra pede, montada a partir do armazenamento.
23
+ *
24
+ * `base` é `togglemove` ou `togglerun` — os dois nomes que já existem no armazenamento da criança.
25
+ */
26
+ export declare function leituraDaAlternancia(armazem: ArmazemDaAlternancia, base: string, jogador: number, transporte: string, padrao: boolean): LeituraDaAlternancia;
27
+ /**
28
+ * A ALTERNÂNCIA DESTE JOGADOR NESTE TRANSPORTE — a pergunta inteira, numa chamada.
29
+ *
30
+ * 📌 TROCAR DE TRANSPORTE TROCA A RESPOSTA SEM ESCREVER NADA, que é a cláusula 1 do ADR-0113 em código: o
31
+ * valor pertence ao mapeamento do controle, como um caps-lock, e mudar de controle é mudar de mapeamento.
32
+ */
33
+ export declare function alternanciaGuardada(armazem: ArmazemDaAlternancia, base: string, jogador: number, transporte: string, padrao: boolean): boolean;
34
+ /**
35
+ * GRAVA A ESCOLHA DA CRIANÇA para o transporte em uso. Devolve se gravou.
36
+ *
37
+ * ⚠️ RECUSA NOS TRANSPORTES DE UM COMANDO, e a recusa é um `false` devolvido e não um lançamento: em olhos,
38
+ * rosto, gestos e fala a alternância é o que faz a entrada funcionar (ADR-0113 cláusula 3), então não há
39
+ * escolha a gravar. Quem chama usa a resposta para DESABILITAR o controle COM MOTIVO — que é a metade que
40
+ * falta e que vive na interface, não aqui.
41
+ *
42
+ * 📌 Gravar mesmo assim seria pior do que inútil: a criança mexeria no ícone, o valor iria para o disco, e o
43
+ * jogo continuaria a ignorá-lo — um controle que mente sobre ter funcionado.
44
+ */
45
+ export declare function gravarAlternancia(escrever: (chave: string, ligada: boolean) => void, base: string, jogador: number, transporte: string, ligada: boolean): boolean;
@@ -0,0 +1,74 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/latch-store.ts — A ALTERNÂNCIA, LIDA E ESCRITA NO ARMAZENAMENTO (ADR-0113).
3
+ //
4
+ // ========================= O QUE ESTE MÓDULO É, E POR QUE É SEPARADO =========================
5
+ // O `input/latch-scope` é a REGRA e não toca em nada: recebe uma `LeituraDaAlternancia` já feita e responde.
6
+ // Este módulo é a única coisa que faltava entre ela e o mundo — quem vai ao armazenamento buscar os três
7
+ // valores que a regra pede, e quem grava o que a criança escolhe.
8
+ //
9
+ // 📌 SEPARADO DE PROPÓSITO, e não por arrumação: a regra é pura e tem gate próprio; misturar armazenamento
10
+ // dentro dela obrigaria todo caso da regra a montar um `localStorage` de mentira para afirmar uma coisa que
11
+ // não depende dele. É a mesma divisão que o `render/viz-axes` (modelo) e o `render/viz-setters` (escrita)
12
+ // já fazem neste repositório.
13
+ //
14
+ // ⚠️ E O QUE ELE NÃO FAZ: não sabe QUAL transporte está em uso. Isso é do `input/transporte-em-uso`, e chega
15
+ // aqui como argumento. Um módulo de armazenamento que adivinhasse o transporte escreveria a escolha de uma
16
+ // criança na chave de outro aparelho — em silêncio, que é o defeito que o ADR-0113 existe para evitar.
17
+ import { chaveDaAlternancia, chaveLegadaDaAlternancia, alternanciaDe, alternanciaEhEscolha, } from './latch-scope.js';
18
+ /**
19
+ * TRÊS ESTADOS E NÃO DOIS: `true`, `false`, e NUNCA ESCRITO.
20
+ *
21
+ * 🔴 ESTA FUNÇÃO EXISTE PARA NÃO SE USAR `getBool`, e a diferença custa o ajuste de uma criança. O `getBool`
22
+ * colapsa «nunca escrito» em `false`. Com ele, o `doTransporte` de uma criança que nunca mexeu neste aparelho
23
+ * chegaria à regra como `false` — e a regra responde na PRIMEIRA linha que encontra um valor, logo devolveria
24
+ * `false` e **nunca consultaria a chave legada**, que é onde vive o ajuste que ela já tinha.
25
+ *
26
+ * ⚠️ É por isso que o `latch-scope` tipa os dois campos como `boolean | null` e tem um caso próprio a dizer
27
+ * que «`false` guardado é um VALOR, e não uma ausência». Este é o lado do armazenamento da mesma frase.
28
+ */
29
+ export function lerTriEstado(armazem, chave) {
30
+ const v = armazem.get(chave, null);
31
+ return v == null ? null : v === '1';
32
+ }
33
+ /**
34
+ * A LEITURA COMPLETA que a regra pede, montada a partir do armazenamento.
35
+ *
36
+ * `base` é `togglemove` ou `togglerun` — os dois nomes que já existem no armazenamento da criança.
37
+ */
38
+ export function leituraDaAlternancia(armazem, base, jogador, transporte, padrao) {
39
+ return {
40
+ doTransporte: lerTriEstado(armazem, chaveDaAlternancia(base, jogador, transporte)),
41
+ doLegado: lerTriEstado(armazem, chaveLegadaDaAlternancia(base, jogador)),
42
+ padrao,
43
+ };
44
+ }
45
+ /**
46
+ * A ALTERNÂNCIA DESTE JOGADOR NESTE TRANSPORTE — a pergunta inteira, numa chamada.
47
+ *
48
+ * 📌 TROCAR DE TRANSPORTE TROCA A RESPOSTA SEM ESCREVER NADA, que é a cláusula 1 do ADR-0113 em código: o
49
+ * valor pertence ao mapeamento do controle, como um caps-lock, e mudar de controle é mudar de mapeamento.
50
+ */
51
+ export function alternanciaGuardada(armazem, base, jogador, transporte, padrao) {
52
+ return alternanciaDe(transporte, leituraDaAlternancia(armazem, base, jogador, transporte, padrao));
53
+ }
54
+ /**
55
+ * GRAVA A ESCOLHA DA CRIANÇA para o transporte em uso. Devolve se gravou.
56
+ *
57
+ * ⚠️ RECUSA NOS TRANSPORTES DE UM COMANDO, e a recusa é um `false` devolvido e não um lançamento: em olhos,
58
+ * rosto, gestos e fala a alternância é o que faz a entrada funcionar (ADR-0113 cláusula 3), então não há
59
+ * escolha a gravar. Quem chama usa a resposta para DESABILITAR o controle COM MOTIVO — que é a metade que
60
+ * falta e que vive na interface, não aqui.
61
+ *
62
+ * 📌 Gravar mesmo assim seria pior do que inútil: a criança mexeria no ícone, o valor iria para o disco, e o
63
+ * jogo continuaria a ignorá-lo — um controle que mente sobre ter funcionado.
64
+ */
65
+ // ⚠️ RECEBE O ESCRITOR E NÃO UM ARMAZÉM, e a mudança é de 2026-09-08, quando o painel foi ligar-se a isto.
66
+ // O `ui/settings-motor` já tem um `store: { setBool }` injectado — exigir-lhe um objecto com `get`/`set`
67
+ // crus obrigaria a inventar um adaptador no ponto de uso, e um adaptador ali é onde uma segunda forma de
68
+ // escrever a mesma chave nasce. Uma função é o mínimo que a escrita precisa.
69
+ export function gravarAlternancia(escrever, base, jogador, transporte, ligada) {
70
+ if (!alternanciaEhEscolha(transporte))
71
+ return false;
72
+ escrever(chaveDaAlternancia(base, jogador, transporte), ligada);
73
+ return true;
74
+ }
@@ -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,44 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Transporte } from './transporte-em-uso.js';
3
+ /**
4
+ * A propriedade pendurada no evento.
5
+ *
6
+ * 📌 Prefixada e feia de propósito: é um expando num objecto que não é nosso, e um nome curto («origem»)
7
+ * podia colidir com o de outra biblioteca sem que nada o dissesse.
8
+ */
9
+ export declare const CHAVE_DE_ORIGEM = "__vpOrigem";
10
+ /**
11
+ * O mínimo que este módulo lê de um evento de tecla — ESTRUTURAL, para um `KeyboardEvent` real e um duplo de
12
+ * teste servirem os dois.
13
+ *
14
+ * ⚠️ `isTrusted` é OPCIONAL, e a ausência dele não é o mesmo que `false` por acaso: um duplo que não o declara
15
+ * está a dizer «não afirmei nada sobre isto», e a resposta certa a isso é `undefined` e não `'teclado'`. É a
16
+ * mesma regra do `origemDe` do `input/state`, aplicada uma camada acima.
17
+ */
18
+ export interface EventoDeTeclaLike {
19
+ readonly isTrusted?: boolean;
20
+ }
21
+ /**
22
+ * DECLARA que este evento veio daquele aparelho. Devolve o próprio evento, para o despacho ficar numa linha.
23
+ *
24
+ * ⚠️ Carimba-se ANTES de despachar. Depois de `dispatchEvent` os ouvintes já correram, e o carimbo chegaria
25
+ * a um evento que ninguém mais vai ler.
26
+ */
27
+ export declare function carimbarOrigem<T extends object>(ev: T, origem: Transporte): T;
28
+ /**
29
+ * QUEM PRODUZIU ESTE EVENTO? `undefined` quando não se sabe.
30
+ *
31
+ * A regra inteira, em três linhas e por esta ordem:
32
+ *
33
+ * 1. **Carimbo válido ganha.** Uma declaração explícita vence sempre uma inferência — inverter isto faria
34
+ * um evento REAL que alguém reatribuiu (um pedal, um interruptor de sopro que emite teclas de verdade)
35
+ * ser lido como teclado, apagando exactamente a informação que quem carimbou se deu ao trabalho de pôr.
36
+ * 2. **Sem carimbo mas de confiança → `'teclado'`.** É o que `isTrusted` significa: o navegador viu a
37
+ * pessoa carregar. É a única inferência que este módulo faz, e fá-la sobre a propriedade que não se forja.
38
+ * 3. **Sem carimbo e sem confiança → `undefined`.** Um evento sintético que ninguém assinou. Depois desta
39
+ * migração nada nesta engine produz um; quem o produz é código de fora, e código de fora não declarou.
40
+ * ⚠️ Devolver `'teclado'` aqui seria a erasão a voltar por outra porta, que é o defeito que o ADR-0109
41
+ * inteiro existe para fechar — e seria pior do que a erasão original, porque teria a forma de uma
42
+ * resposta.
43
+ */
44
+ export declare function origemDoEvento(ev: EventoDeTeclaLike): Transporte | undefined;
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/origem-sintetica.ts — QUEM DESPACHOU ESTA TECLA, quando não foi um dedo num teclado (ADR-0109).
3
+ //
4
+ // ========================= O PONTO DIFÍCIL DA ARESTA, E ELE ESTAVA NOMEADO =========================
5
+ // O crivo `tests/origem-da-tecla` carrega esta frase na entrada do `input/keydown` desde que o
6
+ // estrangulamento começou: «a webcam despacha `KeyboardEvent` SINTÉTICO, entra pelo `keydown` e seria
7
+ // carimbada `teclado` — a erasão a voltar pela porta da frente». O `ui/webcam` constrói um `KeyboardEvent` e
8
+ // despacha-o na janela (`ui/webcam.ts`, `eyeSet`), e do lado de lá ele é indistinguível de uma tecla premida.
9
+ //
10
+ // `isTrusted` distingue PREMIDA de DESPACHADA — é a única propriedade que um script não consegue forjar — mas
11
+ // não diz QUAL transporte assistido despachou. Isso tem de chegar DECLARADO, e é o que este módulo é.
12
+ //
13
+ // ⚠️ POR QUE O CARIMBO VIAJA NO EVENTO, e não num «qual é a fonte sintética agora?» injectado. A resposta
14
+ // injectada é um estado GLOBAL, e a entrada não é global: uma criança que joga por olhar e tem um adulto a
15
+ // carregar numa tecla ao lado produz as duas arestas no mesmo instante, e o estado global carimbaria as duas
16
+ // como olhar. O evento não se confunde consigo próprio — a origem anda com a aresta a que pertence, que é a
17
+ // mesma razão por que o `origemDaTecla` é um mapa por CÓDIGO e não um campo só.
18
+ //
19
+ // 📌 E é aditivo por construção: um evento sem carimbo continua a funcionar. Foi isso que permitiu migrar os
20
+ // escritores um a um sem nenhum commit vermelho pelo meio.
21
+ import { ehTransporte } from './transporte-em-uso.js';
22
+ /**
23
+ * A propriedade pendurada no evento.
24
+ *
25
+ * 📌 Prefixada e feia de propósito: é um expando num objecto que não é nosso, e um nome curto («origem»)
26
+ * podia colidir com o de outra biblioteca sem que nada o dissesse.
27
+ */
28
+ export const CHAVE_DE_ORIGEM = '__vpOrigem';
29
+ /**
30
+ * DECLARA que este evento veio daquele aparelho. Devolve o próprio evento, para o despacho ficar numa linha.
31
+ *
32
+ * ⚠️ Carimba-se ANTES de despachar. Depois de `dispatchEvent` os ouvintes já correram, e o carimbo chegaria
33
+ * a um evento que ninguém mais vai ler.
34
+ */
35
+ export function carimbarOrigem(ev, origem) {
36
+ ev[CHAVE_DE_ORIGEM] = origem;
37
+ return ev;
38
+ }
39
+ /**
40
+ * QUEM PRODUZIU ESTE EVENTO? `undefined` quando não se sabe.
41
+ *
42
+ * A regra inteira, em três linhas e por esta ordem:
43
+ *
44
+ * 1. **Carimbo válido ganha.** Uma declaração explícita vence sempre uma inferência — inverter isto faria
45
+ * um evento REAL que alguém reatribuiu (um pedal, um interruptor de sopro que emite teclas de verdade)
46
+ * ser lido como teclado, apagando exactamente a informação que quem carimbou se deu ao trabalho de pôr.
47
+ * 2. **Sem carimbo mas de confiança → `'teclado'`.** É o que `isTrusted` significa: o navegador viu a
48
+ * pessoa carregar. É a única inferência que este módulo faz, e fá-la sobre a propriedade que não se forja.
49
+ * 3. **Sem carimbo e sem confiança → `undefined`.** Um evento sintético que ninguém assinou. Depois desta
50
+ * migração nada nesta engine produz um; quem o produz é código de fora, e código de fora não declarou.
51
+ * ⚠️ Devolver `'teclado'` aqui seria a erasão a voltar por outra porta, que é o defeito que o ADR-0109
52
+ * inteiro existe para fechar — e seria pior do que a erasão original, porque teria a forma de uma
53
+ * resposta.
54
+ */
55
+ export function origemDoEvento(ev) {
56
+ const declarada = ev[CHAVE_DE_ORIGEM];
57
+ if (ehTransporte(declarada))
58
+ return declarada;
59
+ return ev.isTrusted ? 'teclado' : undefined;
60
+ }
@@ -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
+ }