@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,177 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/pesados-catalogo.ts — O QUE É PESADO, DE ONDE VEM, E QUANTO PESA.
|
|
3
|
+
//
|
|
4
|
+
// 📌 SEPARADO DO BUSCADOR de propósito: a lista é DADO e muda por decisão registada; o buscador é regra e
|
|
5
|
+
// muda por defeito encontrado. Juntos, cada correcção de uma URL mexeria no ficheiro que decide a ordem das
|
|
6
|
+
// descargas, e cada correcção da ordem mexeria na lista que um registo governa.
|
|
7
|
+
//
|
|
8
|
+
// ========================= 🔴 A PRIMEIRA VERSÃO DESTE FICHEIRO COMETEU O DEFEITO QUE ELE SERVE =========================
|
|
9
|
+
// Ela escrevia o host à mão (`const HF = 'https://huggingface.co/…'`), a lista das quatro vozes outra vez, e
|
|
10
|
+
// uma segunda derivação do caminho `idioma/locale/nome/qualidade/…`. As três coisas JÁ EXISTEM no
|
|
11
|
+
// `platform/voice-plan.ts`, que o ADR-0114 designou como o sítio ÚNICO onde o host é nomeado — e cujo próprio
|
|
12
|
+
// comentário nomeia as três vezes que este repositório pagou por tabelas duplicadas (o `DomQuery`, os rótulos
|
|
13
|
+
// de movimento reduzido, as chaves de armazenamento).
|
|
14
|
+
//
|
|
15
|
+
// 📏 QUEM APANHOU FOI O INVENTÁRIO, e não pela duplicação: o `nada-vem-de-fora` recusou uma URL nova sem razão
|
|
16
|
+
// escrita. A URL era só a PROVA; declará-la teria feito o gate ficar verde com a duplicação lá dentro. ⚠️ É a
|
|
17
|
+
// razão de a saída ser APAGAR a cópia e não desculpá-la — declarar duas vezes o mesmo endereço é precisamente
|
|
18
|
+
// a cláusula que o `nada-de-cdn-a-mao` conta com um número.
|
|
19
|
+
import { VOZES_NEURAIS, urlDoModelo, urlDaConfig } from './voice-plan.js';
|
|
20
|
+
/** O nome da Cache Storage. Versionado: mudar o conteúdo do catálogo não deve servir bytes velhos. */
|
|
21
|
+
export const CACHE_PESADOS = 'incl-pesados-v1';
|
|
22
|
+
/**
|
|
23
|
+
* O PESO DE CADA VOZ, MEDIDO EM 2026-09-09 e não estimado — `Content-Length` do modelo e corpo da configuração.
|
|
24
|
+
*
|
|
25
|
+
* 📌 O PESO MORA AQUI E O ENDEREÇO MORA NO `voice-plan`, e a divisão não é arrumação: o `voice-plan` responde
|
|
26
|
+
* «que vozes a engine garante e onde estão», que é decisão do ADR-0110; isto responde «quanto custa descê-las
|
|
27
|
+
* hoje», que é uma medição com data e que muda quando o fornecedor recomprime um ficheiro.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ UMA VOZ NOVA NO `voice-plan` SEM MEDIÇÃO AQUI FICA SEM `bytes`, e o aviso de «faltam N MB» passaria a
|
|
30
|
+
* sub-reportar em silêncio. É por isso que o gate exige `bytes > 0` em toda entrada de voz COM url: o buraco
|
|
31
|
+
* é pequeno e mudo, que é a forma de defeito que este repositório persegue.
|
|
32
|
+
*/
|
|
33
|
+
const PESO_MEDIDO = Object.freeze({
|
|
34
|
+
'pt_BR-faber-medium': { modelo: 63_201_294, config: 4_855 },
|
|
35
|
+
'en_US-ryan-medium': { modelo: 63_201_294, config: 4_883 },
|
|
36
|
+
'en_US-amy-medium': { modelo: 63_201_294, config: 4_882 },
|
|
37
|
+
'es_MX-claude-high': { modelo: 63_122_309, config: 4_963 },
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* ⚠️ O `urlDoModelo` devolve `null` para um identificador que não se deixe ler, e a razão viaja com a entrada
|
|
41
|
+
* em vez de a entrada desaparecer. Uma URL inventada dá 404 na escola; um `null` COM razão dá para reportar
|
|
42
|
+
* antes de sair de casa, que é a regra que o `voice-plan` já escreve na própria função.
|
|
43
|
+
*/
|
|
44
|
+
const RAZAO_ID_TORTO = 'o identificador da voz não tem a forma `locale-nome-qualidade`, então o caminho no '
|
|
45
|
+
+ 'fornecedor não se deixa derivar. Corrija o identificador no `platform/voice-plan.ts`.';
|
|
46
|
+
/**
|
|
47
|
+
* AS DUAS ENTRADAS DE UMA VOZ. ⚠️ SÃO DUAS E NÃO UMA: o `.onnx` é o modelo e o `.onnx.json` é a configuração,
|
|
48
|
+
* e o piper recusa-se a falar sem a segunda. Um catálogo que só trouxesse a primeira produziria uma voz
|
|
49
|
+
* «baixada» que não fala — que é pior do que uma voz em falta, porque a primeira parece resolvida.
|
|
50
|
+
*/
|
|
51
|
+
function entradasDaVoz(v) {
|
|
52
|
+
const peso = PESO_MEDIDO[v.voice];
|
|
53
|
+
return [
|
|
54
|
+
{ id: `voz:${v.voice}`, url: urlDoModelo(v), bytes: peso?.modelo, porQueNaoTemFonte: RAZAO_ID_TORTO },
|
|
55
|
+
{ id: `voz:${v.voice}:cfg`, url: urlDaConfig(v), bytes: peso?.config, porQueNaoTemFonte: RAZAO_ID_TORTO },
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* O RUNTIME DE VISÃO — **MediaPipe**, decidido pelo Dev em 2026-09-09 (ADR-0124): «piper-tts, mediapipe
|
|
60
|
+
* (webgazer não), e LPCP: devem acompanhar a engine».
|
|
61
|
+
*
|
|
62
|
+
* 🔴 ESTA ENTRADA DIZIA «a #129 ainda não escolheu o fornecedor» DEPOIS DE ELE TER ESCOLHIDO, e a linha
|
|
63
|
+
* sobreviveu ao registo que a contradizia. Não era só trabalho em falta: era uma afirmação FALSA a dirigir
|
|
64
|
+
* quem a lesse para uma issue já fechada. O Dev teve de perguntar três vezes.
|
|
65
|
+
*
|
|
66
|
+
* 📏 MEDIDO EM 2026-09-09, como as vozes e no mesmo minuto: os três ficheiros respondem 200 em jsDelivr com
|
|
67
|
+
* `Access-Control-Allow-Origin: *`, na versão FIXADA — 155 439 + 323 377 + 11 756 954 bytes.
|
|
68
|
+
*
|
|
69
|
+
* ⚠️ CDN FIXADA É PERMITIDA E O ADR-0116 DIZ PORQUÊ: o que o pilar 8 proíbe é depender da rede DEPOIS do
|
|
70
|
+
* primeiro dia. Isto desce na INSTALAÇÃO, com o resto — e é a diferença inteira para o WebGazer, que busca
|
|
71
|
+
* quando a criança liga o controle por olhar, logo a máquina que nunca o ligou fica sem ele para sempre.
|
|
72
|
+
* 📌 A versão vai na URL, que é o que o `check:precache` exige de qualquer entrada externa: bytes diferentes
|
|
73
|
+
* chegam por endereço diferente, e uma entrada fixada nunca congela.
|
|
74
|
+
*
|
|
75
|
+
* 🎯 SÃO OS TRÊS FICHEIROS E NÃO SÓ O `.wasm`: o `vision_bundle.mjs` é quem o carrega e o
|
|
76
|
+
* `vision_wasm_internal.js` é a cola do Emscripten. Baixar o wasm sozinho é a mesma armadilha do `.onnx` sem
|
|
77
|
+
* o `.onnx.json` — uma coisa «baixada» que não corre.
|
|
78
|
+
*
|
|
79
|
+
* ⬜ O que continua por fazer é a FIAÇÃO (issue #11): estes bytes descem e ainda ninguém os lê. O
|
|
80
|
+
* `tests/o-que-desce-tem-quem-leia.node.test.js` é onde essa dívida está declarada.
|
|
81
|
+
*/
|
|
82
|
+
const MP = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@1.0.1';
|
|
83
|
+
const MP_MODELOS = 'https://storage.googleapis.com/mediapipe-models';
|
|
84
|
+
/**
|
|
85
|
+
* 🔴 A PRIMEIRA VERSÃO DESTA LISTA TRAZIA O RUNTIME E NENHUM MODELO, e o Dev apanhou-o ao perguntar o que
|
|
86
|
+
* tinha ficado de fora. 11,7 MB de WebAssembly sem um `.task` não reconhecem coisa nenhuma — é o `.onnx` sem
|
|
87
|
+
* o `.onnx.json` outra vez, no ficheiro que escreve essa lição doze linhas acima.
|
|
88
|
+
*
|
|
89
|
+
* 📏 MEDIDOS EM 2026-09-09, todos 200 com CORS aberto. `float16` e não `float32`: metade do peso, e a precisão
|
|
90
|
+
* que se perde é irrelevante para dizer onde está um íris num ecrã de 320×180.
|
|
91
|
+
*/
|
|
92
|
+
const MEDIAPIPE = Object.freeze([
|
|
93
|
+
{ id: 'visao:runtime', url: `${MP}/vision_bundle.mjs`, bytes: 155_439 },
|
|
94
|
+
{ id: 'visao:runtime:cola', url: `${MP}/wasm/vision_wasm_internal.js`, bytes: 323_377 },
|
|
95
|
+
{ id: 'visao:runtime:wasm', url: `${MP}/wasm/vision_wasm_internal.wasm`, bytes: 11_756_954 },
|
|
96
|
+
{ id: 'visao:modelo:rosto', url: `${MP_MODELOS}/face_landmarker/face_landmarker/float16/1/face_landmarker.task`, bytes: 3_758_596 },
|
|
97
|
+
{ id: 'visao:modelo:gestos', url: `${MP_MODELOS}/gesture_recognizer/gesture_recognizer/float16/1/gesture_recognizer.task`, bytes: 8_373_440 },
|
|
98
|
+
{ id: 'visao:modelo:maos', url: `${MP_MODELOS}/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`, bytes: 7_819_105 },
|
|
99
|
+
]);
|
|
100
|
+
/**
|
|
101
|
+
* O WEBGAZER — **volta em 2026-09-09, ao lado do MediaPipe** (ADR-0132): «Traga o WebGazer de volta. Vamos
|
|
102
|
+
* usar ambos.»
|
|
103
|
+
*
|
|
104
|
+
* 🎯 ELES NÃO SE SOBREPÕEM ONDE IMPORTA, e é essa medição que produziu a decisão: o MediaPipe diz ONDE O ÍRIS
|
|
105
|
+
* ESTÁ — `FACE_LANDMARKS_LEFT_IRIS` são conjuntos de conexões sobre marcos —, e o WebGazer diz PARA ONDE A
|
|
106
|
+
* CRIANÇA OLHA NO ECRÃ, que é um modelo de regressão com calibração. Nenhuma das quinze tarefas do
|
|
107
|
+
* `tasks-vision` faz a segunda.
|
|
108
|
+
*
|
|
109
|
+
* ⚠️ E O DEFEITO DELE NUNCA FOI O FORNECEDOR: era ser PREGUIÇOSO. Buscado quando a criança liga o controle
|
|
110
|
+
* por olhar, a máquina que nunca o ligou fica sem ele, e numa escola sem rede não acontece nada — sem erro e
|
|
111
|
+
* sem explicação. Aqui desce na INSTALAÇÃO com tudo o resto, e o defeito desaparece com a capacidade intacta.
|
|
112
|
+
* 📌 Fica a dívida que o ADR-0132 nomeia: o `<script src>` do `ui/webcam.ts` tem de sair no mesmo commit em
|
|
113
|
+
* que a fiação o ler daqui, senão passam a existir dois caminhos para o mesmo ficheiro.
|
|
114
|
+
*/
|
|
115
|
+
const WEBGAZER = Object.freeze([
|
|
116
|
+
{ id: 'visao:olhar', url: 'https://webgazer.cs.brown.edu/webgazer.js', bytes: 1_895_169 },
|
|
117
|
+
]);
|
|
118
|
+
/**
|
|
119
|
+
* O RUNTIME DE VOZ — **piper**, decidido no ADR-0127, e o Dev disse para que serve: «PiperTTS é o que será
|
|
120
|
+
* usado para ler para o usuário. Precisa ser carregado com a engine».
|
|
121
|
+
*
|
|
122
|
+
* 🔴 ATÉ AQUI ELE SÓ CHEGAVA PELA PORTA DO CARTUCHO (ADR-0094), e três dos seis jogos não a declaram — nesses,
|
|
123
|
+
* a engine descarregava 241 MB de modelos e não tinha com que os tocar. Descer os modelos sem o motor é a
|
|
124
|
+
* mesma armadilha do `.onnx` sem o `.onnx.json`, um nível acima.
|
|
125
|
+
*
|
|
126
|
+
* 📏 MEDIDO EM 2026-09-09 em jsDelivr, versões FIXADAS, todos 200 com `Access-Control-Allow-Origin: *`.
|
|
127
|
+
* ⚠️ SÃO CINCO FICHEIROS E NÃO UM: o `piper-tts-web.js` é só a entrada (23 KB) — os dois pedaços com hash no
|
|
128
|
+
* nome são o corpo e a tabela de vozes, e o `onnxruntime-web` é quem corre o modelo. Trazer só a entrada dá
|
|
129
|
+
* um módulo que importa o que não está lá.
|
|
130
|
+
* 📌 `ort-wasm-simd-threaded` e não o `jsep`: o jsep é o caminho WebGPU e pesa 21,7 MB contra 11,2 — e o
|
|
131
|
+
* hardware do pilar 1 não é onde a WebGPU se ganha.
|
|
132
|
+
*/
|
|
133
|
+
const PP = 'https://cdn.jsdelivr.net/npm/@mintplex-labs/piper-tts-web@1.0.5/dist';
|
|
134
|
+
const ORT = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist';
|
|
135
|
+
const PIPER = Object.freeze([
|
|
136
|
+
{ id: 'voz:runtime', url: `${PP}/piper-tts-web.js`, bytes: 23_646 },
|
|
137
|
+
{ id: 'voz:runtime:corpo', url: `${PP}/piper-o91UDS6e.js`, bytes: 158_217 },
|
|
138
|
+
{ id: 'voz:runtime:tabela', url: `${PP}/voices_static-D_OtJDHM.js`, bytes: 147_377 },
|
|
139
|
+
{ id: 'voz:runtime:ort', url: `${ORT}/ort.min.js`, bytes: 446_284 },
|
|
140
|
+
{ id: 'voz:runtime:ort-wasm', url: `${ORT}/ort-wasm-simd-threaded.wasm`, bytes: 11_246_032 },
|
|
141
|
+
]);
|
|
142
|
+
export const PESADOS = Object.freeze([
|
|
143
|
+
...VOZES_NEURAIS.flatMap(entradasDaVoz),
|
|
144
|
+
...PIPER,
|
|
145
|
+
/*
|
|
146
|
+
* 🔴 O RUNTIME DE VISÃO — decidido e SEM FONTE, e a ausência é medida.
|
|
147
|
+
*
|
|
148
|
+
* A issue #11 diz «MediaPipe» e o `ui/webcam.ts` faz WebGazer, buscado de `webgazer.cs.brown.edu` por um
|
|
149
|
+
* `<script src>` com preguiça no PRIMEIRO USO — sem SRI, sem `crossorigin`, e a falhar em silêncio numa
|
|
150
|
+
* escola sem rede. `git grep -i mediapipe` em `app/js` devolve ZERO (ADR-0119).
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ NÃO PONHO AQUI A URL DO WEBGAZER. Trocar o fornecedor é decisão da #129, e escrevê-la aqui seria
|
|
153
|
+
* decidi-la de lado — a mesma coisa que o ADR-0119 apanhou: um subsistema que a engine DECLARA possuir e
|
|
154
|
+
* que na prática é outra coisa.
|
|
155
|
+
*/
|
|
156
|
+
...MEDIAPIPE,
|
|
157
|
+
...WEBGAZER,
|
|
158
|
+
/*
|
|
159
|
+
* 🔴 O ACERVO DE ARTE — a quarta coisa pesada do ADR-0119, e a única SEM FONTE. Medido: `art/` tem DOIS
|
|
160
|
+
* ficheiros — um README e um `ATTRIBUTION.csv` de 40 bytes, só o cabeçalho. Zero arte.
|
|
161
|
+
*
|
|
162
|
+
* ⚠️ ATÉ 2026-09-09 ESTA LINHA CULPAVA A COISA ERRADA. Dizia que a quarentena estava vazia e que a arte
|
|
163
|
+
* era CC BY-SA 3.0 com autoria por recurso — descrevendo o Liberated Pixel Cup, que o ADR-0133 recusou:
|
|
164
|
+
* os dois braços dele são share-alike ou GPL, e nenhum está na lista fechada de quatro licenças.
|
|
165
|
+
*
|
|
166
|
+
* 📌 A razão de continuar sem fonte MUDOU e é mais simples: não há acervo escolhido. A arte entra sob
|
|
167
|
+
* CC0, CC BY 3.0, CC BY 4.0 ou OGA-BY, com uma linha de livro por recurso e a URL da origem — e nada
|
|
168
|
+
* disso é uma URL única que um buscador possa pedir. A fonte desta linha é o dia em que houver acervo.
|
|
169
|
+
*/
|
|
170
|
+
{
|
|
171
|
+
id: 'arte:acervo',
|
|
172
|
+
url: null,
|
|
173
|
+
porQueNaoTemFonte: 'não há acervo escolhido: `art/` tem só README e cabeçalho do CSV. O ADR-0133 fechou '
|
|
174
|
+
+ 'a lista em CC0, CC BY 3.0/4.0 e OGA-BY, e a arte entra recurso a recurso com autoria e URL de '
|
|
175
|
+
+ 'origem — não por uma URL solta que este buscador possa pedir.',
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { CACHE_PESADOS, PESADOS, type Pesado } from './pesados-catalogo.js';
|
|
3
|
+
export { CACHE_PESADOS, PESADOS };
|
|
4
|
+
export type { Pesado };
|
|
5
|
+
/** O que aconteceu com cada entrada, para quem chama poder dizê-lo a uma pessoa. */
|
|
6
|
+
export interface RelatorioPesado {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly estado: 'ja-tinha' | 'baixado' | 'falhou' | 'sem-fonte';
|
|
9
|
+
readonly bytes?: number;
|
|
10
|
+
readonly erro?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OpcoesDosPesados {
|
|
13
|
+
/** `caches` do navegador. Injectado para o gate não precisar de um. */
|
|
14
|
+
readonly cacheStorage?: CacheStorage;
|
|
15
|
+
/** `fetch`. Injectado pela mesma razão. */
|
|
16
|
+
readonly buscar?: typeof fetch;
|
|
17
|
+
/** Chamado a cada entrada resolvida — é o que deixa a interface dizer o que está a acontecer. */
|
|
18
|
+
readonly aoProgredir?: (r: RelatorioPesado) => void;
|
|
19
|
+
/** Só estas ids, se dado. Serve ao consumidor que quer as vozes e não o resto. */
|
|
20
|
+
readonly apenas?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* BAIXA O QUE FALTA, UM DE CADA VEZ, E DEVOLVE O QUE ACONTECEU COM CADA UM.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ AS ENTRADAS SEM `url` NÃO SÃO SALTADAS EM SILÊNCIO — devolvem `sem-fonte`. É a diferença entre «este
|
|
26
|
+
* subsistema ainda não tem de onde vir» e «este subsistema está tratado», e é exactamente a distinção que o
|
|
27
|
+
* ADR-0119 mediu em falta: a engine PROMETIA quatro coisas e entregava uma, sem nada a dizê-lo.
|
|
28
|
+
*/
|
|
29
|
+
export declare function baixarPesados(opcoes?: OpcoesDosPesados): Promise<RelatorioPesado[]>;
|
|
30
|
+
/** O peso do que ainda falta, em bytes — para um aviso poder dizer «faltam 241 MB» antes de começar. */
|
|
31
|
+
export declare function pesoPorBaixar(relatorio: readonly RelatorioPesado[]): number;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/pesados.ts — AS COISAS PESADAS, BAIXADAS NO PRIMEIRO CARREGAMENTO (ADR-0110, ADR-0116, ADR-0119).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE ISTO É =========================
|
|
5
|
+
// O pilar 8 diz «PWA no primeiro dia ONLINE, depois OFFLINE-FIRST», e o ADR-0116 tirou a contradição que
|
|
6
|
+
// travava isto: instalar já é um acto de rede, logo buscar na instalação não viola nada. O que faltava era
|
|
7
|
+
// alguém a buscar.
|
|
8
|
+
//
|
|
9
|
+
// 📏 MEDIDO EM 2026-09-09, e é o estado que este ficheiro existe para mudar: das quatro coisas pesadas que a
|
|
10
|
+
// engine promete (fontes · voz neural · runtime de visão · arte do LCP), só as FONTES viajavam de verdade.
|
|
11
|
+
// As vozes eram porta do cartucho, o runtime de visão era um `<script src>` de CDN buscado com preguiça no
|
|
12
|
+
// PRIMEIRO USO, e a arte do LCP não existe.
|
|
13
|
+
//
|
|
14
|
+
// ⚠️ E ISTO NÃO BLOQUEIA O JOGO. A criança joga enquanto os 241 MB descem; o que não pode acontecer é ela
|
|
15
|
+
// chegar ao segundo dia, sem rede, e descobrir que a voz nunca foi buscada.
|
|
16
|
+
//
|
|
17
|
+
// ========================= AS TRÊS REGRAS QUE A FORMA IMPÕE =========================
|
|
18
|
+
// 1. **UM DE CADA VEZ.** Quatro descargas de 60 MB em paralelo num link de escola disputam a mesma banda e
|
|
19
|
+
// nenhuma acaba primeiro — e o jogo, que precisa da rede para as próprias imagens, fica atrás delas.
|
|
20
|
+
// 2. **NUNCA LANÇA.** Uma falha de rede é REPORTADA e a lista continua. Um `throw` aqui derrubaria o
|
|
21
|
+
// arranque de um jogo por causa de um recurso que ele nem usa hoje.
|
|
22
|
+
// 3. **IDEMPOTENTE.** O que já está na Cache Storage não é buscado outra vez — é o que torna isto seguro de
|
|
23
|
+
// chamar em todo arranque em vez de só «no primeiro», que ninguém sabe detectar com honestidade.
|
|
24
|
+
import { CACHE_PESADOS, PESADOS } from './pesados-catalogo.js';
|
|
25
|
+
export { CACHE_PESADOS, PESADOS };
|
|
26
|
+
/**
|
|
27
|
+
* BAIXA O QUE FALTA, UM DE CADA VEZ, E DEVOLVE O QUE ACONTECEU COM CADA UM.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ AS ENTRADAS SEM `url` NÃO SÃO SALTADAS EM SILÊNCIO — devolvem `sem-fonte`. É a diferença entre «este
|
|
30
|
+
* subsistema ainda não tem de onde vir» e «este subsistema está tratado», e é exactamente a distinção que o
|
|
31
|
+
* ADR-0119 mediu em falta: a engine PROMETIA quatro coisas e entregava uma, sem nada a dizê-lo.
|
|
32
|
+
*/
|
|
33
|
+
export async function baixarPesados(opcoes = {}) {
|
|
34
|
+
const cs = opcoes.cacheStorage ?? (typeof caches !== 'undefined' ? caches : undefined);
|
|
35
|
+
const buscar = opcoes.buscar ?? (typeof fetch !== 'undefined' ? fetch : undefined);
|
|
36
|
+
const alvos = opcoes.apenas
|
|
37
|
+
? PESADOS.filter((p) => opcoes.apenas.includes(p.id))
|
|
38
|
+
: PESADOS;
|
|
39
|
+
const out = [];
|
|
40
|
+
const conta = (r) => { out.push(r); opcoes.aoProgredir?.(r); };
|
|
41
|
+
if (!cs || !buscar) {
|
|
42
|
+
for (const p of alvos)
|
|
43
|
+
conta({ id: p.id, estado: 'falhou', erro: 'sem Cache Storage ou sem fetch' });
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
const cache = await cs.open(CACHE_PESADOS);
|
|
47
|
+
for (const p of alvos) {
|
|
48
|
+
if (!p.url) {
|
|
49
|
+
conta({ id: p.id, estado: 'sem-fonte', erro: p.porQueNaoTemFonte });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
if (await cache.match(p.url)) {
|
|
54
|
+
conta({ id: p.id, estado: 'ja-tinha' });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const resp = await buscar(p.url);
|
|
58
|
+
if (!resp.ok) {
|
|
59
|
+
conta({ id: p.id, estado: 'falhou', erro: `HTTP ${resp.status}` });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
await cache.put(p.url, resp.clone());
|
|
63
|
+
conta({ id: p.id, estado: 'baixado', bytes: p.bytes });
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
conta({ id: p.id, estado: 'falhou', erro: e instanceof Error ? e.message : String(e) });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** O peso do que ainda falta, em bytes — para um aviso poder dizer «faltam 241 MB» antes de começar. */
|
|
72
|
+
export function pesoPorBaixar(relatorio) {
|
|
73
|
+
const feitos = new Set(relatorio.filter((r) => r.estado === 'ja-tinha' || r.estado === 'baixado').map((r) => r.id));
|
|
74
|
+
return PESADOS.filter((p) => p.url && !feitos.has(p.id)).reduce((s, p) => s + (p.bytes ?? 0), 0);
|
|
75
|
+
}
|
|
@@ -62,6 +62,7 @@ export declare const KEYS: {
|
|
|
62
62
|
lang: string;
|
|
63
63
|
letterCase: string;
|
|
64
64
|
captions: string;
|
|
65
|
+
tea: string;
|
|
65
66
|
menuIndex: string;
|
|
66
67
|
fontKey: string;
|
|
67
68
|
padDesign: string;
|
|
@@ -74,9 +75,38 @@ export declare const KEYS: {
|
|
|
74
75
|
padDpadMm: string;
|
|
75
76
|
reducedMotion: string;
|
|
76
77
|
toggleMoveLegacy: string;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated ⚠️ A CHAVE LEGADA do modo visual — UM valor, do tempo em que só cabia um (issue #104).
|
|
80
|
+
*
|
|
81
|
+
* Continua a ser LIDA, e é isso que impede a criança de perder o que já escolheu; continua a ser ESCRITA
|
|
82
|
+
* enquanto os controles ainda escreverem um valor de cada vez, porque um leitor antigo (o cartucho na
|
|
83
|
+
* versão publicada) faz `if (v && VIZ_BY_KEY[v])` e rejeitaria um JSON — escrever a forma nova AQUI
|
|
84
|
+
* apagaria o ajuste dela em silêncio, que é exactamente o defeito que a migração existe para não cometer.
|
|
85
|
+
*/
|
|
77
86
|
vizP: (i: number) => string;
|
|
87
|
+
/**
|
|
88
|
+
* O ESTADO VISUAL de dois eixos, em JSON (ADR-0076, issue #104).
|
|
89
|
+
*
|
|
90
|
+
* ⚠️ CHAVE NOVA AO LADO DA VELHA, e não a mesma chave com conteúdo novo. É o mesmo desenho que o campo
|
|
91
|
+
* `visual` usa ao lado do `viz`: as duas formas coexistem enquanto houver leitores das duas, cada um lê a
|
|
92
|
+
* que entende, e a velha só morre quando não sobrar quem a leia. `migrarVisual` aceita as duas, então o
|
|
93
|
+
* recuo — chave nova ausente, chave velha presente — devolve exactamente o que a criança escolheu.
|
|
94
|
+
*/
|
|
95
|
+
visualP: (i: number) => string;
|
|
78
96
|
sinkP: (i: number) => string;
|
|
79
97
|
easyP: (i: number) => string;
|
|
98
|
+
/**
|
|
99
|
+
* ⚠️ AS DUAS DE BAIXO SÃO AS CHAVES LEGADAS desde 2026-09-08 (ADR-0104 §C, issue #114). Continuam a ser
|
|
100
|
+
* LIDAS — é o ajuste da criança, e a herança dele é o que a impede de o perder — e não voltam a ser
|
|
101
|
+
* escritas. O que se escreve é a chave COM TRANSPORTE, porque a alternância é do APARELHO e não da pessoa:
|
|
102
|
+
* ligá-la no controle de tela, onde ninguém segura um botão virtual com conforto, ligava-a também no
|
|
103
|
+
* teclado, onde segurar uma tecla é exactamente o que a criança sabe fazer.
|
|
104
|
+
*
|
|
105
|
+
* ⚠️ E A CHAVE NOVA NÃO MORA AQUI, de propósito. Ela é `chaveDaAlternancia`, em `input/latch-scope` — este
|
|
106
|
+
* ficheiro é módulo-FOLHA e `platform/` não importa de `input/`, que é a camada acima. Montá-la aqui
|
|
107
|
+
* exigiria ou uma aresta ao contrário ou uma segunda cópia do nome, e a segunda cópia é exactamente o que
|
|
108
|
+
* o comentário do bloco acima existe para impedir. O dono do nome é quem conhece a regra do transporte.
|
|
109
|
+
*/
|
|
80
110
|
toggleMoveP: (i: number) => string;
|
|
81
111
|
toggleRunP: (i: number) => string;
|
|
82
112
|
rmWalkP: (i: number) => string;
|
|
@@ -123,6 +123,12 @@ export const KEYS = {
|
|
|
123
123
|
ttsEngine: 'incl_tts_engine', ttsVoice: 'incl_tts_voice', lang: 'incl_lang', // audiocat_{k}
|
|
124
124
|
// comunicação / legendas (ADR-0028: todo menu persiste)
|
|
125
125
|
letterCase: 'incl_lettercase', captions: 'incl_captions',
|
|
126
|
+
// ⚠️ O NÍVEL TEA (calmo / silencioso) PASSOU A PERSISTIR EM 2026-09-07, e antes não persistia: era um
|
|
127
|
+
// `let calmMode = 0` em `ui/pause-icons`, com o comentário «deliberately NOT persisted — verbatim: game.js
|
|
128
|
+
// never wrote it to storage». O «verbatim» é a chave — foi PRESERVADO na extração do monólito, não
|
|
129
|
+
// decidido. O custo era da criança que mais precisa dele: quem usa o modo silencioso voltava a pô-lo a
|
|
130
|
+
// cada sessão, e é para quem o barulho inesperado custa mais. O ADR-0028 diz que todo menu persiste.
|
|
131
|
+
tea: 'incl_tea',
|
|
126
132
|
menuIndex: 'incl_menuindex', // "6 de 10" no fim do anuncio de item (ADR-0044, item 3)
|
|
127
133
|
// tipografia / controles / toque
|
|
128
134
|
fontKey: 'incl_font_k', padDesign: 'incl_paddesign', padDir: 'incl_paddir', touchmap: 'incl_touchmap',
|
|
@@ -133,9 +139,38 @@ export const KEYS = {
|
|
|
133
139
|
reducedMotion: 'inclusionist.reducedmotion.v1', toggleMoveLegacy: 'inclusionist.togglemove',
|
|
134
140
|
// POR JOGADOR — parametrizadas pelo indice da tela. Eram sufixos '_p'+i montados a mao em varios
|
|
135
141
|
// pontos do game.js; virar funcao aqui e o que impede que um deles escreva num nome torto.
|
|
142
|
+
/**
|
|
143
|
+
* @deprecated ⚠️ A CHAVE LEGADA do modo visual — UM valor, do tempo em que só cabia um (issue #104).
|
|
144
|
+
*
|
|
145
|
+
* Continua a ser LIDA, e é isso que impede a criança de perder o que já escolheu; continua a ser ESCRITA
|
|
146
|
+
* enquanto os controles ainda escreverem um valor de cada vez, porque um leitor antigo (o cartucho na
|
|
147
|
+
* versão publicada) faz `if (v && VIZ_BY_KEY[v])` e rejeitaria um JSON — escrever a forma nova AQUI
|
|
148
|
+
* apagaria o ajuste dela em silêncio, que é exactamente o defeito que a migração existe para não cometer.
|
|
149
|
+
*/
|
|
136
150
|
vizP: (i) => 'incl_viz_p' + i,
|
|
151
|
+
/**
|
|
152
|
+
* O ESTADO VISUAL de dois eixos, em JSON (ADR-0076, issue #104).
|
|
153
|
+
*
|
|
154
|
+
* ⚠️ CHAVE NOVA AO LADO DA VELHA, e não a mesma chave com conteúdo novo. É o mesmo desenho que o campo
|
|
155
|
+
* `visual` usa ao lado do `viz`: as duas formas coexistem enquanto houver leitores das duas, cada um lê a
|
|
156
|
+
* que entende, e a velha só morre quando não sobrar quem a leia. `migrarVisual` aceita as duas, então o
|
|
157
|
+
* recuo — chave nova ausente, chave velha presente — devolve exactamente o que a criança escolheu.
|
|
158
|
+
*/
|
|
159
|
+
visualP: (i) => 'incl_visual_p' + i,
|
|
137
160
|
sinkP: (i) => 'incl_sink_p' + i,
|
|
138
161
|
easyP: (i) => 'incl_easy_p' + i,
|
|
162
|
+
/**
|
|
163
|
+
* ⚠️ AS DUAS DE BAIXO SÃO AS CHAVES LEGADAS desde 2026-09-08 (ADR-0104 §C, issue #114). Continuam a ser
|
|
164
|
+
* LIDAS — é o ajuste da criança, e a herança dele é o que a impede de o perder — e não voltam a ser
|
|
165
|
+
* escritas. O que se escreve é a chave COM TRANSPORTE, porque a alternância é do APARELHO e não da pessoa:
|
|
166
|
+
* ligá-la no controle de tela, onde ninguém segura um botão virtual com conforto, ligava-a também no
|
|
167
|
+
* teclado, onde segurar uma tecla é exactamente o que a criança sabe fazer.
|
|
168
|
+
*
|
|
169
|
+
* ⚠️ E A CHAVE NOVA NÃO MORA AQUI, de propósito. Ela é `chaveDaAlternancia`, em `input/latch-scope` — este
|
|
170
|
+
* ficheiro é módulo-FOLHA e `platform/` não importa de `input/`, que é a camada acima. Montá-la aqui
|
|
171
|
+
* exigiria ou uma aresta ao contrário ou uma segunda cópia do nome, e a segunda cópia é exactamente o que
|
|
172
|
+
* o comentário do bloco acima existe para impedir. O dono do nome é quem conhece a regra do transporte.
|
|
173
|
+
*/
|
|
139
174
|
toggleMoveP: (i) => 'incl_togglemove_p' + i,
|
|
140
175
|
toggleRunP: (i) => 'incl_togglerun_p' + i, // alternância do botão de CORRER (irmã da de movimento)
|
|
141
176
|
rmWalkP: (i) => 'incl_rmWalk_p' + i,
|
package/dist-pkg/platform/tts.js
CHANGED
|
@@ -10,7 +10,22 @@ import { t, bcp47 } from '../core/i18n.js';
|
|
|
10
10
|
import { criarFalaInterrompivel } from './interruptible-speech.js';
|
|
11
11
|
// ⚠️ MIGRAÇÃO PENDENTE (ADR-0022): a implementação neural de hoje é o @mintplex-labs/piper-tts-web e será SUBSTITUÍDA
|
|
12
12
|
// por sherpa-onnx-wasm (loader universal, modelos VITS/Piper + Kokoro-multi-lang carregados de qq URL R2 em runtime via
|
|
13
|
-
// FS.writeFile; lazy-fetch; eSpeak/Web Speech de fallback).
|
|
13
|
+
// FS.writeFile; lazy-fetch; eSpeak/Web Speech de fallback).
|
|
14
|
+
// 🔴 O MOTIVO ESCRITO AQUI ERA «@mintplex-labs foi descontinuado e só carrega 2 vozes pt-BR», E AS DUAS METADES
|
|
15
|
+
// FORAM MEDIDAS FALSAS EM 2026-09-09 — não pela leitura de um registo, que é como elas se propagaram por seis
|
|
16
|
+
// sítios, mas pelo registo npm e pelo pacote instalado:
|
|
17
|
+
// · DESCONTINUADO: nenhuma versão tem campo `deprecated`, e a 1.0.5 foi publicada em 2026-08-11 — depois de
|
|
18
|
+
// o ADR-0022 (2026-07-06) a dar por morta. A frase é de Julho e está atribuída ao Dev; o registo não a nega
|
|
19
|
+
// no passado, nega-a HOJE.
|
|
20
|
+
// · DUAS VOZES pt-BR: o `VoiceId` da 1.0.4 instalada traz 118 vozes, e entre elas as QUATRO exactas do
|
|
21
|
+
// ADR-0110 — `pt_BR-faber-medium`, `en_US-ryan-medium`, `en_US-amy-medium`, `es_MX-claude-high`.
|
|
22
|
+
// ⚠️ O QUE CONTINUA VERDADEIRO, e é mais afiado do que o motivo velho: o bundle prende `HF_BASE` a
|
|
23
|
+
// `huggingface.co/diffusionstudio/piper-voices` COM UMA GUARDA (`if (!url.match("https://huggingface.co")) return`),
|
|
24
|
+
// logo nenhum espelho de escola é alcançável por ele; e ele busca o PRÓPRIO runtime de cdnjs/jsDelivr por
|
|
25
|
+
// omissão, que é o que o ADR-0114 retira. A montante, o Piper mudou para `OHF-Voice/piper1-gpl` e a Open Home
|
|
26
|
+
// Foundation procura mantenedores — o risco existe, mas está noutro sítio.
|
|
27
|
+
// 📌 A ESCOLHA CONTINUA A SER A #129, e este comentário não a toma: descreve o que foi medido para que o
|
|
28
|
+
// próximo leitor não herde o motivo velho como se fosse medição.
|
|
14
29
|
// ⚠️ E DESDE O ADR-0094 ESTE MÓDULO NÃO NOMEIA FORNECEDOR NENHUM — quem o nomeia é o JOGO, por `ctx.carregarVozNeural`.
|
|
15
30
|
// O nome estava aqui num `import()` e o pacote em `devDependencies`, o que publicou uma engine que não compilava
|
|
16
31
|
// (ADR-0093); pô-lo em `dependencies` consertava o build e obrigava todo consumidor a 135,4 MB de `onnxruntime-web`,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** Uma voz do catálogo. `voice` é o identificador no fornecedor; `engine` é o motor que a lê. */
|
|
3
|
+
export interface VozNeural {
|
|
4
|
+
readonly locale: string;
|
|
5
|
+
readonly engine: string;
|
|
6
|
+
readonly voice: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* AS QUATRO QUE A ENGINE GARANTE (ADR-0110), decididas pelo Dev em 2026-09-08.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ TODAS `vits-piper` E NENHUMA `ncnn`, que o ADR-0065 §5 já proibia: `ncnn` é outro MOTOR DE INFERÊNCIA —
|
|
12
|
+
* modelos `.param`/`.bin` e não `.onnx` — e este projeto corre sherpa-onnx. Dois dos links que chegaram em
|
|
13
|
+
* 2026-09-08 apontavam para os espelhos `ncnn`, e é por isso que a proibição tem gate e não só um comentário.
|
|
14
|
+
* ⚠️ E NENHUMA `int8`, pela razão MEDIDA do mesmo §5: no backend WASM o int8 corre ~3× MAIS LENTO que o fp32
|
|
15
|
+
* (RTF 5,6 contra 2,5, do log do próprio Dev) e produz saída incorrecta ou muda. Quantizar não encurta a
|
|
16
|
+
* espera aqui — alonga-a.
|
|
17
|
+
*/
|
|
18
|
+
export declare const VOZES_NEURAIS: readonly VozNeural[];
|
|
19
|
+
/**
|
|
20
|
+
* DE ONDE VÊM OS MODELOS (ADR-0114), num sítio só.
|
|
21
|
+
*
|
|
22
|
+
* 📏 MEDIDO EM 2026-09-08, e não escolhido: o `piper.ttstool.com` — que o Dev nomeou ao perguntar — serve o
|
|
23
|
+
* próprio runtime da própria origem e busca os modelos aqui. As quatro vozes deste catálogo respondem 200
|
|
24
|
+
* neste endereço, com `Access-Control-Allow-Origin: *`, logo um PWA pode buscá-las de outra origem.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ UM SÍTIO SÓ É A METADE QUE O REGISTO PEDE. Um endereço repetido no ponto de uso é como o CDN do
|
|
27
|
+
* WebGazer chegou ao `ui/webcam` — escrito à mão, sem política, e sem ninguém a poder mudá-lo de uma vez.
|
|
28
|
+
*
|
|
29
|
+
* ========================= 🔴 MUDOU DE ESPELHO EM 2026-09-09, E A REGRA É «BUSCAR DE ONDE O LEITOR LÊ» ====
|
|
30
|
+
* Era `rhasspy/piper-voices`, medido a 08/09 como o espelho que o `piper.ttstool.com` usa. Depois de o
|
|
31
|
+
* `platform/pesados` passar a descer os modelos no primeiro carregamento, mediu-se o outro lado — o
|
|
32
|
+
* `@mintplex-labs/piper-tts-web@1.0.4` INSTALADO, lido do bundle e não do README — e ele busca em
|
|
33
|
+
* `diffusionstudio/piper-voices`, com uma guarda que recusa qualquer URL fora de `huggingface.co`.
|
|
34
|
+
*
|
|
35
|
+
* 🎯 ENDEREÇOS DIFERENTES SIGNIFICAM CACHE DIFERENTE: a Cache Storage é indexada pela URL do pedido, logo os
|
|
36
|
+
* 241 MB que desciam no primeiro dia NÃO ERAM LIDOS POR NINGUÉM, e a biblioteca descarregava tudo outra vez
|
|
37
|
+
* no dia em que a voz fosse pedida. Até 482 MB num link de escola para uma voz.
|
|
38
|
+
*
|
|
39
|
+
* 📏 E OS DOIS ESPELHOS SERVEM OS MESMOS BYTES — 63 201 294 para o `pt_BR-faber-medium`, caminhos idênticos,
|
|
40
|
+
* `Access-Control-Allow-Origin: *` nos dois. Medido nos dois no mesmo minuto, o que torna esta troca uma
|
|
41
|
+
* correcção de facto e não uma preferência entre fornecedores.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ A CLÁUSULA DO ADR-0114 NÃO SE MEXE: o host continua nomeado num ponto só. O que mudou foi QUAL, e a
|
|
44
|
+
* regra que decide isso passa a estar escrita para não voltar a divergir — **busca-se de onde o LEITOR lê**.
|
|
45
|
+
* No dia em que a engine for dona do runtime (ADR-0124 cláusula 2), o leitor passa a ser ela e é ela que
|
|
46
|
+
* escolhe; até lá, o único leitor é a porta do cartucho.
|
|
47
|
+
*/
|
|
48
|
+
export declare const HOST_DOS_MODELOS = "https://huggingface.co/diffusionstudio/piper-voices/resolve/main/";
|
|
49
|
+
/**
|
|
50
|
+
* O CAMINHO DO MODELO, DERIVADO DO IDENTIFICADOR — e não uma segunda tabela.
|
|
51
|
+
*
|
|
52
|
+
* `pt_BR-faber-medium` diz tudo o que o caminho precisa: `pt/pt_BR/faber/medium/pt_BR-faber-medium.onnx`.
|
|
53
|
+
* 🎯 DERIVAR EM VEZ DE TABELAR é a decisão inteira desta função: uma tabela de caminhos ao lado da tabela
|
|
54
|
+
* de vozes seria o mesmo facto escrito duas vezes, e este repositório já pagou isso três vezes — o
|
|
55
|
+
* `DomQuery`, os rótulos de movimento reduzido, as chaves de armazenamento. Duas listas divergem, e
|
|
56
|
+
* divergem uma entrada de cada vez.
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ Devolve `null` para um identificador que não tenha a forma esperada, em vez de montar um caminho
|
|
59
|
+
* torto: uma URL inventada dá 404 na escola, e um `null` dá para reportar antes de sair de casa.
|
|
60
|
+
*/
|
|
61
|
+
export declare function caminhoDoModelo(v: VozNeural): string | null;
|
|
62
|
+
/** O endereço completo do modelo. `null` quando o identificador não se deixa ler. */
|
|
63
|
+
export declare function urlDoModelo(v: VozNeural): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* A CONFIGURAÇÃO da voz, que o motor lê junto com o modelo.
|
|
66
|
+
*
|
|
67
|
+
* 📌 `.onnx.json` e não um segundo caminho: é o mesmo ficheiro com outro sufixo, medido a responder 200 no
|
|
68
|
+
* mesmo sítio. Escrevê-lo como derivação mantém a regra de que o identificador é a única fonte.
|
|
69
|
+
*/
|
|
70
|
+
export declare function urlDaConfig(v: VozNeural): string | null;
|
|
71
|
+
/** Em que pé está cada voz. `falhou` é um estado e não uma excepção — ver `vozEmUso`. */
|
|
72
|
+
export type EstadoDaVoz = 'ausente' | 'a-buscar' | 'pronta' | 'falhou';
|
|
73
|
+
/** O que se sabe de cada voz, por identificador. Uma voz ausente do mapa é `ausente`. */
|
|
74
|
+
export type EstadosDasVozes = Readonly<Record<string, EstadoDaVoz | undefined>>;
|
|
75
|
+
export declare const estadoDe: (estados: EstadosDasVozes, v: VozNeural) => EstadoDaVoz;
|
|
76
|
+
/**
|
|
77
|
+
* A ORDEM POR QUE SE BUSCAM: primeiro as do idioma corrente, depois as outras, cada grupo na ordem do
|
|
78
|
+
* catálogo. Só entram as que ainda não estão prontas nem em curso.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ O IDIOMA CORRENTE PRIMEIRO É A REGRA INTEIRA, e ela é sobre uma criança e não sobre eficiência: as
|
|
81
|
+
* outras são para «conforme interesse do jogador», mas a dela é a que ela precisa AGORA. Buscar em ordem de
|
|
82
|
+
* catálogo faria uma criança brasileira esperar por duas vozes inglesas na rede de uma escola.
|
|
83
|
+
*
|
|
84
|
+
* 📌 `falhou` VOLTA À FILA. Uma escola perde a rede a meio da manhã e recupera-a; uma voz marcada como
|
|
85
|
+
* falhada para sempre seria uma criança sem voz até alguém recarregar a página. Quem chama decide QUANDO
|
|
86
|
+
* tentar de novo — este módulo só diz que ainda há o que buscar.
|
|
87
|
+
*/
|
|
88
|
+
export declare function ordemDeBusca(estados: EstadosDasVozes, localeCorrente: string, catalogo?: readonly VozNeural[]): VozNeural[];
|
|
89
|
+
/** O que a criança ouve agora. `recuo` é a voz do sistema — eSpeak ou Web Speech. */
|
|
90
|
+
export type VozEmUso = {
|
|
91
|
+
readonly tipo: 'neural';
|
|
92
|
+
readonly voice: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly tipo: 'recuo';
|
|
95
|
+
readonly porque: 'a-buscar' | 'sem-voz-para-o-idioma' | 'falhou' | 'ausente';
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* QUEM ESTÁ A FALAR, E POR QUE — e esta função é o gate 3 do ADR-0110 em forma de código.
|
|
99
|
+
*
|
|
100
|
+
* ⚠️ O DEFEITO QUE ELA IMPEDE TEM NOME NESTE REPOSITÓRIO: «um recuo que se apresenta como a coisa real é a
|
|
101
|
+
* família do `reflectTTS`». Uma engine que dissesse «voz neural» enquanto o modelo ainda desce faria um adulto
|
|
102
|
+
* concluir que a qualidade que ouve É a qualidade final — e desistir de esperar por uma coisa que já vinha a
|
|
103
|
+
* caminho. Dizer `recuo` e dizer PORQUÊ custa uma linha e é a diferença entre informar e enganar.
|
|
104
|
+
*
|
|
105
|
+
* 📌 A PRIMEIRA VOZ PRONTA DO IDIOMA, e nada mais: onde há duas prontas, esta função NÃO escolhe entre elas —
|
|
106
|
+
* devolve a primeira do catálogo porque tem de devolver alguma, e qual delas a criança prefere é a pergunta
|
|
107
|
+
* que o ADR-0110 deixou em aberto. Ver o cabeçalho.
|
|
108
|
+
*/
|
|
109
|
+
export declare function vozEmUso(estados: EstadosDasVozes, localeCorrente: string, catalogo?: readonly VozNeural[]): VozEmUso;
|