@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.
- package/README.md +5 -3
- package/app/css/style.css +22 -5
- package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-ar.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-br.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-ca.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-cl.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-co.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-mx.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-us-modern.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-us-trad.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +64 -2
- package/dist-pkg/boot/create-game.d.ts +107 -4
- package/dist-pkg/boot/create-game.js +337 -18
- package/dist-pkg/core/constants.d.ts +0 -28
- package/dist-pkg/core/constants.js +34 -17
- package/dist-pkg/core/contract.d.ts +99 -0
- package/dist-pkg/core/contract.js +78 -0
- package/dist-pkg/core/entity.d.ts +47 -4
- package/dist-pkg/core/layers.d.ts +18 -0
- package/dist-pkg/core/layers.js +18 -0
- package/dist-pkg/core/rng.js +14 -4
- package/dist-pkg/core/route.d.ts +42 -0
- package/dist-pkg/core/route.js +158 -0
- package/dist-pkg/core/state.d.ts +10 -1
- package/dist-pkg/core/state.js +13 -0
- package/dist-pkg/educational/adaptive-engine.d.ts +65 -0
- package/dist-pkg/educational/adaptive-engine.js +117 -0
- package/dist-pkg/educational/segment-bar.d.ts +96 -0
- package/dist-pkg/educational/segment-bar.js +89 -0
- package/dist-pkg/i18n/en.js +44 -0
- package/dist-pkg/i18n/es.js +44 -0
- package/dist-pkg/i18n/pt.js +60 -0
- package/dist-pkg/input/default-bindings.d.ts +40 -0
- package/dist-pkg/input/default-bindings.js +151 -9
- package/dist-pkg/input/gamepad.d.ts +33 -12
- package/dist-pkg/input/gamepad.js +103 -16
- package/dist-pkg/input/keyboard-runtime.d.ts +6 -8
- package/dist-pkg/input/keyboard-runtime.js +24 -5
- package/dist-pkg/input/keyboard.d.ts +42 -0
- package/dist-pkg/input/keyboard.js +87 -17
- package/dist-pkg/input/keydown.d.ts +40 -2
- package/dist-pkg/input/keydown.js +29 -6
- package/dist-pkg/input/latch-edge.d.ts +28 -0
- package/dist-pkg/input/latch-edge.js +45 -0
- package/dist-pkg/input/latch-scope.d.ts +70 -0
- package/dist-pkg/input/latch-scope.js +110 -0
- package/dist-pkg/input/latch-store.d.ts +45 -0
- package/dist-pkg/input/latch-store.js +74 -0
- package/dist-pkg/input/latch-sync.d.ts +47 -0
- package/dist-pkg/input/latch-sync.js +43 -0
- package/dist-pkg/input/origem-sintetica.d.ts +44 -0
- package/dist-pkg/input/origem-sintetica.js +60 -0
- package/dist-pkg/input/pad-defaults.d.ts +22 -0
- package/dist-pkg/input/pad-defaults.js +34 -0
- package/dist-pkg/input/pointer.d.ts +61 -0
- package/dist-pkg/input/pointer.js +71 -0
- package/dist-pkg/input/state.d.ts +86 -1
- package/dist-pkg/input/state.js +128 -1
- package/dist-pkg/input/touch-bindings.d.ts +21 -2
- package/dist-pkg/input/touch-bindings.js +15 -3
- package/dist-pkg/input/touch.js +9 -1
- package/dist-pkg/input/transporte-em-uso.d.ts +101 -0
- package/dist-pkg/input/transporte-em-uso.js +130 -0
- package/dist-pkg/input/transports.d.ts +88 -2
- package/dist-pkg/input/transports.js +60 -5
- package/dist-pkg/input/vocabulary-migration.d.ts +18 -7
- package/dist-pkg/platform/audio-earcons.d.ts +29 -2
- package/dist-pkg/platform/audio-earcons.js +10 -0
- package/dist-pkg/platform/audio-nav.d.ts +1 -1
- package/dist-pkg/platform/audio-sonar.d.ts +149 -9
- package/dist-pkg/platform/audio-sonar.js +232 -21
- package/dist-pkg/platform/guide-intensity.d.ts +40 -0
- package/dist-pkg/platform/guide-intensity.js +73 -0
- package/dist-pkg/platform/pesados-catalogo.d.ts +14 -0
- package/dist-pkg/platform/pesados-catalogo.js +177 -0
- package/dist-pkg/platform/pesados.d.ts +31 -0
- package/dist-pkg/platform/pesados.js +75 -0
- package/dist-pkg/platform/storage.d.ts +30 -0
- package/dist-pkg/platform/storage.js +35 -0
- package/dist-pkg/platform/tts.js +16 -1
- package/dist-pkg/platform/voice-plan.d.ts +109 -0
- package/dist-pkg/platform/voice-plan.js +156 -0
- package/dist-pkg/platform/vozes-prontas.d.ts +28 -0
- package/dist-pkg/platform/vozes-prontas.js +61 -0
- package/dist-pkg/render/draw.d.ts +1 -1
- package/dist-pkg/render/draw.js +15 -5
- package/dist-pkg/render/viz-axes.d.ts +17 -0
- package/dist-pkg/render/viz-axes.js +19 -0
- package/dist-pkg/render/viz-setters.d.ts +34 -2
- package/dist-pkg/render/viz-setters.js +189 -33
- package/dist-pkg/render/wheelchair-sprites.d.ts +11 -3
- package/dist-pkg/render/wheelchair-sprites.js +10 -4
- package/dist-pkg/ui/dom.js +20 -2
- package/dist-pkg/ui/fonts.d.ts +71 -1
- package/dist-pkg/ui/fonts.js +111 -8
- package/dist-pkg/ui/latch-refusal.d.ts +32 -0
- package/dist-pkg/ui/latch-refusal.js +60 -0
- package/dist-pkg/ui/layout.d.ts +32 -0
- package/dist-pkg/ui/layout.js +64 -1
- package/dist-pkg/ui/motion-scene.d.ts +41 -0
- package/dist-pkg/ui/motion-scene.js +76 -0
- package/dist-pkg/ui/panel-shell.d.ts +53 -0
- package/dist-pkg/ui/panel-shell.js +103 -0
- package/dist-pkg/ui/pause-icons.d.ts +168 -20
- package/dist-pkg/ui/pause-icons.js +315 -45
- package/dist-pkg/ui/reach-notice.js +8 -0
- package/dist-pkg/ui/settings-audio.d.ts +14 -1
- package/dist-pkg/ui/settings-audio.js +43 -3
- package/dist-pkg/ui/settings-controls.d.ts +66 -6
- package/dist-pkg/ui/settings-controls.js +179 -17
- package/dist-pkg/ui/settings-empathy.d.ts +15 -0
- package/dist-pkg/ui/settings-empathy.js +2 -0
- package/dist-pkg/ui/settings-motion.d.ts +25 -19
- package/dist-pkg/ui/settings-motion.js +32 -19
- package/dist-pkg/ui/settings-motor.d.ts +101 -3
- package/dist-pkg/ui/settings-motor.js +136 -9
- package/dist-pkg/ui/settings-typo.d.ts +49 -5
- package/dist-pkg/ui/settings-typo.js +74 -23
- package/dist-pkg/ui/settings-visual.d.ts +21 -1
- package/dist-pkg/ui/settings-visual.js +48 -4
- package/dist-pkg/ui/shell.d.ts +13 -3
- package/dist-pkg/ui/shell.js +3 -4
- package/dist-pkg/ui/simulation-refusal.d.ts +32 -0
- package/dist-pkg/ui/simulation-refusal.js +57 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +48 -0
- package/dist-pkg/ui/visual-axes-panel.js +95 -0
- package/dist-pkg/ui/webcam.js +6 -1
- package/docs/CREDITS.md +22 -1
- package/docs/LICENSES.md +170 -132
- package/package.json +25 -4
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/voice-plan.ts — O PLANO DAS VOZES NEURAIS (ADR-0110), na metade PURA.
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE ESTE MÓDULO É =========================
|
|
5
|
+
// O catálogo das quatro vozes que a engine GARANTE, a ordem por que se buscam, e — a parte que mais importa —
|
|
6
|
+
// a resposta honesta a «o que é que esta criança está a ouvir AGORA». Sem rede, sem cache, sem tempo. Quem
|
|
7
|
+
// busca de facto é a outra metade; quem decide O QUE buscar e o que DIZER é esta.
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ POR QUE O CATÁLOGO VIVE AQUI E NÃO NO `TTS_SOURCES`. A tabela do `platform/tts` é indexada por idioma e
|
|
10
|
+
// tem UMA voz por idioma; este catálogo tem QUATRO vozes para TRÊS idiomas, porque o en-US tem duas. Ligá-las
|
|
11
|
+
// hoje mudaria comportamento: o `loadTTS` passaria a pedir `en_US-ryan-medium` à PORTA DO CARTUCHO
|
|
12
|
+
// (ADR-0094), que pode não ter essa voz. A tabela liga-se a isto quando o buscador existir, e nem um dia
|
|
13
|
+
// antes — uma lista ligada a um fornecedor que não a conhece é pior do que uma lista à espera.
|
|
14
|
+
//
|
|
15
|
+
// ⚠️ E QUAL DAS DUAS VOZES EN-US A CRIANÇA OUVE POR PADRÃO NÃO SE DECIDE AQUI. O ADR-0110 deixa-o
|
|
16
|
+
// explicitamente em aberto, e a ORDEM DESTE ARRAY NÃO É UMA PREFERÊNCIA — é a ordem em que o Dev as nomeou.
|
|
17
|
+
// Ler ordem como escolha seria decidir por omissão, que é o defeito que este projeto persegue.
|
|
18
|
+
/**
|
|
19
|
+
* AS QUATRO QUE A ENGINE GARANTE (ADR-0110), decididas pelo Dev em 2026-09-08.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ TODAS `vits-piper` E NENHUMA `ncnn`, que o ADR-0065 §5 já proibia: `ncnn` é outro MOTOR DE INFERÊNCIA —
|
|
22
|
+
* modelos `.param`/`.bin` e não `.onnx` — e este projeto corre sherpa-onnx. Dois dos links que chegaram em
|
|
23
|
+
* 2026-09-08 apontavam para os espelhos `ncnn`, e é por isso que a proibição tem gate e não só um comentário.
|
|
24
|
+
* ⚠️ E NENHUMA `int8`, pela razão MEDIDA do mesmo §5: no backend WASM o int8 corre ~3× MAIS LENTO que o fp32
|
|
25
|
+
* (RTF 5,6 contra 2,5, do log do próprio Dev) e produz saída incorrecta ou muda. Quantizar não encurta a
|
|
26
|
+
* espera aqui — alonga-a.
|
|
27
|
+
*/
|
|
28
|
+
export const VOZES_NEURAIS = Object.freeze([
|
|
29
|
+
Object.freeze({ locale: 'pt-BR', engine: 'piper', voice: 'pt_BR-faber-medium' }),
|
|
30
|
+
Object.freeze({ locale: 'en-US', engine: 'piper', voice: 'en_US-ryan-medium' }),
|
|
31
|
+
Object.freeze({ locale: 'en-US', engine: 'piper', voice: 'en_US-amy-medium' }),
|
|
32
|
+
Object.freeze({ locale: 'es-MX', engine: 'piper', voice: 'es_MX-claude-high' }),
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* DE ONDE VÊM OS MODELOS (ADR-0114), num sítio só.
|
|
36
|
+
*
|
|
37
|
+
* 📏 MEDIDO EM 2026-09-08, e não escolhido: o `piper.ttstool.com` — que o Dev nomeou ao perguntar — serve o
|
|
38
|
+
* próprio runtime da própria origem e busca os modelos aqui. As quatro vozes deste catálogo respondem 200
|
|
39
|
+
* neste endereço, com `Access-Control-Allow-Origin: *`, logo um PWA pode buscá-las de outra origem.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ UM SÍTIO SÓ É A METADE QUE O REGISTO PEDE. Um endereço repetido no ponto de uso é como o CDN do
|
|
42
|
+
* WebGazer chegou ao `ui/webcam` — escrito à mão, sem política, e sem ninguém a poder mudá-lo de uma vez.
|
|
43
|
+
*
|
|
44
|
+
* ========================= 🔴 MUDOU DE ESPELHO EM 2026-09-09, E A REGRA É «BUSCAR DE ONDE O LEITOR LÊ» ====
|
|
45
|
+
* Era `rhasspy/piper-voices`, medido a 08/09 como o espelho que o `piper.ttstool.com` usa. Depois de o
|
|
46
|
+
* `platform/pesados` passar a descer os modelos no primeiro carregamento, mediu-se o outro lado — o
|
|
47
|
+
* `@mintplex-labs/piper-tts-web@1.0.4` INSTALADO, lido do bundle e não do README — e ele busca em
|
|
48
|
+
* `diffusionstudio/piper-voices`, com uma guarda que recusa qualquer URL fora de `huggingface.co`.
|
|
49
|
+
*
|
|
50
|
+
* 🎯 ENDEREÇOS DIFERENTES SIGNIFICAM CACHE DIFERENTE: a Cache Storage é indexada pela URL do pedido, logo os
|
|
51
|
+
* 241 MB que desciam no primeiro dia NÃO ERAM LIDOS POR NINGUÉM, e a biblioteca descarregava tudo outra vez
|
|
52
|
+
* no dia em que a voz fosse pedida. Até 482 MB num link de escola para uma voz.
|
|
53
|
+
*
|
|
54
|
+
* 📏 E OS DOIS ESPELHOS SERVEM OS MESMOS BYTES — 63 201 294 para o `pt_BR-faber-medium`, caminhos idênticos,
|
|
55
|
+
* `Access-Control-Allow-Origin: *` nos dois. Medido nos dois no mesmo minuto, o que torna esta troca uma
|
|
56
|
+
* correcção de facto e não uma preferência entre fornecedores.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ A CLÁUSULA DO ADR-0114 NÃO SE MEXE: o host continua nomeado num ponto só. O que mudou foi QUAL, e a
|
|
59
|
+
* regra que decide isso passa a estar escrita para não voltar a divergir — **busca-se de onde o LEITOR lê**.
|
|
60
|
+
* No dia em que a engine for dona do runtime (ADR-0124 cláusula 2), o leitor passa a ser ela e é ela que
|
|
61
|
+
* escolhe; até lá, o único leitor é a porta do cartucho.
|
|
62
|
+
*/
|
|
63
|
+
export const HOST_DOS_MODELOS = 'https://huggingface.co/diffusionstudio/piper-voices/resolve/main/';
|
|
64
|
+
/**
|
|
65
|
+
* O CAMINHO DO MODELO, DERIVADO DO IDENTIFICADOR — e não uma segunda tabela.
|
|
66
|
+
*
|
|
67
|
+
* `pt_BR-faber-medium` diz tudo o que o caminho precisa: `pt/pt_BR/faber/medium/pt_BR-faber-medium.onnx`.
|
|
68
|
+
* 🎯 DERIVAR EM VEZ DE TABELAR é a decisão inteira desta função: uma tabela de caminhos ao lado da tabela
|
|
69
|
+
* de vozes seria o mesmo facto escrito duas vezes, e este repositório já pagou isso três vezes — o
|
|
70
|
+
* `DomQuery`, os rótulos de movimento reduzido, as chaves de armazenamento. Duas listas divergem, e
|
|
71
|
+
* divergem uma entrada de cada vez.
|
|
72
|
+
*
|
|
73
|
+
* ⚠️ Devolve `null` para um identificador que não tenha a forma esperada, em vez de montar um caminho
|
|
74
|
+
* torto: uma URL inventada dá 404 na escola, e um `null` dá para reportar antes de sair de casa.
|
|
75
|
+
*/
|
|
76
|
+
export function caminhoDoModelo(v) {
|
|
77
|
+
const partes = v.voice.split('-');
|
|
78
|
+
if (partes.length !== 3)
|
|
79
|
+
return null;
|
|
80
|
+
const [locale, nome, qualidade] = partes;
|
|
81
|
+
const idioma = locale.split('_')[0];
|
|
82
|
+
if (!idioma || !nome || !qualidade || idioma === locale)
|
|
83
|
+
return null;
|
|
84
|
+
return `${idioma}/${locale}/${nome}/${qualidade}/${v.voice}.onnx`;
|
|
85
|
+
}
|
|
86
|
+
/** O endereço completo do modelo. `null` quando o identificador não se deixa ler. */
|
|
87
|
+
export function urlDoModelo(v) {
|
|
88
|
+
const caminho = caminhoDoModelo(v);
|
|
89
|
+
return caminho === null ? null : HOST_DOS_MODELOS + caminho;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A CONFIGURAÇÃO da voz, que o motor lê junto com o modelo.
|
|
93
|
+
*
|
|
94
|
+
* 📌 `.onnx.json` e não um segundo caminho: é o mesmo ficheiro com outro sufixo, medido a responder 200 no
|
|
95
|
+
* mesmo sítio. Escrevê-lo como derivação mantém a regra de que o identificador é a única fonte.
|
|
96
|
+
*/
|
|
97
|
+
export function urlDaConfig(v) {
|
|
98
|
+
const url = urlDoModelo(v);
|
|
99
|
+
return url === null ? null : url + '.json';
|
|
100
|
+
}
|
|
101
|
+
export const estadoDe = (estados, v) => estados[v.voice] ?? 'ausente';
|
|
102
|
+
/**
|
|
103
|
+
* A ORDEM POR QUE SE BUSCAM: primeiro as do idioma corrente, depois as outras, cada grupo na ordem do
|
|
104
|
+
* catálogo. Só entram as que ainda não estão prontas nem em curso.
|
|
105
|
+
*
|
|
106
|
+
* ⚠️ O IDIOMA CORRENTE PRIMEIRO É A REGRA INTEIRA, e ela é sobre uma criança e não sobre eficiência: as
|
|
107
|
+
* outras são para «conforme interesse do jogador», mas a dela é a que ela precisa AGORA. Buscar em ordem de
|
|
108
|
+
* catálogo faria uma criança brasileira esperar por duas vozes inglesas na rede de uma escola.
|
|
109
|
+
*
|
|
110
|
+
* 📌 `falhou` VOLTA À FILA. Uma escola perde a rede a meio da manhã e recupera-a; uma voz marcada como
|
|
111
|
+
* falhada para sempre seria uma criança sem voz até alguém recarregar a página. Quem chama decide QUANDO
|
|
112
|
+
* tentar de novo — este módulo só diz que ainda há o que buscar.
|
|
113
|
+
*/
|
|
114
|
+
export function ordemDeBusca(estados, localeCorrente, catalogo = VOZES_NEURAIS) {
|
|
115
|
+
const porBuscar = catalogo.filter((v) => {
|
|
116
|
+
const e = estadoDe(estados, v);
|
|
117
|
+
return e === 'ausente' || e === 'falhou';
|
|
118
|
+
});
|
|
119
|
+
return [
|
|
120
|
+
...porBuscar.filter((v) => v.locale === localeCorrente),
|
|
121
|
+
...porBuscar.filter((v) => v.locale !== localeCorrente),
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* QUEM ESTÁ A FALAR, E POR QUE — e esta função é o gate 3 do ADR-0110 em forma de código.
|
|
126
|
+
*
|
|
127
|
+
* ⚠️ O DEFEITO QUE ELA IMPEDE TEM NOME NESTE REPOSITÓRIO: «um recuo que se apresenta como a coisa real é a
|
|
128
|
+
* família do `reflectTTS`». Uma engine que dissesse «voz neural» enquanto o modelo ainda desce faria um adulto
|
|
129
|
+
* concluir que a qualidade que ouve É a qualidade final — e desistir de esperar por uma coisa que já vinha a
|
|
130
|
+
* caminho. Dizer `recuo` e dizer PORQUÊ custa uma linha e é a diferença entre informar e enganar.
|
|
131
|
+
*
|
|
132
|
+
* 📌 A PRIMEIRA VOZ PRONTA DO IDIOMA, e nada mais: onde há duas prontas, esta função NÃO escolhe entre elas —
|
|
133
|
+
* devolve a primeira do catálogo porque tem de devolver alguma, e qual delas a criança prefere é a pergunta
|
|
134
|
+
* que o ADR-0110 deixou em aberto. Ver o cabeçalho.
|
|
135
|
+
*/
|
|
136
|
+
export function vozEmUso(estados, localeCorrente, catalogo = VOZES_NEURAIS) {
|
|
137
|
+
const doIdioma = catalogo.filter((v) => v.locale === localeCorrente);
|
|
138
|
+
if (doIdioma.length === 0)
|
|
139
|
+
return { tipo: 'recuo', porque: 'sem-voz-para-o-idioma' };
|
|
140
|
+
const pronta = doIdioma.find((v) => estadoDe(estados, v) === 'pronta');
|
|
141
|
+
if (pronta)
|
|
142
|
+
return { tipo: 'neural', voice: pronta.voice };
|
|
143
|
+
// A PRECEDÊNCIA DOS TRÊS RECUOS, e cada degrau tem a sua razão:
|
|
144
|
+
//
|
|
145
|
+
// ⚠️ «A BUSCAR» GANHA DE «FALHOU» quando as duas coexistem: enquanto alguma ainda vem a caminho, dizer
|
|
146
|
+
// «falhou» seria anunciar uma derrota que ainda não aconteceu, e um adulto desligaria a espera cedo.
|
|
147
|
+
// ⚠️ E «AUSENTE» NÃO SE DISFARÇA DE «A BUSCAR», que era como esta função estava escrita à primeira. Nada
|
|
148
|
+
// começou é diferente de está a caminho: o primeiro é uma engine que ainda não pediu — possivelmente
|
|
149
|
+
// porque ninguém a mandou —, e chamar-lhe «a buscar» esconderia um buscador que nunca arrancou atrás de
|
|
150
|
+
// uma frase tranquilizadora. É o mesmo defeito de forma que o `neuralDisponivel` já custou à #91.
|
|
151
|
+
if (doIdioma.some((v) => estadoDe(estados, v) === 'a-buscar'))
|
|
152
|
+
return { tipo: 'recuo', porque: 'a-buscar' };
|
|
153
|
+
if (doIdioma.some((v) => estadoDe(estados, v) === 'falhou'))
|
|
154
|
+
return { tipo: 'recuo', porque: 'falhou' };
|
|
155
|
+
return { tipo: 'recuo', porque: 'ausente' };
|
|
156
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type EstadosDasVozes, type VozNeural } from './voice-plan.js';
|
|
3
|
+
import type { RelatorioPesado } from './pesados.js';
|
|
4
|
+
/**
|
|
5
|
+
* O ESTADO DE CADA VOZ A PARTIR DO QUE O BUSCADOR RELATOU.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ `emCurso` NÃO É COSMÉTICA, E É O QUE IMPEDE ESTA PONTE DE MENTIR NOS DOIS SENTIDOS. O relatório só
|
|
8
|
+
* contém o que JÁ resolveu, logo uma voz por relatar é ambígua: pode estar a caminho ou nunca ter sido
|
|
9
|
+
* pedida. O `voice-plan` recusa-se a confundir as duas por escrito — «ausente não se disfarça de a buscar»,
|
|
10
|
+
* porque isso esconderia um buscador que nunca arrancou atrás de uma frase tranquilizadora — e a única coisa
|
|
11
|
+
* que desfaz a ambiguidade é quem chamou saber se a descarga ainda corre.
|
|
12
|
+
*
|
|
13
|
+
* 📌 E o par contrário importa igualmente: com a descarga TERMINADA, uma voz por relatar é `ausente` e nunca
|
|
14
|
+
* `a-buscar`, senão a interface prometeria para sempre uma coisa que já não vem.
|
|
15
|
+
*/
|
|
16
|
+
export declare function estadosDasVozes(relatorio: readonly RelatorioPesado[], opcoes?: {
|
|
17
|
+
readonly emCurso?: boolean;
|
|
18
|
+
}, catalogo?: readonly VozNeural[]): EstadosDasVozes;
|
|
19
|
+
/**
|
|
20
|
+
* AS IDS DAS COISAS PESADAS QUE SÃO VOZ — para quem queira descê-las primeiro, sem adivinhar o formato da id.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ Existe porque a alternativa é o consumidor escrever `p.id.startsWith('voz:')` no ponto de uso, e aí o
|
|
23
|
+
* formato da id passa a ser contrato público sem nunca ter sido decidido como tal.
|
|
24
|
+
*/
|
|
25
|
+
export declare const idsDasVozes: (catalogo?: readonly VozNeural[]) => string[];
|
|
26
|
+
/** Reexportado para quem faz a ponte não ter de importar de dois sítios para responder a uma pergunta. */
|
|
27
|
+
export { vozEmUso, estadoDe } from './voice-plan.js';
|
|
28
|
+
export type { VozEmUso, EstadosDasVozes } from './voice-plan.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/vozes-prontas.ts — A PONTE ENTRE O QUE DESCEU E O QUE A CRIANÇA OUVE (ADR-0110, gates 3 e 4).
|
|
3
|
+
//
|
|
4
|
+
// ========================= POR QUE ESTE MÓDULO EXISTE =========================
|
|
5
|
+
// 📏 MEDIDO EM 2026-09-09: `vozEmUso` — a função que o ADR-0110 chama «o gate 3 em forma de código» — tinha
|
|
6
|
+
// ZERO leitores em produção, e NADA no repositório produzia um `EstadosDasVozes`. O buscador que nasceu hoje
|
|
7
|
+
// relata `RelatorioPesado[]`. São DUAS linguagens para o mesmo facto, e nenhuma falava com a outra.
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ ISSO NÃO É UM ENCAIXE QUE FALTAVA, É A FORMA DE DEFEITO QUE ESTE REPOSITÓRIO PERSEGUE: um modelo aferido
|
|
10
|
+
// e correcto, sem ninguém a lê-lo, ao lado de um mecanismo que produz a mesma verdade noutro vocabulário. O
|
|
11
|
+
// `render/viz-axes` esteve exactamente assim — «é ÓRFÃO» — e o que isso custou foi a composição dos dois eixos
|
|
12
|
+
// visuais a existir no papel e não na tela.
|
|
13
|
+
//
|
|
14
|
+
// ========================= 🎯 A REGRA QUE ESTE MÓDULO CARREGA, E É UMA SÓ =========================
|
|
15
|
+
// **UMA VOZ SÓ ESTÁ PRONTA QUANDO OS DOIS FICHEIROS DESCERAM.** O piper recusa-se a falar sem o `.onnx.json`,
|
|
16
|
+
// então uma voz com o modelo e sem a configuração é uma voz que NÃO FALA — e marcá-la `pronta` faria a engine
|
|
17
|
+
// anunciar «voz neural» e ficar muda. É pior do que a voz em falta, porque a primeira parece resolvida, e é
|
|
18
|
+
// literalmente a família do `reflectTTS` que o ADR-0110 nomeia ao pedir o gate 3.
|
|
19
|
+
import { VOZES_NEURAIS } from './voice-plan.js';
|
|
20
|
+
/** As duas ids que o catálogo dá a uma voz. Derivadas, não tabeladas — a tabela vive no catálogo. */
|
|
21
|
+
const idsDe = (v) => [`voz:${v.voice}`, `voz:${v.voice}:cfg`];
|
|
22
|
+
/**
|
|
23
|
+
* O ESTADO DE CADA VOZ A PARTIR DO QUE O BUSCADOR RELATOU.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ `emCurso` NÃO É COSMÉTICA, E É O QUE IMPEDE ESTA PONTE DE MENTIR NOS DOIS SENTIDOS. O relatório só
|
|
26
|
+
* contém o que JÁ resolveu, logo uma voz por relatar é ambígua: pode estar a caminho ou nunca ter sido
|
|
27
|
+
* pedida. O `voice-plan` recusa-se a confundir as duas por escrito — «ausente não se disfarça de a buscar»,
|
|
28
|
+
* porque isso esconderia um buscador que nunca arrancou atrás de uma frase tranquilizadora — e a única coisa
|
|
29
|
+
* que desfaz a ambiguidade é quem chamou saber se a descarga ainda corre.
|
|
30
|
+
*
|
|
31
|
+
* 📌 E o par contrário importa igualmente: com a descarga TERMINADA, uma voz por relatar é `ausente` e nunca
|
|
32
|
+
* `a-buscar`, senão a interface prometeria para sempre uma coisa que já não vem.
|
|
33
|
+
*/
|
|
34
|
+
export function estadosDasVozes(relatorio, opcoes = {}, catalogo = VOZES_NEURAIS) {
|
|
35
|
+
const porId = new Map(relatorio.map((r) => [r.id, r]));
|
|
36
|
+
const saida = {};
|
|
37
|
+
for (const v of catalogo) {
|
|
38
|
+
const partes = idsDe(v).map((id) => porId.get(id));
|
|
39
|
+
if (partes.some((p) => p && (p.estado === 'falhou' || p.estado === 'sem-fonte'))) {
|
|
40
|
+
// 📌 UMA METADE FALHADA CHEGA PARA A VOZ INTEIRA FALHAR, e é o mesmo argumento da regra do cabeçalho
|
|
41
|
+
// visto do outro lado: sem os dois ficheiros não há fala nenhuma, logo não há meia voz para oferecer.
|
|
42
|
+
saida[v.voice] = 'falhou';
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (partes.every((p) => p && (p.estado === 'baixado' || p.estado === 'ja-tinha'))) {
|
|
46
|
+
saida[v.voice] = 'pronta';
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
saida[v.voice] = opcoes.emCurso ? 'a-buscar' : 'ausente';
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze(saida);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* AS IDS DAS COISAS PESADAS QUE SÃO VOZ — para quem queira descê-las primeiro, sem adivinhar o formato da id.
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ Existe porque a alternativa é o consumidor escrever `p.id.startsWith('voz:')` no ponto de uso, e aí o
|
|
57
|
+
* formato da id passa a ser contrato público sem nunca ter sido decidido como tal.
|
|
58
|
+
*/
|
|
59
|
+
export const idsDasVozes = (catalogo = VOZES_NEURAIS) => catalogo.flatMap((v) => [...idsDe(v)]);
|
|
60
|
+
/** Reexportado para quem faz a ponte não ter de importar de dois sítios para responder a uma pergunta. */
|
|
61
|
+
export { vozEmUso, estadoDe } from './voice-plan.js';
|
|
@@ -65,7 +65,7 @@ interface PowerupWithSprite {
|
|
|
65
65
|
* `sq`/`sqT` são o squash&stretch (amplitude e relógio, escritos por render/fx.setSquash); `easy` desenha a
|
|
66
66
|
* hitbox de coleta tolerante translúcida; `viz` é o modo de visão, que recolore por viewport.
|
|
67
67
|
*/
|
|
68
|
-
export type DrawPlayer = AnimPlayer & PlayerView<'i' | 'x' | 'y' | 'facing' | 'hurtTimer' | 'sq' | 'sqT' | 'easy' | '
|
|
68
|
+
export type DrawPlayer = AnimPlayer & PlayerView<'i' | 'x' | 'y' | 'facing' | 'hurtTimer' | 'sq' | 'sqT' | 'easy' | 'visual' | 'sprite'>;
|
|
69
69
|
/** Flags de Movimento Reduzido que o DESENHO consulta (o objeto `rm` do game.js tem mais chaves). */
|
|
70
70
|
export interface ReducedMotion {
|
|
71
71
|
items?: boolean;
|
package/dist-pkg/render/draw.js
CHANGED
|
@@ -67,7 +67,7 @@ const rnd = rngDecoracao.rnd;
|
|
|
67
67
|
import { JUICE, easeOut3, shakeAmp, drawFx } from './fx.js';
|
|
68
68
|
import { criarCamera } from './camera.js';
|
|
69
69
|
import { drawCane, drawRunCane, drawChair } from './wheelchair-sprites.js';
|
|
70
|
-
import {
|
|
70
|
+
import { chaveDeTextura, ehBaixaVisao } from './viz-axes.js';
|
|
71
71
|
// (`game/powerups` e `game/coin-spawning` SAÍRAM daqui no item 19 — ver o bloco "ITENS DECLARADOS" abaixo.)
|
|
72
72
|
import { drawWeather } from './weather.js';
|
|
73
73
|
import { choosePlayerFrame } from './player-anim.js';
|
|
@@ -121,8 +121,11 @@ export function initDraw(ctx) {
|
|
|
121
121
|
dt, dir, wheelchair: ctx.isWheelchair(), held: ctx.held, rnd, tex: ctx.playerTextures(),
|
|
122
122
|
});
|
|
123
123
|
// solo/default; no MP o drawFrame troca a textura por viewport (applySharedTextures)
|
|
124
|
+
// ⚠️ `chaveDeTextura` E NÃO A ASSINATURA DE `playerVizTex`. Medido na etapa 0 da #104: aquela função só
|
|
125
|
+
// age quando existe `DIRECT_CFG[mode]`, e devolve a textura como veio para todo o resto. Então o que
|
|
126
|
+
// muda é o CHAMADOR — a metade do estado que interessa à textura — e a porta fica onde estava.
|
|
124
127
|
if (pl.sprite)
|
|
125
|
-
pl.sprite.texture = ctx.playerVizTex(tx, pl.
|
|
128
|
+
pl.sprite.texture = ctx.playerVizTex(tx, chaveDeTextura(pl.visual));
|
|
126
129
|
return tx;
|
|
127
130
|
}
|
|
128
131
|
/* ===================== o quadro ===================== */
|
|
@@ -187,12 +190,19 @@ export function initDraw(ctx) {
|
|
|
187
190
|
}
|
|
188
191
|
else {
|
|
189
192
|
// Otimização: se TODOS estão no mesmo modo (caso comum), troca as texturas UMA vez; senão, por viewport.
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
// ⚠️ A COMPARAÇÃO PASSOU A SER PELA CHAVE DE TEXTURA, e isso é output-preservador e mais barato ao
|
|
194
|
+
// mesmo tempo. `applySharedTextures` só usa o modo para escolher TEXTURA (`worldTexFor`,
|
|
195
|
+
// `parallaxTexFor`, `treeTexFor`, `spriteTexFor`, `pupTexFor`), e todas devolvem a base para o que não
|
|
196
|
+
// está em `DIRECT_CFG` — conferido em `high-contrast.worldTexFor`. Antes, dois jogadores em `normal` e
|
|
197
|
+
// `fix-deuter` contavam como modos DIFERENTES e disparavam uma re-aplicação de texturas que produzia
|
|
198
|
+
// exactamente as mesmas texturas. Agora contam como iguais, porque para a textura eles são.
|
|
199
|
+
const v0 = chaveDeTextura(PLS[0].visual);
|
|
200
|
+
const allSame = PLS.every((p) => chaveDeTextura(p.visual) === v0);
|
|
201
|
+
const anyOverlay = PLS.some((p) => ehBaixaVisao(p.visual));
|
|
192
202
|
if (allSame)
|
|
193
203
|
ctx.applySharedTextures(v0);
|
|
194
204
|
for (let i = 0, n = ctx.getNumPlayers(); i < n; i++) {
|
|
195
|
-
const viz = PLS[i].
|
|
205
|
+
const viz = chaveDeTextura(PLS[i].visual);
|
|
196
206
|
if (!allSame)
|
|
197
207
|
ctx.applySharedTextures(viz); // só troca por viewport quando os modos diferem
|
|
198
208
|
const itens2 = ctx.getItemSprites();
|
|
@@ -80,6 +80,23 @@ export declare function proximaCorrecao(v: VisualState): VisualState;
|
|
|
80
80
|
* uma cegueira simulada apaga a tela inteira, e nesse instante o tema não muda nada do que ela percebe.
|
|
81
81
|
*/
|
|
82
82
|
export declare function chaveDeTextura(v: VisualState): string;
|
|
83
|
+
/**
|
|
84
|
+
* A CHAVE ÚNICA que melhor descreve este estado no vocabulário ANTIGO — para quem só sabe ler uma.
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ NÃO É A `chaveDeTextura`, e a diferença custou um gate vermelho para aparecer. A de textura devolve
|
|
87
|
+
* `normal` para uma correção de cor, porque correção não muda textura nenhuma — e usá-la como espelho faria
|
|
88
|
+
* uma criança em `fix-deuter` passar a gravar `'normal'` na chave legada. **Um leitor antigo perderia a
|
|
89
|
+
* correção dela**, que é exactamente o estrago que a migração inteira existe para não cometer.
|
|
90
|
+
*
|
|
91
|
+
* A ordem é simulação → tema → correção → padrão, e ela preserva TODO ajuste que já existia: nenhum estado
|
|
92
|
+
* antigo tinha dois eixos, então nenhum deles perde nada aqui.
|
|
93
|
+
*
|
|
94
|
+
* ⚠️ O ÚNICO CASO COM PERDA É O NOVO — `hc7 + fix-deuter` só cabe como uma das duas metades, e a escolhida é
|
|
95
|
+
* o tema. Não há regressão possível nisso: esse estado NÃO EXISTIA antes, e um leitor que só entende uma
|
|
96
|
+
* chave nunca soube exprimi-lo. Quem quiser as duas metades lê a chave nova, que existe precisamente para
|
|
97
|
+
* isso.
|
|
98
|
+
*/
|
|
99
|
+
export declare function chaveLegada(v: VisualState): string;
|
|
83
100
|
/**
|
|
84
101
|
* Traduz o valor salvo. Aceita o antigo (string) e o novo (objeto), e devolve sempre um estado válido.
|
|
85
102
|
*
|
|
@@ -122,6 +122,25 @@ export function proximaCorrecao(v) {
|
|
|
122
122
|
export function chaveDeTextura(v) {
|
|
123
123
|
return v.simulacao ?? temaDireto(v) ?? 'normal';
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* A CHAVE ÚNICA que melhor descreve este estado no vocabulário ANTIGO — para quem só sabe ler uma.
|
|
127
|
+
*
|
|
128
|
+
* ⚠️ NÃO É A `chaveDeTextura`, e a diferença custou um gate vermelho para aparecer. A de textura devolve
|
|
129
|
+
* `normal` para uma correção de cor, porque correção não muda textura nenhuma — e usá-la como espelho faria
|
|
130
|
+
* uma criança em `fix-deuter` passar a gravar `'normal'` na chave legada. **Um leitor antigo perderia a
|
|
131
|
+
* correção dela**, que é exactamente o estrago que a migração inteira existe para não cometer.
|
|
132
|
+
*
|
|
133
|
+
* A ordem é simulação → tema → correção → padrão, e ela preserva TODO ajuste que já existia: nenhum estado
|
|
134
|
+
* antigo tinha dois eixos, então nenhum deles perde nada aqui.
|
|
135
|
+
*
|
|
136
|
+
* ⚠️ O ÚNICO CASO COM PERDA É O NOVO — `hc7 + fix-deuter` só cabe como uma das duas metades, e a escolhida é
|
|
137
|
+
* o tema. Não há regressão possível nisso: esse estado NÃO EXISTIA antes, e um leitor que só entende uma
|
|
138
|
+
* chave nunca soube exprimi-lo. Quem quiser as duas metades lê a chave nova, que existe precisamente para
|
|
139
|
+
* isso.
|
|
140
|
+
*/
|
|
141
|
+
export function chaveLegada(v) {
|
|
142
|
+
return v.simulacao ?? temaDireto(v) ?? filtroChave(v) ?? 'normal';
|
|
143
|
+
}
|
|
125
144
|
/* ===================== A MIGRAÇÃO ===================== */
|
|
126
145
|
//
|
|
127
146
|
// ⚠️ ELA NÃO É OPCIONAL E VEM ANTES DA PRIMEIRA LEITURA DA FORMA NOVA. O ajuste salvo guarda o valor único
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
import { type VizMode } from './viz-modes.js';
|
|
3
|
+
import { type Tema, type Correcao, type VisualState } from './viz-axes.js';
|
|
3
4
|
import type { DomQuery } from '../core/dom-query.js';
|
|
4
5
|
import type { ComFiltro, ComTextura, Visivel, DesenhoComCirculo, AplicarFiltroCss, AlcanceDoFiltro, AplicarAltoContrasteNoDom } from './port.js';
|
|
5
6
|
/**
|
|
@@ -10,6 +11,24 @@ import type { ComFiltro, ComTextura, Visivel, DesenhoComCirculo, AplicarFiltroCs
|
|
|
10
11
|
export declare function alcanceDoModo(mode: string): AlcanceDoFiltro;
|
|
11
12
|
/** Modo por chave, com o fallback do original: chave desconhecida (ou nula) CAI em `normal`. */
|
|
12
13
|
export declare function resolveViz(key: string | null | undefined): VizMode;
|
|
14
|
+
/**
|
|
15
|
+
* O ESTADO VISUAL GUARDADO deste jogador, de qualquer das duas formas (issue #104).
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ ESTA FUNÇÃO É A CAIXA «um ajuste salvo antes da divisão restaura o mesmo estado visível» da definition
|
|
18
|
+
* of done, e a ordem das duas leituras é a decisão inteira:
|
|
19
|
+
*
|
|
20
|
+
* 1. a chave NOVA (`visualP`), que é a única que sabe dizer dois eixos;
|
|
21
|
+
* 2. na falta dela, a chave VELHA (`vizP`), que guarda a string única — e é aqui que mora o ajuste de toda
|
|
22
|
+
* criança que já jogou este jogo antes de hoje;
|
|
23
|
+
* 3. na falta das duas, o padrão.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ O RECUO NÃO É ZELO: sem ele, a primeira sessão depois da actualização apagaria o modo visual que ela
|
|
26
|
+
* escolheu — e quem escolheu `fix-deuter` ou `hc-direto-7` escolheu-o porque enxerga assim. É a diferença
|
|
27
|
+
* entre migrar e recomeçar.
|
|
28
|
+
*
|
|
29
|
+
* `migrarVisual` aceita as duas formas e é idempotente, então isto pode correr quantas vezes for preciso.
|
|
30
|
+
*/
|
|
31
|
+
export declare function lerVisualGuardado(i: number): VisualState;
|
|
13
32
|
/** É um dos três níveis de Renderização Direta (alto contraste)? Mesmo teste do original: `!!DIRECT_CFG[mode]`. */
|
|
14
33
|
export declare function isDirectMode(mode: string): boolean;
|
|
15
34
|
/** Filtro CSS da canvas no SOLO: simulação/correção de visão + realce L→Q, compostos e sem vazios. */
|
|
@@ -44,6 +63,7 @@ interface ClassListHost {
|
|
|
44
63
|
}
|
|
45
64
|
interface Pl {
|
|
46
65
|
viz: string;
|
|
66
|
+
visual?: VisualState;
|
|
47
67
|
sprite?: Textured | null;
|
|
48
68
|
_tx?: unknown;
|
|
49
69
|
}
|
|
@@ -100,7 +120,7 @@ export interface VizSettersCtx {
|
|
|
100
120
|
setFrontDim: (on: boolean) => void;
|
|
101
121
|
rebuildExtras: () => void;
|
|
102
122
|
rebuildCoins: () => void;
|
|
103
|
-
setModoCego
|
|
123
|
+
setModoCego?: (on: boolean) => void;
|
|
104
124
|
hideTouchControls: (reason?: string) => void;
|
|
105
125
|
reflectVizButtons: () => void;
|
|
106
126
|
renderVisualPanel: () => void;
|
|
@@ -115,8 +135,18 @@ export interface VizSettersApi {
|
|
|
115
135
|
applyVpFilters(): void;
|
|
116
136
|
/** Troca o modo de UM jogador: persiste, invalida o render estático e reaplica pelo caminho certo. */
|
|
117
137
|
setPlayerViz(i: number, mode: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* O ESTADO INTEIRO de um jogador, e os DOIS escritores por eixo (#104).
|
|
140
|
+
*
|
|
141
|
+
* ⚠️ Os dois de eixo existem separados porque é isso que um painel de dois controles precisa: mexer no
|
|
142
|
+
* TEMA sem tocar na correção, e vice-versa. Enquanto havia um campo só, «mexer num» significava
|
|
143
|
+
* inevitavelmente «apagar o outro» — e era o defeito, não a API.
|
|
144
|
+
*/
|
|
145
|
+
setVisualDoJogador(i: number, v: VisualState): void;
|
|
146
|
+
setTemaDoJogador(i: number, tema: Tema): void;
|
|
147
|
+
setCorrecaoDoJogador(i: number, correcao: Correcao): void;
|
|
118
148
|
/** Caminho SOLO: filtro CSS na canvas + texturas globais + overlay DOM + bolinha. */
|
|
119
|
-
applyVizGlobal(
|
|
149
|
+
applyVizGlobal(v: VisualState): void;
|
|
120
150
|
/** Reaplica tudo depois de uma mudança estrutural (cenário, nº de telas). */
|
|
121
151
|
reapplyVizAll(): void;
|
|
122
152
|
/** Bolinha global (#viz-indicator) para um `kind`. */
|
|
@@ -125,6 +155,8 @@ export interface VizSettersApi {
|
|
|
125
155
|
rebakeDirect(): void;
|
|
126
156
|
/** Grupo de rádios de modos visuais nos painéis (visual/empatia). */
|
|
127
157
|
renderVizGroup(listSel: string, tabsSel: string, modes: readonly VizMode[]): void;
|
|
158
|
+
/** Os DOIS eixos do painel visual (#104). Irmão do de cima — ver a nota na implementação. */
|
|
159
|
+
renderEixosVisuais(listSel: string, tabsSel: string): void;
|
|
128
160
|
}
|
|
129
161
|
export declare function initVizSetters(ctx: VizSettersCtx): VizSettersApi;
|
|
130
162
|
export {};
|