@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
package/README.md CHANGED
@@ -70,9 +70,11 @@ npm test # testes Vitest (node + browser via Playwright); npm run test
70
70
  `allow_failure: true` e estavam vermelhos havia semanas. O histórico o guarda; o cabeçalho do `ci.yml`
71
71
  documenta o porte linha a linha.
72
72
 
73
- - **CD** — Cloudflare Pages (plano gratuito). ⚠️ **A conexão git dele apontava para o GitLab, que agora está
74
- arquivado** — o deploy tem de ser reapontado para este repositório antes do próximo push valer publicação.
75
- Não dá para conferir isto de fora do painel da Cloudflare. A cada push na `main`:
73
+ - **CD** — ⚠️ **NÃO HÁ NENHUM**, hoje. Informado pelo Dev em 2026-09-07: **nenhum projeto do Cloudflare Pages
74
+ está conectado a repositório nenhum**. A ressalva anterior — «a conexão apontava para o GitLab, que agora
75
+ está arquivado» — descrevia um deploy desapontado; o estado atual é mais simples e mais grave de confundir:
76
+ **push não é publicação**, e `dist/` só chega a alguém por um passo manual.
77
+ A tabela abaixo fica como a RECEITA de quando houver conexão, e não como descrição do que existe:
76
78
 
77
79
  | Configuração | Valor |
78
80
  |---|---|
package/app/css/style.css CHANGED
@@ -51,6 +51,7 @@
51
51
  --z-menu:30000;
52
52
  --z-menu-max:39999;
53
53
  --z-transition:40000;
54
+ --z-skip-link:41000;
54
55
  --z-loop-crash:45000;
55
56
  --z-debug:90000;
56
57
  }
@@ -178,7 +179,9 @@ html,body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font)
178
179
  (tolerância de 5px lógicos/lado) é cortado pelo .stage-wrap, e os menus rolam DENTRO dos cards. */
179
180
  body{display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden}
180
181
  .sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
181
- .skip-link{position:absolute;left:8px;top:-60px;background:var(--accent);color:var(--accent-ink);padding:.6em 1em;border-radius:0 0 10px 10px;font-weight:700;z-index:100;transition:top .15s}
182
+ /* O `z-index` ADOTOU O SLOT em 2026-09-07. Era `100`, um literal que estava CERTO e cuja tabela estava
183
+ errada: o `Z` punha o skip-link em CAPTIONS(26000), abaixo dos modais. Ver `SKIP_LINK` em core/layers. */
184
+ .skip-link{position:absolute;left:8px;top:-60px;background:var(--accent);color:var(--accent-ink);padding:.6em 1em;border-radius:0 0 10px 10px;font-weight:700;z-index:var(--z-skip-link);transition:top .15s}
182
185
  .skip-link:focus{top:0}
183
186
  :focus-visible{outline:4px solid var(--focus);outline-offset:2px;border-radius:6px}
184
187
 
@@ -300,10 +303,24 @@ body:not(.dbg) .topbar{display:none} /* título da PÁGINA só em ?debug=true (o
300
303
  navegador, e sem esta linha as duas listas do cartão de pausa apareceriam empilhadas (ADR-0044, item 5). */
301
304
  .pause-menu[hidden]{display:none}
302
305
  /* 44 px NÃO é o mínimo da WCAG — o 2.5.8 pede 24×24, e o cartão já passava nisso com folga. 44 é a
303
- recomendação da Apple (HIG), e este projeto trata piso como piso. `min-height` e não `padding` porque a
304
- altura de antes (35,6 px) não estava declarada em lugar nenhum: ela RESULTAVA da soma de padding, fonte e
305
- entrelinha. Um alvo que ninguém escreveu é um alvo que ninguém defende. */
306
- .pm-btn{background:#1a2740;color:#eaf2f8;border:1px solid #3a4a6a;border-radius:10px;padding:.5rem .8rem;min-height:44px;display:flex;align-items:center;justify-content:center;font:inherit;font-weight:700;cursor:pointer;white-space:nowrap}
306
+ recomendação da Apple (HIG). `min-height` e não `padding` porque a altura de antes (35,6 px) não estava
307
+ declarada em lugar nenhum: ela RESULTAVA da soma de padding, fonte e entrelinha. Um alvo que ninguém
308
+ escreveu é um alvo que ninguém defende.
309
+
310
+ ⚠️ ESTA LINHA DIZIA TAMBÉM «e este projeto trata piso como piso», e essa metade morreu no ADR-0095: o
311
+ piso deixou de ser um número único e passou a ser a régua abaixo. Fica o resto, que continua a valer. */
312
+ /* ⚠️ O ALVO DO ITEM DE MENU SEGUE A RÉGUA DO ADR-0095, e não um literal.
313
+ Era `min-height:44px` cravado. O registro fixou que o alvo é função da ALTURA DA TELA — 24 px (WCAG
314
+ 2.5.8, AA) abaixo de 540, 34 a partir de 540, 44 (2.5.5, AAA) a partir de 720 — porque um alvo grande
315
+ que exige ROLAGEM para ser alcançado pode custar mais dedo do que um menor que está à vista.
316
+
317
+ ⚠️ E ELE USA `--alvo-min` E NÃO `--tap`, que são coisas diferentes: `--tap` é o tamanho PREFERIDO e
318
+ cresce com a escala do canvas (22 × k, nunca menos de 44); `--alvo-min` é o CHÃO por altura. Um botão
319
+ isolado pode ser o preferido; um item de LISTA, que tem de caber inteiro, usa o chão.
320
+
321
+ O `44px` do fallback é o comportamento de antes, para o caso de `layout()` não ter corrido — que era
322
+ exatamente o que acontecia no `quiz.html` até 07/09. */
323
+ .pm-btn{background:#1a2740;color:#eaf2f8;border:1px solid #3a4a6a;border-radius:10px;padding:.5rem .8rem;min-height:var(--alvo-min,44px);display:flex;align-items:center;justify-content:center;font:inherit;font-weight:700;cursor:pointer;white-space:nowrap}
307
324
  .pm-btn:hover,.pm-btn:focus,.pm-btn.pm-sel{background:#2a3a5e;border-color:#ffd23f;outline:2px solid #ffd23f;outline-offset:1px}
308
325
  /* Legenda sim/não: ícone do botão (conforme layout do controle) + a palavra. Sony/Nintendo invertem 0↔1. */
309
326
  .pause-legend{display:flex;gap:1.1rem;justify-content:center;margin:.7rem 0 .1rem;font-size:.82em;color:#c3cfe0}
@@ -80,9 +80,71 @@
80
80
  @font-face{font-family:'Newsreader';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/newsreader-400.woff2') format('woff2');}
81
81
  @font-face{font-family:'Newsreader';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/newsreader-700.woff2') format('woff2');}
82
82
  /* Manuscritas (caligráficas inglesas · blackletter alemãs · alfabetização) */
83
- @font-face{font-family:'Great Vibes';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/greatvibes-400.woff2') format('woff2');}
84
83
  @font-face{font-family:'Pinyon Script';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/pinyon-400.woff2') format('woff2');}
85
- @font-face{font-family:'UnifrakturCook';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/ufcook-700.woff2') format('woff2');}
86
84
  @font-face{font-family:'UnifrakturMaguntia';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/ufmag-400.woff2') format('woff2');}
87
85
  @font-face{font-family:'Comic Neue';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/comicneue-400.woff2') format('woff2');}
88
86
  @font-face{font-family:'Comic Neue';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/comicneue-700.woff2') format('woff2');}
87
+
88
+ /* ===== ARCADE (papel `jogo`: HUD, título, rótulos — NUNCA a face da interface) =====
89
+ ⚠️ SEM `unicode-range`, E É DELIBERADO. O Google Fonts serve esta família em CINCO ficheiros ordenados
90
+ por `unicode-range` **com o latino por ÚLTIMO**, e uma vendorização que pegue no primeiro `src:` traz o
91
+ subconjunto cirílico — um woff2 válido, corretamente declarado, corretamente carregado, e sem uma única
92
+ letra latina.
93
+
94
+ Isso aconteceu de verdade, no `SP-the-inclusionist-whackwhack`: o título saiu na fonte errada durante
95
+ QUATRO commits e todas as verificações disponíveis diziam que estava certo —
96
+ `document.fonts.check()` devolvia `true`, o `status` era `loaded`, o `getComputedStyle` mostrava a
97
+ família certa. As três comparam o NOME da família, uma string escrita duas vezes pela mesma mão; nenhuma
98
+ olha para dentro do ficheiro. E o CSS resolve por CODEPOINT, então uma face sem glifo para `U+0057` é
99
+ ignorada para aquele caractere em silêncio.
100
+
101
+ O ficheiro aqui é o subconjunto LATINO, copiado daquele repositório já verificado, e o gate que o mede
102
+ é `tests/fonte-arcade.browser.test.js` — pela LARGURA DE AVANÇO, que vem do glifo e portanto do ficheiro. */
103
+ @font-face{font-family:'Press Start 2P';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/pressstart-400.woff2') format('woff2');}
104
+
105
+
106
+ /* ===== OpenDyslexic (ACESSIBILIDADE — oferecida, e SEM alegação de eficácia) =====
107
+ SIL OFL 1.1, confirmada na API do repositório oficial (antijingoist/opendyslexic) e não de memória — a
108
+ licença desta face JÁ MUDOU uma vez, e afirmar de cabeça seria afirmar o estado antigo.
109
+
110
+ ⚠️ ELA NÃO PROMETE LEITURA MELHOR, e isso é decisão registada. O `docs/game-design/typography.md` diz por
111
+ extenso: «Ofereça a Dyslexie e a OpenDyslexic apenas como escolha do usuário. A pesquisa não mostra ganho
112
+ de leitura com elas.» A descrição no menu fala do desenho — hastes pesadas na base —, nunca do efeito.
113
+
114
+ ⚠️ E ELA NÃO É SUBCONJUNTO: 100,9 KB do ficheiro compilado do próprio projeto, contra 23 KB da Fondamento
115
+ latina. É a face mais pesada do roster, e entra assim porque o upstream não publica subconjuntos; quem
116
+ quiser cortá-la depois faz por `pyftsubset`, e o gate de precache é quem reclama se o orçamento apertar. */
117
+ @font-face{font-family:'OpenDyslexic';font-style:normal;font-weight:400;font-display:swap;
118
+ src:url('fonts/opendyslexic-400.woff2') format('woff2');}
119
+
120
+ /* ===== Fondamento (CALIGRÁFICA — fora do menu, para dentro das atividades) =====
121
+ SIL OFL 1.1, do Google Fonts, nos dois subconjuntos que ele serve. Mínimo de 20 px fixado pelo Dev na
122
+ emenda do ADR-0012: abaixo disso a face deixa de ser DIFÍCIL e passa a ser ilegível, e as duas coisas são
123
+ diferentes — a dificuldade é o exercício, a ilegibilidade é a criança a desistir. */
124
+ @font-face{font-family:'Fondamento';font-style:normal;font-weight:400;font-display:swap;
125
+ src:url('fonts/fondamento-400.woff2') format('woff2');
126
+ unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
127
+ @font-face{font-family:'Fondamento';font-style:normal;font-weight:400;font-display:swap;
128
+ src:url('fonts/fondamento-400-ext.woff2') format('woff2');
129
+ unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
130
+
131
+ /* ===== Playwrite — as OITO das Américas (ADR-0108 §2, issue #87 item 3) =====
132
+ * A mão que cada país ensina a escrever. Os EUA entram com AS DUAS que ensinam — escolher uma seria
133
+ * escolher pela criança. São variáveis no peso (100–400) e cada família é UM ficheiro: a Google não as
134
+ * corta por unicode-range, então não há metade `-ext` para carregar depois. SIL OFL 1.1, como as outras. */
135
+ @font-face{font-family:'Playwrite BR';font-style:normal;font-weight:100 400;font-display:swap;
136
+ src:url('fonts/playwrite-br.woff2') format('woff2');}
137
+ @font-face{font-family:'Playwrite US Trad';font-style:normal;font-weight:100 400;font-display:swap;
138
+ src:url('fonts/playwrite-us-trad.woff2') format('woff2');}
139
+ @font-face{font-family:'Playwrite US Modern';font-style:normal;font-weight:100 400;font-display:swap;
140
+ src:url('fonts/playwrite-us-modern.woff2') format('woff2');}
141
+ @font-face{font-family:'Playwrite CA';font-style:normal;font-weight:100 400;font-display:swap;
142
+ src:url('fonts/playwrite-ca.woff2') format('woff2');}
143
+ @font-face{font-family:'Playwrite MX';font-style:normal;font-weight:100 400;font-display:swap;
144
+ src:url('fonts/playwrite-mx.woff2') format('woff2');}
145
+ @font-face{font-family:'Playwrite AR';font-style:normal;font-weight:100 400;font-display:swap;
146
+ src:url('fonts/playwrite-ar.woff2') format('woff2');}
147
+ @font-face{font-family:'Playwrite CL';font-style:normal;font-weight:100 400;font-display:swap;
148
+ src:url('fonts/playwrite-cl.woff2') format('woff2');}
149
+ @font-face{font-family:'Playwrite CO';font-style:normal;font-weight:100 400;font-display:swap;
150
+ src:url('fonts/playwrite-co.woff2') format('woff2');}
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  import { type Alcance, type Disponibilidade } from '../input/transports.js';
3
3
  import { type ActionPreset } from '../core/actions.js';
4
+ import type { KeyScheme } from '../core/entity.js';
4
5
  import { type GameDeclaration } from '../core/contract.js';
5
6
  import { type SceneStack } from '../core/scenes.js';
6
7
  import { createTts, type CarregarVozNeural } from '../platform/tts.js';
@@ -10,12 +11,49 @@ import { type SettingsPanelApi } from '../ui/settings-panel.js';
10
11
  import { type MenuNavApi } from '../ui/menu-nav.js';
11
12
  import type { NavKeys } from '../input/edges.js';
12
13
  import { type KeyboardRuntime } from '../input/keyboard-runtime.js';
14
+ import { type RelatorioPesado } from '../platform/pesados.js';
13
15
  /** O que o jogo empresta do documento. Tudo opcional menos `doc`/`win`: o que faltar vira `problems`. */
14
16
  export interface EngineHost {
15
17
  readonly doc: Document;
16
18
  readonly win: Window;
17
19
  /** Um `<svg>` vazio onde os seis filtros de daltonismo são montados em tempo de execução. */
18
20
  readonly cvdHost?: SVGElement | Element | null;
21
+ /**
22
+ * ONDE A BARRA DE ACESSIBILIDADE ENTRA, na PRIMEIRA tela do jogo.
23
+ *
24
+ * ⚠️ PEDIDO DO DEV, 2026-09-07: «o menu de pausa, o design do menu de pausa e os ícones de acessibilidade
25
+ * que aparecem no jogo desde a primeira tela devem ser oferecidos pela ENGINE e não pela programação do
26
+ * jogo. Todo jogo da engine inclusionist deve ter o mesmo menu de pausa e ícones de acessibilidade desde a
27
+ * primeira.»
28
+ *
29
+ * ⚠️ E A MEDIÇÃO DE 2026-09-08 MOSTROU QUE É UM ACHADO, e não uma arrumação: dos seis jogos do catálogo
30
+ * local, CINCO não têm barra de acessibilidade nenhuma — nem menu de pausa. `pixi-15-puzzle`, `game-chess`,
31
+ * `game-soccer`, `2048` e `whackwhack` não chamam `initPauseIcons` nem montam HUD, e o `createGame` nunca
32
+ * os montou por eles. O comentário do `ui/pause-icons` diz porquê sem o notar: «`initPauseIcons` é chamado
33
+ * pela raiz de composição de CADA jogo» — ou seja, cada jogo tinha de se lembrar, e cinco não se lembraram.
34
+ * A criança que depende do modo cego, do TTS ou do alto contraste abre esses cinco jogos e não tem por onde.
35
+ *
36
+ * Ausente, a engine procura `#title-icons` — o id que o jogo de plataforma usa desde sempre — e, não o
37
+ * achando, diz-o em `problems`.
38
+ *
39
+ * ✅ E DESDE 2026-09-08 ELA TAMBÉM **MONTA** (etapa 2 do ADR-0106 §4). Este parágrafo dizia «dizer não é
40
+ * montar, e a montagem é o passo seguinte»; o passo seguinte aconteceu. O que a destravou foram as etapas
41
+ * 1 e 3: nenhuma delas era sobre montar, e sem as duas o `PauseIconsCtx` exigia sete coisas que esta raiz
42
+ * não sabe responder por um jogo que não conhece.
43
+ *
44
+ * ⚠️ Achando o hospedeiro, a barra é escrita e fiada aqui. Não achando, continua a ser `problems` — porque
45
+ * a engine pode oferecer os ícones, mas não pode adivinhar ONDE eles cabem no desenho de um jogo alheio.
46
+ */
47
+ readonly a11yBarHost?: Element | null;
48
+ /**
49
+ * ONDE O CARTÃO DE PAUSA da primeira tela é pendurado. Ausente, a engine usa `#game-region`.
50
+ *
51
+ * ⚠️ Existe pela mesma razão do `a11yBarHost`: a engine pode OFERECER a pausa, mas não sabe onde ela cabe
52
+ * no desenho de um jogo alheio. 📌 E desde o ADR-0120 ONDE já não é SE: a pausa deixou de ser declinável, e
53
+ * o que sobra deste campo é o lugar. Um hospedeiro que não aceite filhos vira linha de `problems`, que é a
54
+ * diferença entre a engine não saber e a engine calar-se.
55
+ */
56
+ readonly pauseHost?: Element | null;
19
57
  }
20
58
  /**
21
59
  * O que este jogo NÃO tem. Declarado, e não deduzido de um getter que devolve null.
@@ -24,12 +62,24 @@ export interface EngineHost {
24
62
  * "pausado" para navegar os próprios menus, porque a engine não tinha por onde ouvir "eu não tenho fases".
25
63
  */
26
64
  export interface Declinios {
27
- /** Sem menu de pausa por tela (um quiz não tem). */
28
- readonly semMenuDePausa?: boolean;
29
65
  /** Sem assistente de mapeamento de controle. */
30
66
  readonly semAssistenteDePad?: boolean;
31
67
  /** Sem "ator da pausa" — quem apertou o botão que abriu o menu. */
32
68
  readonly semAtorDePausa?: boolean;
69
+ /**
70
+ * Sem voz neural — este jogo não abre a porta do ADR-0094.
71
+ *
72
+ * ⚠️ EXISTE PORQUE A AUSÊNCIA ESTAVA A SER SILENCIOSA, e a medição de 2026-09-08 diz quanto: dos SEIS jogos
73
+ * do catálogo local, TRÊS declaram `carregarVozNeural` (platformer, 15-puzzle, 2048) e TRÊS não
74
+ * (`game-soccer`, `whackwhack`, `game-chess`). Nos três últimos não há voz neural nenhuma, e nada o dizia.
75
+ *
76
+ * ⚠️ E ISSO CONTRADIZ UMA PROMESSA ESCRITA. O ADR-0065 §3 diz que as vozes «fazem parte da engine, e não do
77
+ * jogo em si» e que um cartucho «não tem de saber que existe»; o ADR-0094 — com razão, e por 135 MB de WASM
78
+ * — passou a exigir UMA LINHA do jogo. As duas coisas podem ser verdade ao mesmo tempo (a engine é dona das
79
+ * VOZES, o jogo nomeia o FORNECEDOR), mas só se quem esquece a linha for avisado. Declinar é escolha; não
80
+ * declarar era omissão.
81
+ */
82
+ readonly semVozNeural?: boolean;
33
83
  }
34
84
  export interface CreateGameOptions {
35
85
  /** Os SETE CAMPOS (core/contract). É o que a pilha de acessibilidade lê, e a única coisa que ela lê. */
@@ -51,9 +101,12 @@ export interface CreateGameOptions {
51
101
  readonly comIndice?: () => boolean;
52
102
  readonly naBarraDe?: (i: number) => boolean;
53
103
  readonly navBar?: (i: number, k: NavKeys) => void;
54
- /** Jogadores para o teclado remapeável. `Pick<ControlledPlayer,'ctrl'>` — esquema de teclas e nada mais. */
104
+ /** Jogadores para o teclado remapeável. `Pick<ControlledPlayer,'ctrl'>` — esquema de teclas e nada mais.
105
+ * ⚠️ `KeyScheme` e não `Record<string, string[]>` desde a #118: era uma CÓPIA ESTRUTURAL do tipo, e uma
106
+ * cópia que ninguém obriga a concordar diverge — é a lição que o próprio `core/entity` abre a dizer, com
107
+ * o `DomQuery` (dezasseis cópias) e o `KeyScheme` (seis) como as contas já pagas. */
55
108
  readonly players?: {
56
- ctrl: Record<string, string[]>;
109
+ ctrl: KeyScheme;
57
110
  }[];
58
111
  /** Troca de fase, para quem tem fases. Ausente = não faz nada (o jogo sem fases não perde nada). */
59
112
  readonly setPhase?: (p: 'title' | 'playing' | 'paused') => void;
@@ -91,6 +144,35 @@ export interface CreateGameOptions {
91
144
  * painel de áudio deixa de OFERECER o motor neural, em vez de o oferecer e nunca o carregar.
92
145
  */
93
146
  readonly carregarVozNeural?: CarregarVozNeural;
147
+ /**
148
+ * BAIXAR AS COISAS PESADAS NO PRIMEIRO CARREGAMENTO? Padrão **sim** (ADR-0110 (b), ADR-0116, ADR-0119).
149
+ *
150
+ * As quatro vozes neurais são ~241 MB e descem em SEGUNDO PLANO, uma de cada vez, sem bloquear o jogo: a
151
+ * criança joga enquanto elas chegam, e o que não pode acontecer é ela voltar no segundo dia, sem rede, e
152
+ * descobrir que a voz nunca foi buscada. O pilar 8 é «primeiro dia ONLINE, depois offline-first», e o
153
+ * ADR-0116 tirou a contradição que travava isto — instalar já é um acto de rede.
154
+ *
155
+ * ⚠️ PÔR `false` É PARA QUEM TEM RAZÃO PARA O FAZER, e a razão que já existe é um TESTE: um caso que monte
156
+ * o arranque num navegador de verdade não pode disparar 241 MB contra o Hugging Face. Um jogo em produção
157
+ * que o desligue está a decidir que a criança dele fica sem voz neural offline.
158
+ *
159
+ * 📌 E o ADR-0117 diz que quem devia pagar isto uma vez é a PLATAFORMA, não cada cartucho — a Cache Storage
160
+ * é particionada por origem, e num site só os 241 MB descem uma vez para todos os jogos. Enquanto a
161
+ * plataforma não os pede, é o jogo que os pede: melhor descer duas vezes do que nunca.
162
+ */
163
+ readonly baixarPesados?: boolean;
164
+ /**
165
+ * O QUE ACONTECEU COM CADA COISA PESADA, à medida que acontece. Ausente = ninguém está a ver.
166
+ *
167
+ * ⚠️ É AQUI E NÃO EM `problems` porque a descarga é de FUNDO: `problems` é devolvido sincronamente pelo
168
+ * `createGame`, e uma linha que chegue depois disso entra num vector que o leitor já leu. O ADR-0110 pede
169
+ * que uma busca falhada seja REPORTADA — reportar é ter um canal que existe quando a notícia chega, e não
170
+ * empurrar para uma lista que já foi entregue.
171
+ *
172
+ * 📌 A engine não inventa superfície nenhuma com isto: quem sabe onde cabe «faltam 241 MB» na tela de um
173
+ * jogo é o jogo. `pesoPorBaixar(relatorio)` dá o número para a frase.
174
+ */
175
+ readonly aoProgredirPesados?: (r: RelatorioPesado) => void;
94
176
  /**
95
177
  * Como se descobre que cada transporte está aqui. Ausente = a engine pergunta ao aparelho.
96
178
  *
@@ -101,6 +183,27 @@ export interface CreateGameOptions {
101
183
  }
102
184
  export interface Engine {
103
185
  readonly declaration: GameDeclaration;
186
+ /**
187
+ * A PAUSA QUE ESTA RAIZ MONTOU — mostrar e esconder, sem o consumidor caçar id nenhum.
188
+ *
189
+ * ⚠️ EXISTE PORQUE MONTAR NÃO É MOSTRAR, e a etapa 2 do ADR-0106 tinha ficado a meio sem que nada o
190
+ * dissesse. O cartão nasce `hidden` (é assim que o `buildScreenPause` o entrega, e tem de ser: a pausa
191
+ * abre-se, não está aberta) e QUEM O REVELA é o `ui/shell`, por fase — que esta raiz **não monta**, de
192
+ * propósito: «não substitui o boot do main.js, que tem catorze anos de ordem própria».
193
+ *
194
+ * ⚠️ Sem estas duas, um jogo montado por `createGame` ficava com um cartão de pausa que NADA mostrava. Não
195
+ * é «o consumidor esqueceu-se»: não havia por onde, a não ser procurar `#vp-pause-0` no documento — que é
196
+ * exactamente o tipo de conhecimento que este ficheiro existe para não exigir.
197
+ *
198
+ * 📌 A engine OFERECE o mecanismo e não toma a fase. Quando abrir a pausa continua a ser do jogo, porque só
199
+ * ele sabe o que é estar a jogar; o que deixa de ser dele é saber COMO.
200
+ */
201
+ readonly pausa: {
202
+ /** Revela o cartão da tela `i` e refaz os itens — o §5 avaliado no instante em que ela abre. */
203
+ readonly mostrar: (i: number) => void;
204
+ /** Esconde-o outra vez. */
205
+ readonly esconder: (i: number) => void;
206
+ };
104
207
  readonly tts: ReturnType<typeof createTts>;
105
208
  readonly overlays: SettingsPanelApi;
106
209
  readonly nav: MenuNavApi;