@the-inclusionist/engine 6.36.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-loop-crash:45000;
54
55
  --z-debug:90000;
55
56
  }
56
57
  /* Emoji: usa o Noto Color Emoji do sistema (Chromebook/Android/Linux) e, se ausente, o woff2 embutido
@@ -379,9 +380,13 @@ main{flex:1;display:flex;flex-direction:column;width:100%;margin:0 auto;padding:
379
380
  ⚠️ E AS CORES SÃO CRUAS, DE PROPÓSITO: nada de `var(--…)`. Este aviso aparece justamente quando alguma
380
381
  coisa quebrou, e a máquina de temas é candidata plausível. Branco sobre vinho dá 9,5:1, acima do 7:1 do
381
382
  1.4.6, e não depende de nada ter sobrevivido. */
382
- .game-region[data-incl-parou]::after{
383
- content:attr(data-incl-parou);
384
- position:absolute;inset:auto 0 0 0;z-index:60;
383
+ /* ⚠️ ERA UM `::after` COM `content: attr(...)`, E NUNCA APARECERIA. Medido no arranque real: o
384
+ `.game-region.crt-scan-1::after` logo abaixo é a scanline do CRT, ligada por padrão — e um elemento tem UM
385
+ `::after` só. As duas regras não se empilham; a do CRT vem depois e vence. O `::before` está tomado pela
386
+ vinheta. Agora é um ELEMENTO, que não disputa lugar com ninguém — e cujo texto entra na árvore de
387
+ acessibilidade, coisa que `content` não garante. `z-index` acima do 500 da scanline, de propósito. */
388
+ #incl-parou{
389
+ position:absolute;inset:auto 0 0 0;z-index:var(--z-loop-crash);
385
390
  padding:14px 18px;
386
391
  background:#7f1d1d;color:#fff;border-top:3px solid #fff;
387
392
  font:700 15px/1.35 system-ui,-apple-system,"Segoe UI",sans-serif;
@@ -3,7 +3,7 @@ import { type Alcance, type Disponibilidade } from '../input/transports.js';
3
3
  import { type ActionPreset } from '../core/actions.js';
4
4
  import { type GameDeclaration } from '../core/contract.js';
5
5
  import { type SceneStack } from '../core/scenes.js';
6
- import { createTts } from '../platform/tts.js';
6
+ import { createTts, type CarregarVozNeural } from '../platform/tts.js';
7
7
  import { type AudioSonar, type SonarPlayer } from '../platform/audio-sonar.js';
8
8
  import type { AlcanceDoFiltro } from '../render/port.js';
9
9
  import { type SettingsPanelApi } from '../ui/settings-panel.js';
@@ -76,6 +76,21 @@ export interface CreateGameOptions {
76
76
  * aparece, que é o comportamento de hoje e não uma regressão.
77
77
  */
78
78
  readonly preset?: ActionPreset;
79
+ /**
80
+ * COMO SE CARREGA A VOZ NEURAL — uma linha do lado do jogo (ADR-0094):
81
+ *
82
+ * carregarVozNeural: () => import('@mintplex-labs/piper-tts-web')
83
+ *
84
+ * ⚠️ AUSENTE POR OMISSÃO, E ISSO É A DECISÃO E NÃO UM DESCUIDO. A engine não pode nomear o fornecedor:
85
+ * ele traz `onnxruntime-web` como peer NÃO-opcional, que o npm instala sozinho — **135,4 MB** no
86
+ * `node_modules` de todo consumidor, incluindo um jogo que nunca fale por voz neural. E declará-lo em
87
+ * `devDependencies`, que era o estado até 06/09, publicou uma engine que NÃO COMPILAVA para ninguém
88
+ * (ADR-0093). A porta é a única forma que resolve as duas coisas ao mesmo tempo.
89
+ *
90
+ * Sem ela a narração cai na voz do navegador (Web Speech), que fala o idioma certo e não pesa nada — e o
91
+ * painel de áudio deixa de OFERECER o motor neural, em vez de o oferecer e nunca o carregar.
92
+ */
93
+ readonly carregarVozNeural?: CarregarVozNeural;
79
94
  /**
80
95
  * Como se descobre que cada transporte está aqui. Ausente = a engine pergunta ao aparelho.
81
96
  *
@@ -106,6 +106,7 @@ export function createGame(o) {
106
106
  const tts = createTts({
107
107
  srSay, srAlert, ensureAC, catNode, audioOut,
108
108
  getSoundOn: () => soundOn, getVolume: () => volume, getAudioCat: () => audioCat,
109
+ carregarVozNeural: o.carregarVozNeural,
109
110
  });
110
111
  /**
111
112
  * O FILTRO DE VISÃO, aplicado ao MUNDO QUE O JOGO DECLAROU (ADR-0087).
@@ -260,6 +261,7 @@ export function createGame(o) {
260
261
  // que ele perde é dizer que parou.
261
262
  const aoFalhar = criarAvisoDeQueda({
262
263
  procurar: (sel) => $(sel),
264
+ criar: (tag) => doc.createElement(tag),
263
265
  narrar: (texto) => tts.narrate(texto),
264
266
  });
265
267
  return { declaration: o.declaration, tts, overlays, nav, keyboard, sonar, aplicarFiltroDeVisao, cenas: criarPilha(), cvdFilters, problems, declines, aoFalhar, alcance: alcanceAqui };
@@ -32,6 +32,12 @@ export declare const Z: {
32
32
  readonly MENU: 30000;
33
33
  readonly MENU_MAX: 39999;
34
34
  readonly TRANSITION: 40000;
35
+ /**
36
+ * O LAÇO MORREU (ADR-0054). Acima do `TRANSITION` de propósito: quando o quadro lança, o que estiver na
37
+ * tela — menu aberto, fade a meio, mensagem de fase — está congelado e deixou de importar. O aviso tem de
38
+ * ser visto por cima de tudo isso, e só o painel de desenvolvimento fica acima dele.
39
+ */
40
+ readonly LOOP_CRASH: 45000;
35
41
  readonly DEBUG: 90000;
36
42
  };
37
43
  export type LayerName = keyof typeof Z;
@@ -49,6 +49,12 @@ export const Z = {
49
49
  MENU: 30000, // menus — RANGE reservado 30000–39999 (níveis aninhados: +1000 por nível)
50
50
  MENU_MAX: 39999,
51
51
  TRANSITION: 40000, // fade/wipe de troca de fase (cobre tudo)
52
+ /**
53
+ * O LAÇO MORREU (ADR-0054). Acima do `TRANSITION` de propósito: quando o quadro lança, o que estiver na
54
+ * tela — menu aberto, fade a meio, mensagem de fase — está congelado e deixou de importar. O aviso tem de
55
+ * ser visto por cima de tudo isso, e só o painel de desenvolvimento fica acima dele.
56
+ */
57
+ LOOP_CRASH: 45000,
52
58
  DEBUG: 90000, // painel ?debug / FPS / hitboxes (dev; topo absoluto)
53
59
  };
54
60
  // Cadeia de PÓS-PROCESSO — passes sobre o frame COMPOSTO, do interno p/ o externo. NÃO são camadas Z.
@@ -259,6 +259,7 @@ const en = {
259
259
  'audio.noSystemVoices': '(no system voices)',
260
260
  'sr.tts.engineNoLanguage': 'This engine does not speak this language yet — for now, use Piper (neural) or the browser voice.',
261
261
  'sr.tts.noNeuralForLanguage': 'There is no neural voice for this language yet — carrying on with the browser voice, which speaks it.',
262
+ 'sr.tts.neuralNotBundled': 'This build of the game ships no neural voice — carrying on with the browser voice.',
262
263
  'sr.tts.downloading': 'Downloading the neural voice (needs the internet on the first use only)…',
263
264
  'sr.tts.progress': 'Neural voice: {pct}%.',
264
265
  'sr.tts.ready': 'Neural voice ready, in {s} seconds.',
@@ -259,6 +259,7 @@ const es = {
259
259
  'audio.noSystemVoices': '(sin voces del sistema)',
260
260
  'sr.tts.engineNoLanguage': 'Este motor todavía no habla este idioma — por ahora, usa Piper (neuronal) o la voz del navegador.',
261
261
  'sr.tts.noNeuralForLanguage': 'Todavía no hay voz neuronal para este idioma — seguimos con la voz del navegador, que sí lo habla.',
262
+ 'sr.tts.neuralNotBundled': 'Esta versión del juego no trae voz neuronal — seguimos con la voz del navegador.',
262
263
  'sr.tts.downloading': 'Descargando la voz neuronal (necesita internet solo en el 1.er uso)…',
263
264
  'sr.tts.progress': 'Voz neuronal: {pct} %.',
264
265
  'sr.tts.ready': 'Voz neuronal lista, en {s} segundos.',
@@ -331,6 +331,9 @@ const pt = {
331
331
  'audio.noSystemVoices': '(sem vozes do sistema)',
332
332
  'sr.tts.engineNoLanguage': 'Este motor ainda não fala este idioma — por enquanto, use Piper (neural) ou a voz do navegador.',
333
333
  'sr.tts.noNeuralForLanguage': 'Ainda não há voz neural para este idioma — seguindo com a voz do navegador, que fala a língua certa.',
334
+ // ⚠️ NÃO diz «para este idioma» (ADR-0094): sem motor neural nesta montagem, não há voz neural em idioma
335
+ // nenhum, e a outra frase faria a criança trocar de idioma à procura do que não está lá.
336
+ 'sr.tts.neuralNotBundled': 'Esta versão do jogo não traz voz neural — seguindo com a voz do navegador.',
334
337
  'sr.tts.downloading': 'Baixando a voz neural (precisa de internet só no 1º uso)…',
335
338
  'sr.tts.progress': 'Voz neural: {pct}%.',
336
339
  'sr.tts.ready': 'Voz neural pronta, em {s} segundos.',
@@ -3,6 +3,30 @@ interface TtsEngine {
3
3
  id: string;
4
4
  speak: (text: string) => void;
5
5
  }
6
+ /**
7
+ * O QUE A ENGINE PRECISA DE UM MOTOR NEURAL — e nada mais. Duas chamadas, escritas aqui em vez de importadas
8
+ * do fornecedor, porque importar o TIPO obrigaria o pacote a estar instalado para o `tsc` do consumidor
9
+ * correr: seria o mesmo defeito do ADR-0093 mudado de campo, do `dependencies` para o espaço de tipos.
10
+ */
11
+ export interface SessaoNeural {
12
+ predict: (texto: string) => Promise<{
13
+ arrayBuffer: () => Promise<ArrayBuffer>;
14
+ }>;
15
+ }
16
+ export interface ModuloNeural {
17
+ TtsSession: {
18
+ create: (o: {
19
+ voiceId: string;
20
+ progress?: (p: {
21
+ loaded: number;
22
+ total: number;
23
+ }) => void;
24
+ logger?: (...a: unknown[]) => void;
25
+ }) => Promise<SessaoNeural>;
26
+ };
27
+ }
28
+ /** A PORTA (ADR-0094). Uma linha do lado do jogo: `() => import('@mintplex-labs/piper-tts-web')`. */
29
+ export type CarregarVozNeural = () => Promise<ModuloNeural>;
6
30
  export interface TtsCtx {
7
31
  srSay: (t: string) => void;
8
32
  srAlert: (t: string) => void;
@@ -14,6 +38,17 @@ export interface TtsCtx {
14
38
  getAudioCat: () => Record<string, {
15
39
  on: boolean;
16
40
  }> | null;
41
+ /**
42
+ * COMO SE CARREGA O MOTOR NEURAL — fornecido pelo JOGO, ausente por omissão (ADR-0094).
43
+ *
44
+ * ⚠️ A engine NÃO NOMEIA O FORNECEDOR, e o motivo é medido: o `@mintplex-labs/piper-tts-web` traz
45
+ * `onnxruntime-web` como peer NÃO-opcional, que o npm instala sozinho — **135,4 MB** no `node_modules` de
46
+ * todo consumidor, incluindo um que nunca fale por voz neural. Nomeá-lo aqui obrigaria os 135 MB, e
47
+ * declará-lo em `devDependencies` (como estava até 06/09) publica um pacote que não compila.
48
+ *
49
+ * Ausente = a narração cai na voz do navegador (Web Speech), que fala o idioma certo e não pesa nada.
50
+ */
51
+ carregarVozNeural?: CarregarVozNeural;
17
52
  }
18
53
  export interface Tts {
19
54
  narrate: (text: string) => void;
@@ -28,6 +63,12 @@ export interface Tts {
28
63
  readonly loading: boolean;
29
64
  readonly failed: boolean;
30
65
  readonly narrateCount: number;
66
+ /**
67
+ * ESTA MONTAGEM TEM MOTOR NEURAL? (ADR-0094) O painel de áudio pergunta antes de o oferecer: uma opção
68
+ * que não pode funcionar é pior que uma opção a menos — quem a escolhe fica à espera de um download que
69
+ * nunca começa, e quem navega por escuta não tem como ver que não começou.
70
+ */
71
+ readonly neuralDisponivel: boolean;
31
72
  }
32
73
  export declare function createTts(ctx: TtsCtx): Tts;
33
74
  export {};
@@ -1,16 +1,20 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // platform/tts — narração por voz. Motor NEURAL Piper pt-BR carregado LAZY (import de CDN + cache OPFS → a 2ª sessão fala
3
- // offline), com FALLBACK imediato p/ a voz nativa do navegador (Web Speech) enquanto o neural não chega. narrate() é o
2
+ // platform/tts — narração por voz. Motor NEURAL pt-BR carregado LAZY pela PORTA que o jogo fornece (ADR-0094; modelo
3
+ // em cache OPFS → a 2ª sessão fala offline), com FALLBACK imediato p/ a voz nativa do navegador (Web Speech) — que é
4
+ // também o que se ouve quando não há porta nenhuma, e é por isso que a ausência dela não cala nada. narrate() é o
4
5
  // ponto de entrada, gated pelo toggle 'Narração (TTS)' do mixer (audioCat.tts.on) — independe das legendas. As funções de
5
6
  // PAINEL (populateTTSEngines/Voices/reflectTTS) ficam no game.js (→ ui/settings-audio, #38→#54) e usam get/setEngineSel +
6
7
  // get/setVoiceObj daqui. Injeção por closure. Ver docs/plano-tts-fase-f5.md + docs/5-Refactoring/plano-modularizacao-mapa.md.
7
8
  import * as store from './storage.js';
8
9
  import { t, bcp47 } from '../core/i18n.js';
9
10
  import { criarFalaInterrompivel } from './interruptible-speech.js';
10
- // ⚠️ MIGRAÇÃO PENDENTE (ADR-0022): esta implementação com @mintplex-labs/piper-tts-web será SUBSTITUÍDA por
11
- // sherpa-onnx-wasm (loader universal, modelos VITS/Piper + Kokoro-multi-lang carregados de qq URL R2 em runtime via
11
+ // ⚠️ MIGRAÇÃO PENDENTE (ADR-0022): a implementação neural de hoje é o @mintplex-labs/piper-tts-web e será SUBSTITUÍDA
12
+ // por sherpa-onnx-wasm (loader universal, modelos VITS/Piper + Kokoro-multi-lang carregados de qq URL R2 em runtime via
12
13
  // FS.writeFile; lazy-fetch; eSpeak/Web Speech de fallback). @mintplex-labs foi descontinuado e só carrega 2 vozes pt-BR.
13
- // Enquanto a migração não vem: a lib vem do npm (code-split pelo Vite no 1º uso), modelo do HF em cache OPFS. (#69→#38)
14
+ // ⚠️ E DESDE O ADR-0094 ESTE MÓDULO NÃO NOMEIA FORNECEDOR NENHUM — quem o nomeia é o JOGO, por `ctx.carregarVozNeural`.
15
+ // O nome estava aqui num `import()` e o pacote em `devDependencies`, o que publicou uma engine que não compilava
16
+ // (ADR-0093); pô-lo em `dependencies` consertava o build e obrigava todo consumidor a 135,4 MB de `onnxruntime-web`,
17
+ // que o fornecedor traz como peer não-opcional. A porta é o que faz a troca do ADR-0022 não ser um segundo abalo.
14
18
  /**
15
19
  * Voz NEURAL por idioma, indexada pela etiqueta BCP-47 de core/i18n. Só o pt-BR tem uma hoje, e essa ausência
16
20
  * é agora CONSULTÁVEL em vez de presumida: `loadTTS` pergunta à tabela se existe voz para o idioma corrente,
@@ -57,6 +61,15 @@ export function createTts(ctx) {
57
61
  ctx.srAlert(t('sr.tts.engineNoLanguage'));
58
62
  return;
59
63
  }
64
+ // ESTA MONTAGEM NÃO TRAZ MOTOR NEURAL (ADR-0094). Vem ANTES da pergunta do idioma de propósito: sem
65
+ // porta, não há voz neural em idioma nenhum, e dizer «não há voz para o teu idioma» faria a criança
66
+ // pensar que trocar de idioma resolveria. `ttsFailed` para não repetir a pergunta a cada fala.
67
+ const carregar = ctx.carregarVozNeural;
68
+ if (!carregar) {
69
+ ttsFailed = true;
70
+ ctx.srAlert(t('sr.tts.neuralNotBundled'));
71
+ return;
72
+ }
60
73
  // O Piper só tem voz para os idiomas em TTS_SOURCES. Sem voz para o idioma corrente, NÃO se baixa a de
61
74
  // outro idioma: avisa e segue na voz do navegador, que fala a língua certa.
62
75
  const fonte = TTS_SOURCES[bcp47()];
@@ -67,7 +80,7 @@ export function createTts(ctx) {
67
80
  ttsLoading = true;
68
81
  const t0 = performance.now();
69
82
  ctx.srSay(t('sr.tts.downloading'));
70
- import('@mintplex-labs/piper-tts-web').then(async (mod) => {
83
+ carregar().then(async (mod) => {
71
84
  const session = await mod.TtsSession.create({ voiceId: fonte.voice,
72
85
  progress: (p) => { if (!p || !p.total)
73
86
  return; const pct = Math.round(p.loaded * 100 / p.total); if (pct >= _ttsPct + 25 && pct < 100) {
@@ -138,5 +151,6 @@ export function createTts(ctx) {
138
151
  getEngineSel: () => ttsEngineSel, setEngineSel: (v) => { ttsEngineSel = v; },
139
152
  getEngine: () => ttsEngine, getVoiceObj: () => _ttsVoiceObj, setVoiceObj: (v) => { _ttsVoiceObj = v; },
140
153
  get loading() { return ttsLoading; }, get failed() { return ttsFailed; }, get narrateCount() { return _narrateCount; },
154
+ get neuralDisponivel() { return !!ctx.carregarVozNeural; },
141
155
  };
142
156
  }
@@ -1,7 +1,11 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** O id da caixa do aviso. Estável porque a folha de estilo e o teste a procuram. */
3
+ export declare const AVISO_DE_QUEDA_ID = "incl-parou";
2
4
  export interface AvisoDeQuedaCtx {
3
5
  /** `querySelector` do documento deste jogo. Injetado: a engine recebe o dela, `main.ts` passa o `$` global. */
4
6
  procurar: (sel: string) => HTMLElement | null;
7
+ /** `document.createElement` — a caixa do aviso é um elemento de verdade, não um pseudo-elemento. */
8
+ criar: (tag: string) => HTMLElement;
5
9
  /** A narração falada, quando o jogo tiver uma. Ausente = o aviso escrito basta. */
6
10
  narrar?: (texto: string) => void;
7
11
  }
@@ -24,6 +24,8 @@
24
24
  // segundo jogo na mesma página) perderia o aviso. O anúncio de que tudo parou é o último lugar que
25
25
  // deveria depender de uma busca global.
26
26
  import { t } from '../core/i18n.js';
27
+ /** O id da caixa do aviso. Estável porque a folha de estilo e o teste a procuram. */
28
+ export const AVISO_DE_QUEDA_ID = 'incl-parou';
27
29
  /**
28
30
  * Monta o `aoFalhar` para entrar em `startLoop(ticker, quadro, maxDt, { aoFalhar })`.
29
31
  *
@@ -50,11 +52,28 @@ export function criarAvisoDeQueda(ctx) {
50
52
  ctx.narrar?.(msg);
51
53
  }
52
54
  catch { /* noop: a narração falhou; o aviso escrito já saiu */ }
53
- // O VISÍVEL. O texto entra no ATRIBUTO e a folha de estilo o mostra com `content: attr(...)` — é o que
54
- // mantém a frase LOCALIZADA. Um aviso cravado no CSS estaria em inglês para uma criança brasileira, que
55
- // é o defeito que o pilar 3 existe para impedir.
55
+ // O VISÍVEL, e é um ELEMENTO DE VERDADE — não um pseudo-elemento.
56
+ //
57
+ // ⚠️ ERA `::after` COM `content: attr(data-incl-parou)`, E NUNCA TERIA APARECIDO. Medido no arranque real
58
+ // em 2026-09-06: `.game-region.crt-scan-1::after` é o efeito de scanline do CRT, ligado por padrão, e um
59
+ // elemento tem UM `::after` só — as duas regras não se empilham, a do CRT vem depois e vence. O
60
+ // `::before` está igualmente tomado, pela vinheta. O aviso disputava um lugar já ocupado.
61
+ //
62
+ // ⚠️ E O PSEUDO-ELEMENTO ERA ERRADO POR UMA SEGUNDA RAZÃO, que a primeira escondia: texto de `content`
63
+ // não entra de forma confiável na árvore de acessibilidade. O aviso de que o jogo morreu é exatamente o
64
+ // que não pode depender disso.
65
+ //
66
+ // O raciocínio que continua de pé é o da TRADUÇÃO: a frase vem de `t()` e entra por `textContent`, então
67
+ // ela chega traduzida e escapada. O que muda é o recipiente.
56
68
  const regiao = ctx.procurar('#game-region');
57
- if (regiao)
58
- regiao.setAttribute('data-incl-parou', msg);
69
+ if (!regiao)
70
+ return;
71
+ const anterior = ctx.procurar('#' + AVISO_DE_QUEDA_ID);
72
+ const caixa = anterior ?? ctx.criar('div');
73
+ caixa.id = AVISO_DE_QUEDA_ID;
74
+ caixa.setAttribute('role', 'alert');
75
+ caixa.textContent = msg;
76
+ if (!anterior)
77
+ regiao.appendChild(caixa);
59
78
  };
60
79
  }
@@ -29,6 +29,12 @@ export interface TtsPanel {
29
29
  setVoiceObj: (v: SpeechSynthesisVoice | null) => void;
30
30
  loadTTS: () => void;
31
31
  narrate: (text: string) => void;
32
+ /**
33
+ * ESTA MONTAGEM TEM MOTOR NEURAL? (ADR-0094) OPCIONAL, e a ausência vale `true`: um falso de teste
34
+ * escrito antes deste campo não tem opinião sobre motores neurais, e fazê-lo esconder o Piper mudaria o
35
+ * que esse teste afirma sem que ninguém tenha escrito a mudança.
36
+ */
37
+ neuralDisponivel?: boolean;
32
38
  }
33
39
  /**
34
40
  * Saída de áudio dedicada de um jogador: o id do dispositivo e o AudioContext/ganho que ele abriu.
@@ -104,6 +110,18 @@ export declare function caneDivMessage(div: number): string;
104
110
  /** #tts-engine's option catalog: (value, i18n KEY of the label). The engine NAMES are proper nouns and stay
105
111
  * put; what translates is the parenthetical that explains each one. Keys, not text — see input/devices. */
106
112
  export declare const TTS_ENGINE_OPTIONS: readonly (readonly [string, string])[];
113
+ /**
114
+ * Os motores que ESTA MONTAGEM pode de facto oferecer (ADR-0094).
115
+ *
116
+ * ⚠️ Desde que o motor neural passou a chegar por PORTA (`ctx.carregarVozNeural`), «o Piper existe» deixou
117
+ * de ser verdade sobre a engine e passou a ser verdade sobre o JOGO. Oferecer uma opção que não pode
118
+ * funcionar é pior que uma opção a menos: quem a escolhe fica à espera de um download que nunca começa, e
119
+ * quem navega por escuta não tem como ver que não começou.
120
+ *
121
+ * Só o `piper` é filtrado. `kokoro`/`kitten`/`espeak` também não funcionam hoje — «ainda não entraram» —,
122
+ * mas isso é anterior a este registro e escondê-los aqui mudaria comportamento que ninguém pediu para mudar.
123
+ */
124
+ export declare function opcoesDeMotor(neuralDisponivel: boolean): readonly (readonly [string, string])[];
107
125
  export interface VoiceLike {
108
126
  name: string;
109
127
  lang: string;
@@ -69,6 +69,20 @@ export const TTS_ENGINE_OPTIONS = [
69
69
  ['kitten', 'tts.engine.kitten'],
70
70
  ['espeak', 'tts.engine.espeak'],
71
71
  ];
72
+ /**
73
+ * Os motores que ESTA MONTAGEM pode de facto oferecer (ADR-0094).
74
+ *
75
+ * ⚠️ Desde que o motor neural passou a chegar por PORTA (`ctx.carregarVozNeural`), «o Piper existe» deixou
76
+ * de ser verdade sobre a engine e passou a ser verdade sobre o JOGO. Oferecer uma opção que não pode
77
+ * funcionar é pior que uma opção a menos: quem a escolhe fica à espera de um download que nunca começa, e
78
+ * quem navega por escuta não tem como ver que não começou.
79
+ *
80
+ * Só o `piper` é filtrado. `kokoro`/`kitten`/`espeak` também não funcionam hoje — «ainda não entraram» —,
81
+ * mas isso é anterior a este registro e escondê-los aqui mudaria comportamento que ninguém pediu para mudar.
82
+ */
83
+ export function opcoesDeMotor(neuralDisponivel) {
84
+ return neuralDisponivel ? TTS_ENGINE_OPTIONS : TTS_ENGINE_OPTIONS.filter(([v]) => v !== 'piper');
85
+ }
72
86
  /**
73
87
  * As vozes do sistema no idioma pedido; sem nenhuma, a lista inteira.
74
88
  *
@@ -204,7 +218,7 @@ export function initSettingsAudio(ctx) {
204
218
  if (!sel || sel.dataset.filled)
205
219
  return;
206
220
  sel.dataset.filled = '1';
207
- TTS_ENGINE_OPTIONS.forEach(([v, l]) => {
221
+ opcoesDeMotor(ctx.tts.neuralDisponivel !== false).forEach(([v, l]) => {
208
222
  const o = document.createElement('option');
209
223
  o.value = v;
210
224
  o.textContent = t(l);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-inclusionist/engine",
3
- "version": "6.36.1",
3
+ "version": "7.0.0",
4
4
  "type": "module",
5
5
  "description": "Accessibility-first (WCAG 2.2 + GAG) engine for educational 2D pixel-art games, built with PixiJS. Screen reader, sonar, high contrast by role, colour-vision filters, remappable input as intent, i18n and offline PWA — the parts every game inherits (ADR-0035, ADR-0036).",
6
6
  "comment:exports": [
@@ -23,13 +23,34 @@
23
23
  "faces by RELATIVE url, so the folder travels whole or not at all."
24
24
  ],
25
25
  "exports": {
26
- ".": { "types": "./dist-pkg/boot/create-game.d.ts", "default": "./dist-pkg/boot/create-game.js" },
27
- "./core/*.js": { "types": "./dist-pkg/core/*.d.ts", "default": "./dist-pkg/core/*.js" },
28
- "./input/*.js": { "types": "./dist-pkg/input/*.d.ts", "default": "./dist-pkg/input/*.js" },
29
- "./render/*.js": { "types": "./dist-pkg/render/*.d.ts", "default": "./dist-pkg/render/*.js" },
30
- "./platform/*.js": { "types": "./dist-pkg/platform/*.d.ts", "default": "./dist-pkg/platform/*.js" },
31
- "./ui/*.js": { "types": "./dist-pkg/ui/*.d.ts", "default": "./dist-pkg/ui/*.js" },
32
- "./educational/*.js": { "types": "./dist-pkg/educational/*.d.ts", "default": "./dist-pkg/educational/*.js" },
26
+ ".": {
27
+ "types": "./dist-pkg/boot/create-game.d.ts",
28
+ "default": "./dist-pkg/boot/create-game.js"
29
+ },
30
+ "./core/*.js": {
31
+ "types": "./dist-pkg/core/*.d.ts",
32
+ "default": "./dist-pkg/core/*.js"
33
+ },
34
+ "./input/*.js": {
35
+ "types": "./dist-pkg/input/*.d.ts",
36
+ "default": "./dist-pkg/input/*.js"
37
+ },
38
+ "./render/*.js": {
39
+ "types": "./dist-pkg/render/*.d.ts",
40
+ "default": "./dist-pkg/render/*.js"
41
+ },
42
+ "./platform/*.js": {
43
+ "types": "./dist-pkg/platform/*.d.ts",
44
+ "default": "./dist-pkg/platform/*.js"
45
+ },
46
+ "./ui/*.js": {
47
+ "types": "./dist-pkg/ui/*.d.ts",
48
+ "default": "./dist-pkg/ui/*.js"
49
+ },
50
+ "./educational/*.js": {
51
+ "types": "./dist-pkg/educational/*.d.ts",
52
+ "default": "./dist-pkg/educational/*.js"
53
+ },
33
54
  "./style.css": "./app/css/style.css",
34
55
  "./assets/*": "./app/public/*",
35
56
  "./package.json": "./package.json"
@@ -42,7 +63,9 @@
42
63
  "⚠️ E o consumidor que mais depende disto hoje não é o registro: `the-inclusionist/hartwig-zdog-chess`",
43
64
  "consome esta engine por `file:../SP-the-inclusionist-tracer`, ou seja, lê ESTA pasta ao vivo."
44
65
  ],
45
- "sideEffects": ["*.css"],
66
+ "sideEffects": [
67
+ "*.css"
68
+ ],
46
69
  "license": "AGPL-3.0-or-later",
47
70
  "comment:ownership": [
48
71
  "ADR-0071 §2 said the ownership claim does NOT live in the package NAME, and that nothing would enter",
@@ -51,8 +74,13 @@
51
74
  "`files`). Patrimonial owner: the Municipio, under Lei no 9.609/1998 art. 4. Publication is a PEDIDO,",
52
75
  "not the developer's decision."
53
76
  ],
54
- "repository": { "type": "git", "url": "git+https://github.com/the-inclusionist/the-inclusionist-engine.git" },
55
- "publishConfig": { "access": "public" },
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "git+https://github.com/the-inclusionist/the-inclusionist-engine.git"
80
+ },
81
+ "publishConfig": {
82
+ "access": "public"
83
+ },
56
84
  "files": [
57
85
  "dist-pkg",
58
86
  "app/css/style.css",
@@ -62,6 +90,22 @@
62
90
  "docs/LICENSES.md",
63
91
  "docs/CREDITS.md"
64
92
  ],
93
+ "comment:no-dependencies": [
94
+ "⚠️ ESTE PACOTE NÃO TEM `dependencies`, E ISSO É UMA DECISÃO MEDIDA (ADR-0094), não um acaso.",
95
+ "A 6.36.1 saiu com `@mintplex-labs/piper-tts-web` em `devDependencies` enquanto `platform/tts` — código",
96
+ "EMBARCADO, alcançado por `createGame` — o nomeava num `import()`. O npm não instala devDependencies para",
97
+ "quem consome, então a versão publicada NÃO COMPILAVA para ninguém (ADR-0093).",
98
+ "O conserto óbvio era mover para `dependencies`, e ele foi feito e DESFEITO no mesmo dia, por uma medida",
99
+ "que faltava: o fornecedor traz `onnxruntime-web` como peer NÃO-opcional, que o npm instala sozinho —",
100
+ "**135,4 MB** no `node_modules` de todo consumidor, incluindo um jogo que nunca fale por voz neural.",
101
+ "⚠️ `optionalDependencies` NÃO resolveria: o npm as instala por omissão; só tolera a falha da instalação.",
102
+ "O motor neural passa a chegar por PORTA — `carregarVozNeural` em `CreateGameOptions`, uma linha do lado",
103
+ "do jogo. `main.ts` (o app DESTE repositório, que fica fora do pacote) é o único ponto da árvore que",
104
+ "nomeia o fornecedor, e é por isso que ele continua em `devDependencies` aqui.",
105
+ "O gate que prende as duas direções é `tests/engine-package.node.test.js`: nenhum módulo embarcado pode",
106
+ "nomear pacote não declarado, e nada declarado pode ficar sem quem o nomeie. É ele que faz a troca por",
107
+ "sherpa-onnx-wasm (ADR-0022) não reabrir o mesmo buraco em silêncio."
108
+ ],
65
109
  "comment:peerDependencies": [
66
110
  "PixiJS is a PEER, not a dependency. Measured on the emitted package (2026-09-05): exactly TWO shipped",
67
111
  "modules import it — `render/canvas` and `render/minimap` — plus their two `.d.ts`. A consumer that",