@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,130 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // input/transporte-em-uso — A ALTERNÂNCIA SEGUE O APARELHO EM USO (ADR-0109), na metade pura.
3
+ //
4
+ // ========================= O QUE ESTE MÓDULO É =========================
5
+ // O autómato das quatro regras do ADR-0109 §1, sem DOM, sem armazenamento e sem eventos. Recebe ARESTAS com
6
+ // origem e devolve estado; quem pergunta «há alternância agora?» pergunta ao estado.
7
+ //
8
+ // 1. Por padrão, controle e teclado, ambos SEM alternância.
9
+ // 2. Clique de mouse ou toque na tela → controles de tela COM alternância.
10
+ // 3. Apertar tecla devolve o teclado SEM alternância; usar o controle faz o mesmo.
11
+ // 4. Câmera e microfone precisam ser habilitados; habilitados, ligam a alternância em TODOS os outros
12
+ // controles, SEM possibilidade de desligar. São prioridade.
13
+ //
14
+ // ⚠️ POR QUE ISTO É UM AUTÓMATO E NÃO UM VALOR GUARDADO — e é o que o ADR-0109 supersede do ADR-0104 §C. A
15
+ // alternância era uma ESCOLHA guardada por transporte, e a issue #114 mediu que a fiação dela não era
16
+ // escrevível: a origem da aresta é apagada à porta (`input/state.keys` é `Set<string>` de CÓDIGOS, e o toque
17
+ // e a webcam escrevem lá dentro). O Dev decidiu o COMPORTAMENTO, e o comportamento escolhe o mecanismo.
18
+ //
19
+ // ⚠️ E A REGRA 3 É A RAZÃO DE PRECISAR DE DUAS COISAS, não de uma. «Apertar uma tecla devolve o teclado sem
20
+ // alternância» são DOIS factos: um EVENTO cuja origem tem de ser conhecida, e um MODO que persiste até à
21
+ // troca seguinte. Uma aresta não guarda estado; um modo guardado não detecta a própria troca. Nenhuma metade
22
+ // exprime a regra; juntas exprimem. Este ficheiro é a segunda metade — o MODO.
23
+ //
24
+ // 📌 A imagem do Dev, mantida porque diz a coisa: é um CAPS-LOCK NUM TECLADO COM MEMÓRIA. Modo e não estado
25
+ // momentâneo; lembrado por aparelho; trocar de aparelho não apaga o que o outro lembra.
26
+ /**
27
+ * A UNIÃO COMO VALOR, porque há um sítio onde ela tem de ser verificada em runtime.
28
+ *
29
+ * ⚠️ EXISTE POR CAUSA DE UMA FRONTEIRA, e é a única razão que a justifica: o `input/origem-sintetica` lê o
30
+ * transporte de um EXPANDO pendurado num `KeyboardEvent` — um objecto que este código não construiu e que
31
+ * qualquer script da página pode construir. Um valor que atravessa essa fronteira não é um `Transporte` por
32
+ * o TypeScript o dizer; é uma `string` até alguém a conferir. Sem lista, `'olho'` entrava no mapa de origens
33
+ * como transporte fantasma, e nada o diria.
34
+ *
35
+ * 📌 Não é o defeito da lista-ao-lado-da-união que este repositório já desfez três vezes (o `RM_KEYS`, os
36
+ * rótulos de movimento reduzido, as chaves de armazenamento), porque a guarda abaixo é do COMPILADOR: as duas
37
+ * não podem divergir. Uma cópia que não pode divergir é uma projecção, não uma segunda fonte.
38
+ */
39
+ export const TRANSPORTES = ['teclado', 'gamepad', 'toque', 'olhos', 'rosto', 'gestos', 'fala'];
40
+ const _COBRE_OS_TRANSPORTES = true;
41
+ void _COBRE_OS_TRANSPORTES;
42
+ /**
43
+ * Isto que veio de fora é mesmo um transporte?
44
+ *
45
+ * ⚠️ A pergunta não é de segurança — os scripts desta página são todos da casa, e quem quisesse mentir usaria
46
+ * um valor VÁLIDO. É de correcção: impede que um carimbo errado ou ausente vire uma entrada silenciosa no
47
+ * `origemDaTecla`, que é a estrutura de que a alternância inteira depende.
48
+ */
49
+ export function ehTransporte(v) {
50
+ return typeof v === 'string' && TRANSPORTES.includes(v);
51
+ }
52
+ /**
53
+ * OS QUATRO QUE EXIGEM HABILITAÇÃO EXPLÍCITA e, uma vez habilitados, mandam em todos (regra 4).
54
+ *
55
+ * ⚠️ É a mesma lista do `UM_COMANDO_DE_CADA_VEZ` do `input/latch-scope`, e a coincidência não é acaso: são
56
+ * os transportes de quem NÃO CONSEGUE SEGURAR NADA. O que o ADR-0109 acrescenta é que eles não ligam a
57
+ * alternância só para si — ligam-na para o resto, porque quem usa a webcam pode também tocar na tela, e uma
58
+ * alternância que se desliga ao mudar de aparelho é uma armadilha para exactamente essa pessoa.
59
+ */
60
+ export const EXIGEM_HABILITACAO = new Set(['olhos', 'rosto', 'gestos', 'fala']);
61
+ /** O transporte que liga a alternância por si só, sem prioridade nenhuma envolvida (regra 2). */
62
+ export const COM_ALTERNANCIA_PROPRIA = new Set(['toque']);
63
+ /**
64
+ * O ESTADO INICIAL: teclado, sem alternância.
65
+ *
66
+ * ⚠️ A regra 1 diz «controle E teclado, ambos sem alternância», e é por isso que o padrão pode nomear um só
67
+ * sem mentir: entre os dois a resposta à única pergunta que este módulo faz — há alternância? — é a MESMA.
68
+ * O `emUso` só passa a distingui-los quando alguém quiser MOSTRAR o aparelho corrente, que é outra questão
69
+ * e o ADR-0109 deixa-a explicitamente por decidir.
70
+ */
71
+ export const PADRAO = Object.freeze({ emUso: 'teclado', assistidaLigada: false });
72
+ /**
73
+ * HÁ ALTERNÂNCIA AGORA? — ⚠️ **NÃO PERGUNTE ISTO A ESTA FUNÇÃO.** Ver o parágrafo abaixo.
74
+ *
75
+ * ⚠️ A prioridade da assistida vem PRIMEIRO, e a ordem é a regra 4 inteira: enquanto ela estiver ligada,
76
+ * nenhum outro aparelho a desliga — nem o teclado, que noutro caso a desligaria. Inverter estas duas linhas
77
+ * é o defeito que trancaria uma criança fora do próprio jogo, e é silencioso.
78
+ *
79
+ * @deprecated 🔴 **ESTA FUNÇÃO IMPLEMENTA O MODELO QUE O ADR-0113 SUPERSEDEU**, e fica exportada por ser
80
+ * superfície publicada (`./input/*.js`) e por o registo ter valor histórico — não por ser a resposta.
81
+ *
82
+ * O ADR-0109 decidia a alternância **só pelo aparelho**: assistida ligada → sim; toque → sim; todo o resto
83
+ * → não. O ADR-0113 retirou essa cláusula, com a razão do Dev: a alternância é um **caps-lock guardado com
84
+ * o mapeamento do controle**, e o valor que a criança gravou vale.
85
+ *
86
+ * 🔴 A DIVERGÊNCIA TEM UMA CRIANÇA CONCRETA, e é a que motivou o registo: quem tem dificuldade motora, joga
87
+ * no TECLADO e gravou a alternância ligada. Esta função devolve `false` para ela — `teclado` não está em
88
+ * `COM_ALTERNANCIA_PROPRIA` — e é exactamente o controle que lhe seria retirado. O `latch-scope.alternanciaDe`
89
+ * devolve `true`, porque lê o que ela gravou.
90
+ *
91
+ * ⚠️ E A CLÁUSULA DO TOQUE TAMBÉM CAIU: sob o ADR-0113 o toque é um transporte como os outros — o valor dele
92
+ * é escolha e fica guardado. Só olhos, rosto, gestos e fala podem recusar-se a DESLIGAR, e essa metade vive
93
+ * em `latch-scope.alternanciaSempreLigada`, com um conjunto diferente deste e a responder a outra pergunta.
94
+ *
95
+ * **A resposta certa é `latch-scope.alternanciaDe(estado.emUso, leitura)`.** O papel que sobra a este
96
+ * módulo é o que o nome dele diz: QUAL transporte está em uso — que é o que alimenta aquele primeiro
97
+ * argumento. `tests/alternancia-por-transporte.node.test.js` afirma que esta função continua sem consumidor.
98
+ */
99
+ export function alternanciaAgora(estado) {
100
+ if (estado.assistidaLigada)
101
+ return true;
102
+ return COM_ALTERNANCIA_PROPRIA.has(estado.emUso);
103
+ }
104
+ /**
105
+ * UMA ARESTA CHEGOU, com a sua origem. Devolve o estado NOVO.
106
+ *
107
+ * ⚠️ Uma aresta de um transporte assistido NÃO o habilita. Habilitar é um acto explícito (regra 4: «precisam
108
+ * ser habilitados»), e deixar uma aresta fazê-lo significaria que um falso positivo da webcam — uma sombra,
109
+ * um segundo rosto a passar — trancava a alternância de toda a gente sem ninguém ter pedido.
110
+ */
111
+ export function aposAresta(estado, origem) {
112
+ if (estado.emUso === origem)
113
+ return estado; // sem mudança: devolve o MESMO objecto, não uma cópia
114
+ return { emUso: origem, assistidaLigada: estado.assistidaLigada };
115
+ }
116
+ /** A criança (ou quem a acompanha) habilitou câmera/microfone. Daqui em diante a alternância é lei. */
117
+ export function habilitarAssistida(estado) {
118
+ return estado.assistidaLigada ? estado : { emUso: estado.emUso, assistidaLigada: true };
119
+ }
120
+ /**
121
+ * DESABILITAR a assistida. Existe, e o ADR-0109 diz de quem é: NÃO é da criança durante a partida.
122
+ *
123
+ * ⚠️ Fica exportada porque desligar a câmera tem de ser possível em algum lugar — trocar de utilizador,
124
+ * fechar o jogo, um adulto a reconfigurar. O que o §4 proíbe é oferecê-la como um botão ao lado do jogo.
125
+ * Uma função que existe e não é oferecida é diferente de uma função que não existe: a primeira diz onde a
126
+ * decisão mora.
127
+ */
128
+ export function desabilitarAssistida(estado) {
129
+ return estado.assistidaLigada ? { emUso: estado.emUso, assistidaLigada: false } : estado;
130
+ }
@@ -22,11 +22,38 @@ export declare const LUGARES: Readonly<{
22
22
  toque: 9;
23
23
  teclado: 14;
24
24
  }>;
25
+ /**
26
+ * QUANTAS POSIÇÕES O CONTROLE DE TELA SEGURA AO MESMO TEMPO. Dois, e é uma DECLARAÇÃO (ADR-0104 §B).
27
+ *
28
+ * ⚠️ E A DECISÃO É NÃO PERGUNTAR AO APARELHO. O `navigator.maxTouchPoints` existe, responde depressa, e
29
+ * MENTE — mente para cima: muitos aparelhos anunciam cinco e reconhecem dois. Um modelo assente nele falha
30
+ * exactamente no telemóvel barato da escola pública, que é o pilar 1 deste projeto, e falha em silêncio: a
31
+ * criança tenta correr e pular ao mesmo tempo, não acontece nada, e ela conclui que o jogo está partido.
32
+ *
33
+ * Um piso declarado não pode errar para cima. Ele erra para baixo — um aparelho que segurava três fica
34
+ * servido por dois —, e esse erro tem conserto: a opção do terceiro botão, PROVADA POR TESTE, porque um
35
+ * gesto real é a única evidência que um aparelho não consegue falsificar. Nada é recusado e nada é assumido.
36
+ *
37
+ * ⚠️ NÃO É UM REGISTO DE TETOS POR TRANSPORTE, e o ADR diz isso em `more-information`: só o toque tem número
38
+ * declarado. O `holds` do `Transport` é opcional justamente por isso — ausente significa «não há tecto
39
+ * conhecido», não «segura uma». Um registo de tetos para teclado e controle é uma mudança maior, e nada
40
+ * precisa dela hoje.
41
+ */
42
+ export declare const SEGURA_TOQUE = 2;
25
43
  /** Como se descobre que cada transporte está aqui AGORA. Injetado: nenhuma destas perguntas é pura. */
26
44
  export interface Disponibilidade {
27
45
  gamepad: () => boolean;
28
46
  toque: () => boolean;
29
47
  teclado: () => boolean;
48
+ /**
49
+ * Há um RATO aqui? (ADR-0112)
50
+ *
51
+ * ⚠️ ELE NÃO É UM TRANSPORTE À PARTE, e é por isso que entra como uma pergunta e não como uma quarta linha
52
+ * do `transportesPadrao`: um rato sozinho não carrega as catorze posições. Ele é o SINAL CONTÍNUO ao lado
53
+ * do teclado — a frase do Dev, «no caso do teclado, o sinal contínuo passa a ser o mouse» —, e é o que dá
54
+ * fundação aos transportes 8 e 9 do ADR-0074, que aquele registo declarava como não tendo nenhuma.
55
+ */
56
+ rato: () => boolean;
30
57
  }
31
58
  /**
32
59
  * Os três transportes que esta engine sabe oferecer hoje.
@@ -44,14 +71,43 @@ export interface Transport {
44
71
  readonly id: string;
45
72
  /** Quantos lugares ATRIBUÍVEIS. É o número que a aritmética da garantia usa. */
46
73
  readonly slots: number;
74
+ /**
75
+ * Quantas posições ele SEGURA AO MESMO TEMPO, quando isso é conhecido. Eixo diferente dos `slots`: o
76
+ * controle de tela tem nove lugares e segura dois.
77
+ *
78
+ * ⚠️ AUSENTE SIGNIFICA «NÃO HÁ TECTO CONHECIDO», e não «segura uma». Hoje só o toque declara um número
79
+ * (`SEGURA_TOQUE`), porque só sobre ele há decisão — ver a nota lá. Ler a ausência como zero faria todo
80
+ * transporte sem número reprovar de repente, que é o oposto do que um campo opcional deve fazer.
81
+ */
82
+ readonly holds?: number;
47
83
  /**
48
84
  * Está disponível NESTE aparelho, AGORA? Função e não valor: um controle é ligado no meio da partida, e
49
85
  * uma tela de toque aparece quando a criança gira o tablet.
50
86
  */
51
87
  readonly available: () => boolean;
88
+ /**
89
+ * Este transporte oferece um PONTEIRO — posição contínua (ADR-0112)?
90
+ *
91
+ * ⚠️ FUNÇÃO E NÃO BOOLEANO, pela mesma razão escrita no `available` acima: um rato é ligado no meio da
92
+ * partida, tal como um controle. Um valor fixo aqui responderia com o estado do arranque.
93
+ *
94
+ * ⚠️ AUSENTE SIGNIFICA «NÃO OFERECE», e aqui — ao contrário do `holds` — ler a ausência assim é o correcto:
95
+ * o ponteiro é uma capacidade que se DECLARA, e um transporte que não a declara não a tem. O `holds` é o
96
+ * oposto porque lá a ausência é «não há tecto conhecido», e lê-la como zero reprovaria todo o mundo.
97
+ */
98
+ readonly aponta?: () => boolean;
52
99
  }
53
100
  /** Este transporte carrega este conjunto de ações? Aritmética, como o ADR-0079 §3 a descreve. */
54
101
  export declare function carries(t: Transport, acoes: readonly Action[]): boolean;
102
+ /**
103
+ * Este transporte SEGURA quantas o jogo pede ao mesmo tempo? (ADR-0104 §A.)
104
+ *
105
+ * ⚠️ TRANSPORTE SEM TECTO DECLARADO RESPONDE SIM, e a escolha é deliberada: o `holds` ausente quer dizer «não
106
+ * medimos isto», e recusar por falta de medida transformaria uma ignorância em acusação — o teclado e o
107
+ * controle passariam a reprovar todos os jogos por não terem número nenhum. Onde não há decisão, o modelo
108
+ * cala; é o toque que tem decisão, e é só ele que pode reprovar aqui.
109
+ */
110
+ export declare function holds(t: Transport, pedidas: number): boolean;
55
111
  /**
56
112
  * Os transportes DISPONÍVEIS que carregam este conjunto.
57
113
  *
@@ -69,10 +125,20 @@ export declare function carriedBy(lista: readonly Transport[], acoes: readonly A
69
125
  export declare function reachable(lista: readonly Transport[], acoes: readonly Action[]): boolean;
70
126
  /** O que a tela de seleção precisa dizer, e o que ela precisa saber para o dizer. */
71
127
  export interface Alcance {
72
- /** Verdadeiro = pelo menos um transporte disponível carrega o conjunto. */
128
+ /**
129
+ * Verdadeiro = pelo menos um transporte disponível carrega o conjunto **e segura quantas o jogo pede ao
130
+ * mesmo tempo**.
131
+ *
132
+ * ⚠️ A SEGUNDA METADE DESTA FRASE É NOVA (ADR-0104), e é o conserto do ponto cego que ninguém tinha
133
+ * nomeado: a plataforma declara nove ações, o toque tem nove lugares, o `ok` dizia sim — e correr, andar e
134
+ * pular ao mesmo tempo são três dedos que um telemóvel de dois não tem. O `ok` afirmava «dá para jogar»
135
+ * sobre um jogo que não dava, que é a pior coisa que este campo podia fazer.
136
+ */
73
137
  readonly ok: boolean;
74
138
  /** Quantas ações o jogo pede. */
75
139
  readonly pedidas: number;
140
+ /** Quantas ele pede SEGURAR ao mesmo tempo — o segundo eixo, e o que o `holdsAtOnce` declara. */
141
+ readonly seguraPedidas: number;
76
142
  /** Os que serviriam se estivessem ligados — a informação acionável: «ligue um controle». */
77
143
  readonly serviriamSeLigados: readonly string[];
78
144
  /** Os disponíveis que NÃO cabem, com quantos lugares têm. Para a frase dizer o número. */
@@ -80,6 +146,26 @@ export interface Alcance {
80
146
  readonly id: string;
81
147
  readonly slots: number;
82
148
  }[];
149
+ /**
150
+ * Os disponíveis que CHEGAM às ações mas não seguram quantas o jogo pede de uma vez, com o tecto deles.
151
+ *
152
+ * É a terceira frase do cartão da #112, e ela precisa dos dois números: «o controle de tela segura dois
153
+ * botões de cada vez e este jogo pede três». Sem o tecto, a frase diria que algo falta sem dizer o quê.
154
+ */
155
+ readonly naoSeguram: readonly {
156
+ readonly id: string;
157
+ readonly holds: number;
158
+ }[];
159
+ /** Este jogo declarou que precisa de PONTEIRO? (ADR-0112) */
160
+ readonly pedePonteiro: boolean;
161
+ /**
162
+ * Os disponíveis que chegam às acções E seguram quantas o jogo pede, e falham SÓ por não apontarem.
163
+ *
164
+ * ⚠️ SÓ QUEM FALHA APENAS NISTO, pela mesma regra que o `naoSeguram` já segue: um transporte em duas listas
165
+ * faria o cartão da #112 dizer dois problemas onde há um, e a criança leria dois motivos para a mesma
166
+ * recusa. A lista é vazia quando o jogo não pede ponteiro — não «todos», porque nenhum falhou.
167
+ */
168
+ readonly naoApontam: readonly string[];
83
169
  }
84
170
  /**
85
171
  * Mede o alcance ANTES de a criança começar.
@@ -87,4 +173,4 @@ export interface Alcance {
87
173
  * ⚠️ Devolve DADO e não texto. A frase é da interface e tem de passar por `t()`; devolver português daqui
88
174
  * repetiria o defeito que o `PADWIZ_STEPS` acabou de deixar de cometer.
89
175
  */
90
- export declare function alcance(lista: readonly Transport[], acoes: readonly Action[]): Alcance;
176
+ export declare function alcance(lista: readonly Transport[], acoes: readonly Action[], seguraPedidas: number, pedePonteiro?: boolean): Alcance;
@@ -40,6 +40,24 @@ import { ACTIONS } from '../core/actions.js';
40
40
  * número cresce com ele e nunca passa a mentir.
41
41
  */
42
42
  export const LUGARES = Object.freeze({ gamepad: 17, toque: 9, teclado: ACTIONS.length });
43
+ /**
44
+ * QUANTAS POSIÇÕES O CONTROLE DE TELA SEGURA AO MESMO TEMPO. Dois, e é uma DECLARAÇÃO (ADR-0104 §B).
45
+ *
46
+ * ⚠️ E A DECISÃO É NÃO PERGUNTAR AO APARELHO. O `navigator.maxTouchPoints` existe, responde depressa, e
47
+ * MENTE — mente para cima: muitos aparelhos anunciam cinco e reconhecem dois. Um modelo assente nele falha
48
+ * exactamente no telemóvel barato da escola pública, que é o pilar 1 deste projeto, e falha em silêncio: a
49
+ * criança tenta correr e pular ao mesmo tempo, não acontece nada, e ela conclui que o jogo está partido.
50
+ *
51
+ * Um piso declarado não pode errar para cima. Ele erra para baixo — um aparelho que segurava três fica
52
+ * servido por dois —, e esse erro tem conserto: a opção do terceiro botão, PROVADA POR TESTE, porque um
53
+ * gesto real é a única evidência que um aparelho não consegue falsificar. Nada é recusado e nada é assumido.
54
+ *
55
+ * ⚠️ NÃO É UM REGISTO DE TETOS POR TRANSPORTE, e o ADR diz isso em `more-information`: só o toque tem número
56
+ * declarado. O `holds` do `Transport` é opcional justamente por isso — ausente significa «não há tecto
57
+ * conhecido», não «segura uma». Um registo de tetos para teclado e controle é uma mudança maior, e nada
58
+ * precisa dela hoje.
59
+ */
60
+ export const SEGURA_TOQUE = 2;
43
61
  /**
44
62
  * Os três transportes que esta engine sabe oferecer hoje.
45
63
  *
@@ -51,15 +69,32 @@ export const LUGARES = Object.freeze({ gamepad: 17, toque: 9, teclado: ACTIONS.l
51
69
  */
52
70
  export function transportesPadrao(d) {
53
71
  return [
72
+ // ⚠️ O GAMEPAD NÃO DECLARA PONTEIRO, e a ausência é medida e não esquecimento: o stick tem o sinal
73
+ // contínuo e a engine deita-o fora na fonte (`PAD_DEAD = 0.5`, `PadState = Record<string, boolean>`).
74
+ // Ligá-lo ao ponteiro é possível e traz de volta uma pergunta que o ADR-0112 já deixou nomeada — metade
75
+ // do curso morta é ergonomia certa para um BOTÃO e errada para um CURSOR.
54
76
  { id: 'gamepad', slots: LUGARES.gamepad, available: d.gamepad },
55
- { id: 'teclado', slots: LUGARES.teclado, available: d.teclado },
56
- { id: 'toque', slots: LUGARES.toque, available: d.toque },
77
+ // O teclado aponta QUANDO HÁ RATO — a cláusula do Dev, e a fundação dos transportes 8 e 9 do ADR-0074.
78
+ { id: 'teclado', slots: LUGARES.teclado, available: d.teclado, aponta: d.rato },
79
+ // O toque aponta por natureza: a superfície É o ponteiro, e é o mesmo dedo que carrega nos botões.
80
+ { id: 'toque', slots: LUGARES.toque, holds: SEGURA_TOQUE, available: d.toque, aponta: d.toque },
57
81
  ];
58
82
  }
59
83
  /** Este transporte carrega este conjunto de ações? Aritmética, como o ADR-0079 §3 a descreve. */
60
84
  export function carries(t, acoes) {
61
85
  return t.slots >= acoes.length;
62
86
  }
87
+ /**
88
+ * Este transporte SEGURA quantas o jogo pede ao mesmo tempo? (ADR-0104 §A.)
89
+ *
90
+ * ⚠️ TRANSPORTE SEM TECTO DECLARADO RESPONDE SIM, e a escolha é deliberada: o `holds` ausente quer dizer «não
91
+ * medimos isto», e recusar por falta de medida transformaria uma ignorância em acusação — o teclado e o
92
+ * controle passariam a reprovar todos os jogos por não terem número nenhum. Onde não há decisão, o modelo
93
+ * cala; é o toque que tem decisão, e é só ele que pode reprovar aqui.
94
+ */
95
+ export function holds(t, pedidas) {
96
+ return t.holds === undefined || t.holds >= pedidas;
97
+ }
63
98
  /**
64
99
  * Os transportes DISPONÍVEIS que carregam este conjunto.
65
100
  *
@@ -85,12 +120,32 @@ export function reachable(lista, acoes) {
85
120
  * ⚠️ Devolve DADO e não texto. A frase é da interface e tem de passar por `t()`; devolver português daqui
86
121
  * repetiria o defeito que o `PADWIZ_STEPS` acabou de deixar de cometer.
87
122
  */
88
- export function alcance(lista, acoes) {
123
+ export function alcance(lista, acoes, seguraPedidas, pedePonteiro = false) {
89
124
  const disponiveis = lista.filter((t) => t.available());
125
+ /**
126
+ * ⚠️ PADRÃO `false` E NÃO PARÂMETRO OBRIGATÓRIO: os trezentos jogos que não desenham não podem sentir esta
127
+ * mudança, e um quarto argumento exigido faria cada chamador existente decidir hoje uma coisa que não lhe
128
+ * diz respeito.
129
+ */
130
+ const apontaSeFor = (t) => !pedePonteiro || (!!t.aponta && t.aponta());
131
+ // ⚠️ «Serve» passou a ser TRÊS coisas. Foi DUAS na #114 (o `ok` dizia sim a quem não segurava três dedos), e
132
+ // é três desde o ADR-0112 — pela mesma razão das duas vezes: um `ok` verdadeiro sobre um jogo que a criança
133
+ // não consegue jogar é a pior coisa que este campo pode fazer.
134
+ const serve = (t) => carries(t, acoes) && holds(t, seguraPedidas) && apontaSeFor(t);
90
135
  return {
91
- ok: reachable(lista, acoes),
136
+ ok: acoes.length > 0 && disponiveis.some(serve),
92
137
  pedidas: acoes.length,
93
- serviriamSeLigados: lista.filter((t) => !t.available() && carries(t, acoes)).map((t) => t.id),
138
+ seguraPedidas,
139
+ pedePonteiro,
140
+ serviriamSeLigados: lista.filter((t) => !t.available() && serve(t)).map((t) => t.id),
94
141
  curtos: disponiveis.filter((t) => !carries(t, acoes)).map((t) => ({ id: t.id, slots: t.slots })),
142
+ naoApontam: disponiveis
143
+ .filter((t) => carries(t, acoes) && holds(t, seguraPedidas) && !apontaSeFor(t))
144
+ .map((t) => t.id),
145
+ // ⚠️ SÓ QUEM CHEGA, e não quem já reprovou por lugares. Um transporte que aparecesse nas duas listas
146
+ // faria o cartão dizer duas coisas sobre o mesmo defeito, e a criança leria dois problemas onde há um.
147
+ naoSeguram: disponiveis
148
+ .filter((t) => carries(t, acoes) && !holds(t, seguraPedidas))
149
+ .map((t) => ({ id: t.id, holds: t.holds })),
95
150
  };
96
151
  }
@@ -1,5 +1,16 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- import type { KeyScheme } from '../core/entity.js';
2
+ /**
3
+ * ⚠️ O DADO SALVO NÃO É UM `KeyScheme`, e a issue #118 tornou isso um erro de compilação em vez de uma
4
+ * suposição. Um `KeyScheme` é FECHADO nas quatorze posições e completo; o que está no navegador da criança é
5
+ * uma SOBREPOSIÇÃO — parcial por construção (`loadKB` funde-a sobre os padrões com `Object.assign`) e capaz
6
+ * de carregar chaves que este código não conhece, o que o cabeçalho de `migrarEsquema` já dizia com todas as
7
+ * letras: «chave desconhecida atravessa intacta».
8
+ *
9
+ * Dar-lhe o tipo fechado obrigaria este ficheiro a inventar as posições que faltam no dado antigo — quer
10
+ * dizer, a escrever teclas que a criança nunca escolheu, no exacto módulo que existe para não lhe perder o
11
+ * remapeamento. O tipo aberto é o honesto aqui, e é só aqui.
12
+ */
13
+ export type EsquemaSalvo = Record<string, readonly string[]>;
3
14
  /**
4
15
  * Nome de plataforma → posição abstrata. **ADR-0086 §2**, e não o ADR-0074.
5
16
  *
@@ -38,11 +49,11 @@ export declare function migrarMapaDeToque(mapa: Record<string, string> | null |
38
49
  export declare function migrarMapaDeControle<T>(mapa: Record<string, T> | null | undefined): Record<string, T> | null;
39
50
  /** O objeto salvo, tal como `input/keyboard` o persiste. `p34` é o formato mais antigo de todos. */
40
51
  export interface SavedKB {
41
- solo?: KeyScheme;
42
- p2?: KeyScheme[];
43
- p3?: KeyScheme[];
44
- p4?: KeyScheme[];
45
- p34?: (KeyScheme | null)[];
52
+ solo?: EsquemaSalvo;
53
+ p2?: EsquemaSalvo[];
54
+ p3?: EsquemaSalvo[];
55
+ p4?: EsquemaSalvo[];
56
+ p34?: (EsquemaSalvo | null)[];
46
57
  }
47
58
  /**
48
59
  * Traduz UM esquema salvo do vocabulário antigo para o abstrato.
@@ -53,6 +64,6 @@ export interface SavedKB {
53
64
  * sumirem. É também o que torna esta função IDEMPOTENTE: aplicada sobre um esquema já migrado, nenhuma chave
54
65
  * casa e o resultado é igual à entrada, o que importa porque `loadKB` pode correr mais de uma vez na sessão.
55
66
  */
56
- export declare function migrarEsquema(esquema: KeyScheme | null | undefined): KeyScheme | null;
67
+ export declare function migrarEsquema(esquema: EsquemaSalvo | null | undefined): EsquemaSalvo | null;
57
68
  /** Traduz o objeto salvo inteiro — o esquema solo e as listas por contagem de jogadores. */
58
69
  export declare function migrarSalvo(s: SavedKB | null | undefined): SavedKB | null;
@@ -1,9 +1,37 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- interface SfxDef {
2
+ /**
3
+ * A definição de UM earcon. A tabela é do JOGO (item 19); esta é a forma que a engine sabe tocar.
4
+ *
5
+ * ⚠️ EXPORTADA DESDE 2026-09-07 (#124), e a falta era um defeito de interface: o `game-platformer` declarava
6
+ * a sua própria cópia deste tipo, palavra por palavra, porque não tinha como o nomear. Uma forma que cada
7
+ * consumidor redescobre por cópia é uma forma que diverge — foi assim que cinco cópias de `Gfx` divergiram
8
+ * nesta árvore, e está escrito noutro módulo.
9
+ */
10
+ export interface SfxDef {
11
+ /** O timbre. */
3
12
  t: OscillatorType;
13
+ /** A frequência inicial, em hertz. */
4
14
  f: number;
15
+ /** A duração, em segundos. */
5
16
  d: number;
17
+ /** A CHAVE de i18n da legenda (a11y surdez). Quem exibe resolve. */
6
18
  cap?: string;
19
+ /**
20
+ * A frequência FINAL, em hertz. Ausente = nota parada, que é o que sempre houve.
21
+ *
22
+ * ⚠️ ELE EXISTE PORQUE UM EARCON PRECISA DE PODER IR PARA ALGUM LADO (#124). Medido ao construir o
23
+ * `game-soccer`: marcar e sofrer golo têm de ser distinguíveis **só de ouvido** — uma criança cega ouve a
24
+ * sala reagir e precisa de saber para que lado antes de a narração chegar. O desenho óbvio é uma figura
25
+ * que SOBE para o golo dela e DESCE para o do outro, e a tabela não o sabia dizer. O que sobrava era
26
+ * agudo-e-longo contra grave-e-curto: distinguível, e menos informação do que o momento carrega.
27
+ *
28
+ * ⚠️ E A CAPACIDADE JÁ ESTAVA NESTE FICHEIRO, sem ser alcançável da tabela: o `doorSound` faz exatamente
29
+ * isto, com `frequency.exponentialRampToValueAtTime`. O conserto não é síntese nova — é abrir a porta.
30
+ *
31
+ * A rampa é EXPONENCIAL e não linear porque a altura é percebida em razão e não em diferença: uma rampa
32
+ * linear de 200 a 800 sobe depressa no início e devagar no fim, e ouve-se torta.
33
+ */
34
+ f2?: number;
7
35
  }
8
36
  export interface AudioEarconsCtx {
9
37
  SFX: Record<string, SfxDef | undefined>;
@@ -21,4 +49,3 @@ export interface AudioEarcons {
21
49
  doorSound: (mat: string) => void;
22
50
  }
23
51
  export declare function createAudioEarcons(ctx: AudioEarconsCtx): AudioEarcons;
24
- export {};
@@ -30,6 +30,16 @@ export function createAudioEarcons(ctx) {
30
30
  g.gain.value = 0.0001;
31
31
  o.connect(g).connect(ctx.catNode('earcons') || ctx.audioOut() || ac.destination);
32
32
  const t = ac.currentTime, vol = ctx.getVolume();
33
+ // A FIGURA, quando a tabela pede uma (#124). `setValueAtTime` antes da rampa como no `doorSound`: sem
34
+ // ele o ponto de partida da curva fica por conta da implementação, e o glissando começa onde calhar.
35
+ //
36
+ // ⚠️ AS DUAS GUARDAS SÃO NECESSÁRIAS E NÃO ZELO. `exponentialRampToValueAtTime` LANÇA com alvo zero ou
37
+ // negativo — uma tabela com `f2: 0` mataria o earcon inteiro pelo `catch`, em silêncio. E `f2 === f`
38
+ // não é rampa nenhuma: pedi-la ao navegador seria trabalho para produzir a nota parada que já havia.
39
+ if (typeof c.f2 === 'number' && c.f2 > 0 && c.f2 !== c.f) {
40
+ o.frequency.setValueAtTime(c.f, t);
41
+ o.frequency.exponentialRampToValueAtTime(c.f2, t + c.d);
42
+ }
33
43
  g.gain.exponentialRampToValueAtTime(Math.max(0.02, 0.25 * vol), t + 0.01);
34
44
  g.gain.exponentialRampToValueAtTime(0.0001, t + c.d);
35
45
  o.start(t);
@@ -14,7 +14,7 @@ export type { PlayerCtxOut };
14
14
  * globais ambientes em qualquer arquivo desta árvore. O motivo verdadeiro é de CAMADA: `_ac` e `_acOut` não
15
15
  * são da entidade da engine, e por isso o `core/entity` deixou de mencioná-los (ADR-0039, opção A1).
16
16
  */
17
- type Player = PlayerView<'x' | 'y' | 'facing' | 'viz' | 'i' | 'audioSink' | 'wnT' | 'guideT' | 'pad'> & PlayerAudioOut & Pick<ControlledPlayer, 'ctrl'>;
17
+ type Player = PlayerView<'x' | 'y' | 'facing' | 'viz' | 'i' | 'audioSink' | 'wnT' | 'pad'> & PlayerAudioOut & Pick<ControlledPlayer, 'ctrl'>;
18
18
  export interface AudioNavCtx {
19
19
  tileAt: (x: number, y: number) => number;
20
20
  solidAt: (x: number, y: number) => boolean;