@the-inclusionist/engine 7.0.0 → 8.0.0-rc.1
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/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +43 -2
- package/dist-pkg/boot/create-game.d.ts +76 -2
- package/dist-pkg/boot/create-game.js +271 -13
- package/dist-pkg/core/constants.d.ts +0 -28
- package/dist-pkg/core/constants.js +34 -17
- package/dist-pkg/core/contract.d.ts +39 -0
- package/dist-pkg/core/contract.js +36 -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 +33 -0
- package/dist-pkg/i18n/es.js +33 -0
- package/dist-pkg/i18n/pt.js +46 -0
- package/dist-pkg/input/default-bindings.d.ts +40 -0
- package/dist-pkg/input/default-bindings.js +143 -9
- package/dist-pkg/input/gamepad.d.ts +18 -11
- package/dist-pkg/input/gamepad.js +75 -9
- 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 +10 -0
- package/dist-pkg/input/keyboard.js +41 -11
- package/dist-pkg/input/keydown.d.ts +27 -2
- package/dist-pkg/input/keydown.js +20 -6
- 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/origem-sintetica.d.ts +44 -0
- package/dist-pkg/input/origem-sintetica.js +60 -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 +11 -2
- package/dist-pkg/input/touch-bindings.js +9 -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/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 +90 -0
- package/dist-pkg/platform/voice-plan.js +137 -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 +47 -1
- package/dist-pkg/ui/fonts.js +47 -9
- 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 +134 -20
- package/dist-pkg/ui/pause-icons.js +307 -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 +81 -3
- package/dist-pkg/ui/settings-motor.js +118 -9
- package/dist-pkg/ui/settings-typo.d.ts +32 -2
- package/dist-pkg/ui/settings-typo.js +64 -18
- 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 +18 -0
- package/docs/LICENSES.md +12 -0
- package/package.json +24 -4
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
|
@@ -6,22 +6,52 @@
|
|
|
6
6
|
// e o ADR-0086 §2 diz qual é para a plataforma. Esquemas por contagem de jogadores (solo/p2/p3/p4).
|
|
7
7
|
// A INSTÂNCIA atual (KB) e o remap ficam no composition root — aqui só config/load/save/reset.
|
|
8
8
|
import * as store from '../platform/storage.js';
|
|
9
|
+
// ⚠️ A DECLARAÇÃO DOS ESQUEMAS MORA LÁ (ADR-0096), e este ficheiro passou a derivá-los em vez de os repetir
|
|
10
|
+
// — é a união que a issue #118 pede. `default-bindings` é folha (só importa `core/actions`), então não há
|
|
11
|
+
// ciclo: quem depende é o ficheiro de persistência, e não o contrário.
|
|
12
|
+
import { KEYBOARD_SOLO, KEYBOARD_DUO } from './default-bindings.js';
|
|
9
13
|
const CKEY = 'inclusionist.kbcontrols.v3';
|
|
14
|
+
/**
|
|
15
|
+
* AS SEIS POSIÇÕES QUE UM TECLADO PARTIDO NÃO ALCANÇA, declaradas como ausência (issue #118, decisão do Dev).
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ `null` AQUI É UMA AFIRMAÇÃO, e é o que torna o `KeyScheme` fechado útil em vez de burocrático: quando o
|
|
18
|
+
* teclado é repartido por três ou quatro crianças, não há lugar físico para ombros, gatilhos, start e select
|
|
19
|
+
* de cada uma — o bloco de cada jogador tem oito teclas e acabou. Inventar teclas para preencher seria dar a
|
|
20
|
+
* cada criança um alcance que ela não tem, e o `ui/reach-notice` (#112) diria a coisa errada.
|
|
21
|
+
*
|
|
22
|
+
* O que `null` compra: o aviso de alcance pode dizer, ANTES de a criança começar, quais das ações do jogo o
|
|
23
|
+
* controlo dela não alcança — e o jogo pode decidir não usar essas posições no modo de quatro.
|
|
24
|
+
*/
|
|
25
|
+
const SEM_ALCANCE_NO_TECLADO_PARTIDO = Object.freeze({
|
|
26
|
+
leftShoulder: null, leftTrigger: null, rightShoulder: null, rightTrigger: null, start: null, select: null,
|
|
27
|
+
});
|
|
10
28
|
// 4 esquemas base p/ 3–4 jogadores (modos 3 e 4 têm esquemas SEPARADOS, p3 e p4, editáveis por jogador)
|
|
11
29
|
export const KB_SCHEMES4 = [
|
|
12
|
-
{ left: ['KeyA'], right: ['KeyD'], up: ['KeyW'], down: ['KeyS'], action1: ['KeyZ'], action2: ['KeyX'], action4: ['KeyC'], action3: ['KeyV'] },
|
|
13
|
-
{ left: ['KeyJ'], right: ['KeyL'], up: ['KeyI'], down: ['KeyK'], action1: ['KeyM'], action2: ['Comma'], action4: ['Period'], action3: ['Semicolon', 'Slash'] },
|
|
14
|
-
{ left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Home'], action2: ['End'], action4: ['PageUp'], action3: ['PageDown'] },
|
|
15
|
-
{ left: ['Numpad4'], right: ['Numpad6'], up: ['Numpad8'], down: ['Numpad5'], action1: ['Numpad2'], action2: ['Numpad0'], action4: ['Numpad3'], action3: ['NumpadDecimal'] },
|
|
30
|
+
{ left: ['KeyA'], right: ['KeyD'], up: ['KeyW'], down: ['KeyS'], action1: ['KeyZ'], action2: ['KeyX'], action4: ['KeyC'], action3: ['KeyV'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
31
|
+
{ left: ['KeyJ'], right: ['KeyL'], up: ['KeyI'], down: ['KeyK'], action1: ['KeyM'], action2: ['Comma'], action4: ['Period'], action3: ['Semicolon', 'Slash'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
32
|
+
{ left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Home'], action2: ['End'], action4: ['PageUp'], action3: ['PageDown'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
33
|
+
{ left: ['Numpad4'], right: ['Numpad6'], up: ['Numpad8'], down: ['Numpad5'], action1: ['Numpad2'], action2: ['Numpad0'], action4: ['Numpad3'], action3: ['NumpadDecimal'], ...SEM_ALCANCE_NO_TECLADO_PARTIDO },
|
|
16
34
|
];
|
|
35
|
+
/**
|
|
36
|
+
* Cópia PROFUNDA e MUTÁVEL de uma tabela declarada. O remapeamento escreve dentro do esquema vivo, então ele
|
|
37
|
+
* não pode partilhar objeto com a tabela de `input/default-bindings`, que é congelada e é a declaração.
|
|
38
|
+
*/
|
|
39
|
+
const vivo = (t) => JSON.parse(JSON.stringify(t));
|
|
40
|
+
/**
|
|
41
|
+
* ⚠️ AS DUAS TABELAS DE TECLADO PASSARAM A SER UMA (issue #118). O solo e a dupla já não são escritos aqui:
|
|
42
|
+
* são CÓPIAS VIVAS do que `input/default-bindings` declara, que é onde o ADR-0096 pôs a decisão.
|
|
43
|
+
*
|
|
44
|
+
* Antes eram duas listas paralelas com oito posições cada, e as oito «coincidiam» — menos uma. A `Space` do
|
|
45
|
+
* jogador 1 em dupla estava numa e não na outra, e o custo era concreto: `ui/webcam.ts` sintetiza `Space`
|
|
46
|
+
* para «olhar para cima = pular», então entrar um segundo jogador tirava o PULO de quem joga com os olhos e
|
|
47
|
+
* deixava o andar. Nada errava em voz alta. Derivar em vez de repetir torna essa divergência impossível de
|
|
48
|
+
* voltar a existir, em vez de a apanhar depois de acontecer.
|
|
49
|
+
*/
|
|
17
50
|
export const KB_DEFAULTS = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{ left: ['ArrowLeft'], right: ['ArrowRight'], up: ['ArrowUp'], down: ['ArrowDown'], action1: ['Numpad8'], action2: ['Numpad5'], action4: ['Numpad9'], action3: ['Numpad6'] }],
|
|
23
|
-
p3: JSON.parse(JSON.stringify(KB_SCHEMES4.slice(0, 3))), // modo 3 jogadores (independente do 4)
|
|
24
|
-
p4: JSON.parse(JSON.stringify(KB_SCHEMES4)), // modo 4 jogadores
|
|
51
|
+
solo: vivo(KEYBOARD_SOLO),
|
|
52
|
+
p2: KEYBOARD_DUO.map(vivo),
|
|
53
|
+
p3: KB_SCHEMES4.slice(0, 3).map(vivo), // modo 3 jogadores (independente do 4)
|
|
54
|
+
p4: KB_SCHEMES4.map(vivo), // modo 4 jogadores
|
|
25
55
|
};
|
|
26
56
|
// dado salvo (parcial): sobrepõe os defaults; p34 é o formato ANTIGO (migra p/ p3+p4).
|
|
27
57
|
// A FORMA vem de `vocabulary-migration`, que é quem a traduz — declarar aqui outra vez seria a
|
|
@@ -9,6 +9,14 @@ export interface KeydownEventLike {
|
|
|
9
9
|
altKey: boolean;
|
|
10
10
|
ctrlKey: boolean;
|
|
11
11
|
preventDefault(): void;
|
|
12
|
+
/**
|
|
13
|
+
* O navegador viu a pessoa carregar? (ADR-0109) — OPCIONAL, e a opcionalidade é a decisão.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Torná-lo obrigatório partiria todos os duplos de teste que já existem, e partiria-os por uma razão
|
|
16
|
+
* falsa: eles descrevem a decisão de teclado, que não depende disto. O que a ausência significa está
|
|
17
|
+
* escrito no `input/origem-sintetica` — «não afirmei nada», cuja resposta é `undefined` e não `'teclado'`.
|
|
18
|
+
*/
|
|
19
|
+
isTrusted?: boolean;
|
|
12
20
|
}
|
|
13
21
|
/** `keyup` só precisa do código. */
|
|
14
22
|
export interface KeyupEventLike {
|
|
@@ -174,6 +182,7 @@ export declare function isEasyShortcut(code: string, s: KeydownSnapshot): boolea
|
|
|
174
182
|
export declare function titleNavOf(code: string, s: KeydownSnapshot, action2: boolean): TitleNav;
|
|
175
183
|
import type { EventTargetLike } from './touch-bindings.js';
|
|
176
184
|
import type { DomQuery } from '../core/dom-query.js';
|
|
185
|
+
import type { Transporte } from './transporte-em-uso.js';
|
|
177
186
|
export { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
178
187
|
/**
|
|
179
188
|
* Quem é o dono do modal que esta tecla comanda? Devolve a POSIÇÃO no array, ou -1.
|
|
@@ -219,8 +228,24 @@ export interface KeydownCtx {
|
|
|
219
228
|
getPlayers: () => readonly KeydownPlayer[];
|
|
220
229
|
/** `kbRuntime.controlsState()` — memorizado do lado de lá; uma chamada por tecla, como no original. */
|
|
221
230
|
getControls: () => ControlsSnapshot;
|
|
222
|
-
/**
|
|
223
|
-
|
|
231
|
+
/**
|
|
232
|
+
* O PAR DE `input/state`, E NÃO O CONJUNTO CRU (ADR-0109) — o mesmo corte que o `input/touch-bindings` fez.
|
|
233
|
+
*
|
|
234
|
+
* ⚠️ `ReadonlySet` e não `Set`, e a diferença É a razão de o par existir: LER o conjunto nunca foi o
|
|
235
|
+
* problema; ESCREVER nele apagava a origem. Com o tipo assim, uma escrita crua deixa de compilar — o crivo
|
|
236
|
+
* do inventário passa a ter o compilador do lado dele, em vez de ser a única coisa a segurar a linha.
|
|
237
|
+
*/
|
|
238
|
+
readonly heldKeys: ReadonlySet<string>;
|
|
239
|
+
/** Uma tecla foi segurada, e sabe-se por quem. */
|
|
240
|
+
marcarTecla: (code: string, origem: Transporte) => void;
|
|
241
|
+
/**
|
|
242
|
+
* Uma tecla foi segurada e NÃO se sabe por quem — o evento sintético que ninguém assinou.
|
|
243
|
+
*
|
|
244
|
+
* ⚠️ Está no ctx a par das outras duas de propósito: se fosse importada, um consumidor não teria como ver
|
|
245
|
+
* que ela existe, e é justamente ele quem produz os eventos que caem aqui.
|
|
246
|
+
*/
|
|
247
|
+
marcarTeclaSemOrigem: (code: string) => void;
|
|
248
|
+
soltarTecla: (code: string) => void;
|
|
224
249
|
/** `let oneButton` do game.js (empatia motora) → getter. */
|
|
225
250
|
isOneButton: () => boolean;
|
|
226
251
|
actionOf: (code: string, playerIndex: number) => string | null;
|
|
@@ -113,7 +113,9 @@ export function isJumpKey(code, s) {
|
|
|
113
113
|
* jogador. NÃO inclui os atalhos do Fácil (o original também não: por isso `easyKey` é somado à parte). */
|
|
114
114
|
export function isGameKeyCode(code, s) {
|
|
115
115
|
return s.controls.gameKeys.includes(code)
|
|
116
|
-
|
|
116
|
+
// `arr &&` porque uma posição sem alcance é `null` desde a #118 — e `null.includes` seria uma exceção
|
|
117
|
+
// no caminho do teclado, ou seja, o jogo a parar de responder a qualquer tecla.
|
|
118
|
+
|| s.players.some((p) => !!p.ctrl && Object.values(p.ctrl).some((arr) => !!arr && arr.includes(code)));
|
|
117
119
|
}
|
|
118
120
|
/** Os atalhos de acessibilidade do Fácil só existem SOLO (`numPlayers<=1`) e só para o Jogador 1. */
|
|
119
121
|
export function isEasyShortcut(code, s) {
|
|
@@ -138,6 +140,10 @@ export function titleNavOf(code, s, action2) {
|
|
|
138
140
|
}
|
|
139
141
|
/** Alguma intenção foi expressa? Definição única em input/edges; aqui só o nome que este módulo sempre teve. */
|
|
140
142
|
import { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
143
|
+
// ⚠️ IMPORTADA E NÃO INJECTADA, ao contrário dos três escritores logo abaixo, e a linha que separa os dois é
|
|
144
|
+
// esta: `origemDoEvento` é uma função PURA do evento — não toca estado nenhum que o cartucho possua. Os
|
|
145
|
+
// escritores tocam o `input/state`, que é mutado in-place e partilhado, e é por isso que continuam a entrar.
|
|
146
|
+
import { origemDoEvento } from './origem-sintetica.js';
|
|
141
147
|
export { hasNavIntent as hasTitleIntent } from './edges.js';
|
|
142
148
|
/**
|
|
143
149
|
* Quem é o dono do modal que esta tecla comanda? Devolve a POSIÇÃO no array, ou -1.
|
|
@@ -287,7 +293,7 @@ export function initKeydown(ctx) {
|
|
|
287
293
|
};
|
|
288
294
|
}
|
|
289
295
|
/** A metade IMPURA: pega a decisão pronta e a carimba no mundo. */
|
|
290
|
-
function apply(d, code) {
|
|
296
|
+
function apply(d, code, origem) {
|
|
291
297
|
switch (d.kind) {
|
|
292
298
|
case 'overlay':
|
|
293
299
|
if (d.closeId)
|
|
@@ -354,8 +360,14 @@ export function initKeydown(ctx) {
|
|
|
354
360
|
p[edge] = true;
|
|
355
361
|
}
|
|
356
362
|
for (const k of d.releaseKeys)
|
|
357
|
-
ctx.
|
|
358
|
-
|
|
363
|
+
ctx.soltarTecla(k);
|
|
364
|
+
// ⚠️ A ORIGEM CHEGA DO EVENTO E NÃO É INVENTADA AQUI (ADR-0109). `origem` é `undefined` só para o
|
|
365
|
+
// evento sintético que ninguém assinou — e nesse caso a porta estreita APAGA a entrada anterior, em
|
|
366
|
+
// vez de deixar a tecla herdar de quem a segurou da última vez. Ver `marcarTeclaSemOrigem`.
|
|
367
|
+
if (origem)
|
|
368
|
+
ctx.marcarTecla(code, origem);
|
|
369
|
+
else
|
|
370
|
+
ctx.marcarTeclaSemOrigem(code);
|
|
359
371
|
return;
|
|
360
372
|
}
|
|
361
373
|
}
|
|
@@ -370,9 +382,11 @@ export function initKeydown(ctx) {
|
|
|
370
382
|
const d = decideKeydown(e, snapshot());
|
|
371
383
|
if (d.preventDefault)
|
|
372
384
|
e.preventDefault();
|
|
373
|
-
apply(d, e.code);
|
|
385
|
+
apply(d, e.code, origemDoEvento(e));
|
|
374
386
|
}
|
|
375
|
-
|
|
387
|
+
// ⚠️ SOLTA NOS DOIS. Um `keys.delete` cru deixava a origem para trás, e um mapa que descreve teclas que já
|
|
388
|
+
// ninguém segura responde à alternância com o aparelho errado — sem erro, e só na aresta seguinte.
|
|
389
|
+
function onKeyup(e) { ctx.soltarTecla(e.code); }
|
|
376
390
|
function attach() {
|
|
377
391
|
ctx.win.addEventListener('keydown', onKeydown);
|
|
378
392
|
ctx.win.addEventListener('keyup', onKeyup);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* Os transportes que emitem UM COMANDO DE CADA VEZ, e em que a alternância está sempre ligada.
|
|
4
|
+
*
|
|
5
|
+
* Os nomes são os que a barra rápida já usa para os ícones (`face`, `eyes`, `voice`), mais `gestos`, que é o
|
|
6
|
+
* quarto que o ADR-0104 §C nomeia. Ficam em português como o resto do vocabulário de transporte
|
|
7
|
+
* (`teclado`, `toque`) — `transportesPadrao` já mistura, e mudar isso é outra conversa.
|
|
8
|
+
*/
|
|
9
|
+
export declare const UM_COMANDO_DE_CADA_VEZ: ReadonlySet<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Neste transporte a alternância está sempre ligada?
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ «Sempre ligada» e «ligada por omissão» são coisas diferentes, e a diferença é a que o ADR-0104 §C faz:
|
|
14
|
+
* um padrão pode ser mudado, e mudá-lo aqui deixaria o controle inutilizável. Por isso o valor guardado nem
|
|
15
|
+
* chega a ser lido nestes transportes — ver `alternanciaDe`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function alternanciaSempreLigada(transporte: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* A opção deve ser OFERECIDA para este transporte?
|
|
20
|
+
*
|
|
21
|
+
* O contrário de `alternanciaSempreLigada`, e existe com nome próprio porque quem pergunta é outro: um
|
|
22
|
+
* chama para decidir o estado, o outro para decidir se desenha o botão. Um painel que desenhasse o botão e
|
|
23
|
+
* ignorasse o clique seria pior do que não o desenhar.
|
|
24
|
+
*/
|
|
25
|
+
export declare function alternanciaEhEscolha(transporte: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* A chave de armazenamento da alternância, agora com o transporte no nome.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ FUNÇÃO, e não concatenação no ponto de uso, pela razão que o `platform/storage` já escreveu sobre as
|
|
30
|
+
* chaves por jogador: «virar função aqui é o que impede que um deles escreva num nome torto». Não é
|
|
31
|
+
* hipótese — o `ui/settings-motor` reescrevia `'incl_togglerun_p' + i` à mão, com um comentário ao lado a
|
|
32
|
+
* dizer «== toggleRunP de platform/storage». Duas cópias de um nome mudam uma de cada vez.
|
|
33
|
+
*
|
|
34
|
+
* `base` é `togglemove` ou `togglerun`, os dois nomes que já existem no armazenamento da criança.
|
|
35
|
+
*/
|
|
36
|
+
export declare function chaveDaAlternancia(base: string, jogador: number, transporte: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* A chave ANTIGA, por jogador e sem transporte. Continua a ser lida, e nunca mais escrita.
|
|
39
|
+
*
|
|
40
|
+
* ⚠️ ELA HERDA PARA TODOS OS TRANSPORTES, e a escolha custa uma frase a explicar. O valor velho foi posto
|
|
41
|
+
* pela criança nalgum contexto, e não há como saber qual — a chave não o registava, que é o defeito. As
|
|
42
|
+
* saídas eram três: perder o ajuste dela, adivinhar um transporte, ou herdar para todos. Herdar para todos é
|
|
43
|
+
* a única que não tira nada a quem depende do ajuste, e o vazamento que ela mantém dura só até a criança
|
|
44
|
+
* mexer no assunto uma vez em cada aparelho. Perder o ajuste custaria mais, e a quem menos pode pagar.
|
|
45
|
+
*
|
|
46
|
+
* É também o padrão que este repositório já escolheu para este mesmo valor: o `KEYS.toggleMoveLegacy` existe
|
|
47
|
+
* desde a migração anterior, e a nota do `platform/storage` diz porquê — «a chave velha fica onde está: é
|
|
48
|
+
* dado da criança, não meu para apagar, e a sua permanência é o que torna um retorno possível».
|
|
49
|
+
*/
|
|
50
|
+
export declare function chaveLegadaDaAlternancia(base: string, jogador: number): string;
|
|
51
|
+
/** O que se sabe ao resolver a alternância de um transporte. */
|
|
52
|
+
export interface LeituraDaAlternancia {
|
|
53
|
+
/** O que está guardado para ESTE transporte. `null` = nunca foi escrito. */
|
|
54
|
+
readonly doTransporte: boolean | null;
|
|
55
|
+
/** O que está guardado na chave antiga, sem transporte. `null` = nunca foi escrito. */
|
|
56
|
+
readonly doLegado: boolean | null;
|
|
57
|
+
/** O padrão de fábrica (`DEFAULTS.toggleMove` / `DEFAULTS.toggleRun`). */
|
|
58
|
+
readonly padrao: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A alternância deste transporte, resolvida.
|
|
62
|
+
*
|
|
63
|
+
* A ordem é: transporte de um comando → SEMPRE ligada, e nem se lê o resto · valor deste transporte · valor
|
|
64
|
+
* legado · padrão de fábrica.
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ O TRANSPORTE DE UM COMANDO VEM PRIMEIRO, e não por atalho: se ele lesse o guardado primeiro, uma
|
|
67
|
+
* criança que tivesse desligado a alternância no teclado herdaria esse `false` pelo legado e ficaria com um
|
|
68
|
+
* controle de olhar que não responde — o pior defeito possível, no controle de quem tem menos alternativas.
|
|
69
|
+
*/
|
|
70
|
+
export declare function alternanciaDe(transporte: string, l: LeituraDaAlternancia): boolean;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/latch-scope — A ALTERNÂNCIA É DE UM TRANSPORTE, e não da criança (ADR-0104 §C, issue #114).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O DEFEITO QUE ISTO CONSERTA, E ELE NÃO TINHA NOME =========================
|
|
5
|
+
// «Segurar vira alternar» estava guardado POR JOGADOR — `incl_togglemove_p0` —, o que quer dizer: por
|
|
6
|
+
// pessoa, e para todos os aparelhos ao mesmo tempo. Uma criança que liga a alternância no controle de TELA,
|
|
7
|
+
// porque num botão virtual ninguém segura com conforto, liga-a também no teclado, onde segurar uma tecla é
|
|
8
|
+
// exactamente o que ela sabe fazer. Ela não pediu isso e nada lho diz.
|
|
9
|
+
//
|
|
10
|
+
// ⚠️ E O REPOSITÓRIO JÁ CONHECIA O DEFEITO SEM O NOMEAR: o `core/state` traz a nota «a alternância do botão
|
|
11
|
+
// de CORRER nasce desligada de FÁBRICA — e liga sozinha no controle de tela, que é CONTEXTO e não escolha».
|
|
12
|
+
// Contexto é precisamente a palavra: o valor depende do aparelho em que a criança está. Guardá-lo por pessoa
|
|
13
|
+
// obrigava a distinguir «ligou porque quis» de «ligou porque é toque» com uma marca à parte (o ADR-0029), e
|
|
14
|
+
// essa marca existia para compensar uma chave que estava no escopo errado.
|
|
15
|
+
//
|
|
16
|
+
// O mapeamento de teclas já se guarda por transporte, e sempre se guardou. Esta é a mesma coisa.
|
|
17
|
+
//
|
|
18
|
+
// ========================= E PARA QUATRO TRANSPORTES ELA NÃO É ESCOLHA NENHUMA =========================
|
|
19
|
+
// ⚠️ Olhos, rosto, gestos e fala emitem UM COMANDO DE CADA VEZ. Não há como olhar para a esquerda e para o
|
|
20
|
+
// botão de pular ao mesmo tempo; não há como dizer duas palavras em simultâneo. Neles a alternância não é
|
|
21
|
+
// preferência — é a única forma de o controle funcionar, e oferecê-la como opção seria oferecer a uma
|
|
22
|
+
// criança a escolha de um controle que não funciona.
|
|
23
|
+
//
|
|
24
|
+
// Isto resolve, de passagem, uma tensão que o ADR-0084 tinha contra a sua própria regra «valor salvo
|
|
25
|
+
// significa escolha»: a alternância a ligar-se sozinha na webcam era uma excepção àquela regra. Deixa de
|
|
26
|
+
// ser, porque nestes transportes ela nunca foi um valor salvo — é uma propriedade do transporte.
|
|
27
|
+
//
|
|
28
|
+
// ⚠️ OS QUATRO AINDA NÃO EXISTEM COMO TRANSPORTE, e a regra fica escrita à mesma. Medido em 2026-09-08: o
|
|
29
|
+
// `transportesPadrao` devolve três (gamepad, teclado, toque), e o `ui/webcam` sintetiza `KeyboardEvent` — do
|
|
30
|
+
// ponto de vista da engine, ele É o teclado. Os três ícones da barra rápida dizem-no: `face`, `eyes` e
|
|
31
|
+
// `voice` estão marcados `soon`. Escrever a regra agora custa nada e faz com que eles cheguem COBERTOS, em
|
|
32
|
+
// vez de chegarem a uma excepção que alguém terá de se lembrar de abrir.
|
|
33
|
+
//
|
|
34
|
+
// Módulo-folha: não importa nada, nem sequer o `platform/storage` cujas chaves ele monta.
|
|
35
|
+
/**
|
|
36
|
+
* Os transportes que emitem UM COMANDO DE CADA VEZ, e em que a alternância está sempre ligada.
|
|
37
|
+
*
|
|
38
|
+
* Os nomes são os que a barra rápida já usa para os ícones (`face`, `eyes`, `voice`), mais `gestos`, que é o
|
|
39
|
+
* quarto que o ADR-0104 §C nomeia. Ficam em português como o resto do vocabulário de transporte
|
|
40
|
+
* (`teclado`, `toque`) — `transportesPadrao` já mistura, e mudar isso é outra conversa.
|
|
41
|
+
*/
|
|
42
|
+
export const UM_COMANDO_DE_CADA_VEZ = new Set(['olhos', 'rosto', 'gestos', 'fala']);
|
|
43
|
+
/**
|
|
44
|
+
* Neste transporte a alternância está sempre ligada?
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ «Sempre ligada» e «ligada por omissão» são coisas diferentes, e a diferença é a que o ADR-0104 §C faz:
|
|
47
|
+
* um padrão pode ser mudado, e mudá-lo aqui deixaria o controle inutilizável. Por isso o valor guardado nem
|
|
48
|
+
* chega a ser lido nestes transportes — ver `alternanciaDe`.
|
|
49
|
+
*/
|
|
50
|
+
export function alternanciaSempreLigada(transporte) {
|
|
51
|
+
return UM_COMANDO_DE_CADA_VEZ.has(transporte);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A opção deve ser OFERECIDA para este transporte?
|
|
55
|
+
*
|
|
56
|
+
* O contrário de `alternanciaSempreLigada`, e existe com nome próprio porque quem pergunta é outro: um
|
|
57
|
+
* chama para decidir o estado, o outro para decidir se desenha o botão. Um painel que desenhasse o botão e
|
|
58
|
+
* ignorasse o clique seria pior do que não o desenhar.
|
|
59
|
+
*/
|
|
60
|
+
export function alternanciaEhEscolha(transporte) {
|
|
61
|
+
return !alternanciaSempreLigada(transporte);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A chave de armazenamento da alternância, agora com o transporte no nome.
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ FUNÇÃO, e não concatenação no ponto de uso, pela razão que o `platform/storage` já escreveu sobre as
|
|
67
|
+
* chaves por jogador: «virar função aqui é o que impede que um deles escreva num nome torto». Não é
|
|
68
|
+
* hipótese — o `ui/settings-motor` reescrevia `'incl_togglerun_p' + i` à mão, com um comentário ao lado a
|
|
69
|
+
* dizer «== toggleRunP de platform/storage». Duas cópias de um nome mudam uma de cada vez.
|
|
70
|
+
*
|
|
71
|
+
* `base` é `togglemove` ou `togglerun`, os dois nomes que já existem no armazenamento da criança.
|
|
72
|
+
*/
|
|
73
|
+
export function chaveDaAlternancia(base, jogador, transporte) {
|
|
74
|
+
return `incl_${base}_p${jogador}_${transporte}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A chave ANTIGA, por jogador e sem transporte. Continua a ser lida, e nunca mais escrita.
|
|
78
|
+
*
|
|
79
|
+
* ⚠️ ELA HERDA PARA TODOS OS TRANSPORTES, e a escolha custa uma frase a explicar. O valor velho foi posto
|
|
80
|
+
* pela criança nalgum contexto, e não há como saber qual — a chave não o registava, que é o defeito. As
|
|
81
|
+
* saídas eram três: perder o ajuste dela, adivinhar um transporte, ou herdar para todos. Herdar para todos é
|
|
82
|
+
* a única que não tira nada a quem depende do ajuste, e o vazamento que ela mantém dura só até a criança
|
|
83
|
+
* mexer no assunto uma vez em cada aparelho. Perder o ajuste custaria mais, e a quem menos pode pagar.
|
|
84
|
+
*
|
|
85
|
+
* É também o padrão que este repositório já escolheu para este mesmo valor: o `KEYS.toggleMoveLegacy` existe
|
|
86
|
+
* desde a migração anterior, e a nota do `platform/storage` diz porquê — «a chave velha fica onde está: é
|
|
87
|
+
* dado da criança, não meu para apagar, e a sua permanência é o que torna um retorno possível».
|
|
88
|
+
*/
|
|
89
|
+
export function chaveLegadaDaAlternancia(base, jogador) {
|
|
90
|
+
return `incl_${base}_p${jogador}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A alternância deste transporte, resolvida.
|
|
94
|
+
*
|
|
95
|
+
* A ordem é: transporte de um comando → SEMPRE ligada, e nem se lê o resto · valor deste transporte · valor
|
|
96
|
+
* legado · padrão de fábrica.
|
|
97
|
+
*
|
|
98
|
+
* ⚠️ O TRANSPORTE DE UM COMANDO VEM PRIMEIRO, e não por atalho: se ele lesse o guardado primeiro, uma
|
|
99
|
+
* criança que tivesse desligado a alternância no teclado herdaria esse `false` pelo legado e ficaria com um
|
|
100
|
+
* controle de olhar que não responde — o pior defeito possível, no controle de quem tem menos alternativas.
|
|
101
|
+
*/
|
|
102
|
+
export function alternanciaDe(transporte, l) {
|
|
103
|
+
if (alternanciaSempreLigada(transporte))
|
|
104
|
+
return true;
|
|
105
|
+
if (l.doTransporte !== null)
|
|
106
|
+
return l.doTransporte;
|
|
107
|
+
if (l.doLegado !== null)
|
|
108
|
+
return l.doLegado;
|
|
109
|
+
return l.padrao;
|
|
110
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type LeituraDaAlternancia } from './latch-scope.js';
|
|
3
|
+
/** O mínimo do `platform/storage` que isto precisa. Injectado, para o gate não precisar de um navegador. */
|
|
4
|
+
export interface ArmazemDaAlternancia {
|
|
5
|
+
/** ⚠️ O CRU, e não `getBool`. Ver `lerTriEstado`. */
|
|
6
|
+
get(chave: string, padrao?: null): string | null;
|
|
7
|
+
set(chave: string, valor: string): void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* TRÊS ESTADOS E NÃO DOIS: `true`, `false`, e NUNCA ESCRITO.
|
|
11
|
+
*
|
|
12
|
+
* 🔴 ESTA FUNÇÃO EXISTE PARA NÃO SE USAR `getBool`, e a diferença custa o ajuste de uma criança. O `getBool`
|
|
13
|
+
* colapsa «nunca escrito» em `false`. Com ele, o `doTransporte` de uma criança que nunca mexeu neste aparelho
|
|
14
|
+
* chegaria à regra como `false` — e a regra responde na PRIMEIRA linha que encontra um valor, logo devolveria
|
|
15
|
+
* `false` e **nunca consultaria a chave legada**, que é onde vive o ajuste que ela já tinha.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ É por isso que o `latch-scope` tipa os dois campos como `boolean | null` e tem um caso próprio a dizer
|
|
18
|
+
* que «`false` guardado é um VALOR, e não uma ausência». Este é o lado do armazenamento da mesma frase.
|
|
19
|
+
*/
|
|
20
|
+
export declare function lerTriEstado(armazem: ArmazemDaAlternancia, chave: string): boolean | null;
|
|
21
|
+
/**
|
|
22
|
+
* A LEITURA COMPLETA que a regra pede, montada a partir do armazenamento.
|
|
23
|
+
*
|
|
24
|
+
* `base` é `togglemove` ou `togglerun` — os dois nomes que já existem no armazenamento da criança.
|
|
25
|
+
*/
|
|
26
|
+
export declare function leituraDaAlternancia(armazem: ArmazemDaAlternancia, base: string, jogador: number, transporte: string, padrao: boolean): LeituraDaAlternancia;
|
|
27
|
+
/**
|
|
28
|
+
* A ALTERNÂNCIA DESTE JOGADOR NESTE TRANSPORTE — a pergunta inteira, numa chamada.
|
|
29
|
+
*
|
|
30
|
+
* 📌 TROCAR DE TRANSPORTE TROCA A RESPOSTA SEM ESCREVER NADA, que é a cláusula 1 do ADR-0113 em código: o
|
|
31
|
+
* valor pertence ao mapeamento do controle, como um caps-lock, e mudar de controle é mudar de mapeamento.
|
|
32
|
+
*/
|
|
33
|
+
export declare function alternanciaGuardada(armazem: ArmazemDaAlternancia, base: string, jogador: number, transporte: string, padrao: boolean): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* GRAVA A ESCOLHA DA CRIANÇA para o transporte em uso. Devolve se gravou.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ RECUSA NOS TRANSPORTES DE UM COMANDO, e a recusa é um `false` devolvido e não um lançamento: em olhos,
|
|
38
|
+
* rosto, gestos e fala a alternância é o que faz a entrada funcionar (ADR-0113 cláusula 3), então não há
|
|
39
|
+
* escolha a gravar. Quem chama usa a resposta para DESABILITAR o controle COM MOTIVO — que é a metade que
|
|
40
|
+
* falta e que vive na interface, não aqui.
|
|
41
|
+
*
|
|
42
|
+
* 📌 Gravar mesmo assim seria pior do que inútil: a criança mexeria no ícone, o valor iria para o disco, e o
|
|
43
|
+
* jogo continuaria a ignorá-lo — um controle que mente sobre ter funcionado.
|
|
44
|
+
*/
|
|
45
|
+
export declare function gravarAlternancia(escrever: (chave: string, ligada: boolean) => void, base: string, jogador: number, transporte: string, ligada: boolean): boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/latch-store.ts — A ALTERNÂNCIA, LIDA E ESCRITA NO ARMAZENAMENTO (ADR-0113).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE ESTE MÓDULO É, E POR QUE É SEPARADO =========================
|
|
5
|
+
// O `input/latch-scope` é a REGRA e não toca em nada: recebe uma `LeituraDaAlternancia` já feita e responde.
|
|
6
|
+
// Este módulo é a única coisa que faltava entre ela e o mundo — quem vai ao armazenamento buscar os três
|
|
7
|
+
// valores que a regra pede, e quem grava o que a criança escolhe.
|
|
8
|
+
//
|
|
9
|
+
// 📌 SEPARADO DE PROPÓSITO, e não por arrumação: a regra é pura e tem gate próprio; misturar armazenamento
|
|
10
|
+
// dentro dela obrigaria todo caso da regra a montar um `localStorage` de mentira para afirmar uma coisa que
|
|
11
|
+
// não depende dele. É a mesma divisão que o `render/viz-axes` (modelo) e o `render/viz-setters` (escrita)
|
|
12
|
+
// já fazem neste repositório.
|
|
13
|
+
//
|
|
14
|
+
// ⚠️ E O QUE ELE NÃO FAZ: não sabe QUAL transporte está em uso. Isso é do `input/transporte-em-uso`, e chega
|
|
15
|
+
// aqui como argumento. Um módulo de armazenamento que adivinhasse o transporte escreveria a escolha de uma
|
|
16
|
+
// criança na chave de outro aparelho — em silêncio, que é o defeito que o ADR-0113 existe para evitar.
|
|
17
|
+
import { chaveDaAlternancia, chaveLegadaDaAlternancia, alternanciaDe, alternanciaEhEscolha, } from './latch-scope.js';
|
|
18
|
+
/**
|
|
19
|
+
* TRÊS ESTADOS E NÃO DOIS: `true`, `false`, e NUNCA ESCRITO.
|
|
20
|
+
*
|
|
21
|
+
* 🔴 ESTA FUNÇÃO EXISTE PARA NÃO SE USAR `getBool`, e a diferença custa o ajuste de uma criança. O `getBool`
|
|
22
|
+
* colapsa «nunca escrito» em `false`. Com ele, o `doTransporte` de uma criança que nunca mexeu neste aparelho
|
|
23
|
+
* chegaria à regra como `false` — e a regra responde na PRIMEIRA linha que encontra um valor, logo devolveria
|
|
24
|
+
* `false` e **nunca consultaria a chave legada**, que é onde vive o ajuste que ela já tinha.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ É por isso que o `latch-scope` tipa os dois campos como `boolean | null` e tem um caso próprio a dizer
|
|
27
|
+
* que «`false` guardado é um VALOR, e não uma ausência». Este é o lado do armazenamento da mesma frase.
|
|
28
|
+
*/
|
|
29
|
+
export function lerTriEstado(armazem, chave) {
|
|
30
|
+
const v = armazem.get(chave, null);
|
|
31
|
+
return v == null ? null : v === '1';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A LEITURA COMPLETA que a regra pede, montada a partir do armazenamento.
|
|
35
|
+
*
|
|
36
|
+
* `base` é `togglemove` ou `togglerun` — os dois nomes que já existem no armazenamento da criança.
|
|
37
|
+
*/
|
|
38
|
+
export function leituraDaAlternancia(armazem, base, jogador, transporte, padrao) {
|
|
39
|
+
return {
|
|
40
|
+
doTransporte: lerTriEstado(armazem, chaveDaAlternancia(base, jogador, transporte)),
|
|
41
|
+
doLegado: lerTriEstado(armazem, chaveLegadaDaAlternancia(base, jogador)),
|
|
42
|
+
padrao,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A ALTERNÂNCIA DESTE JOGADOR NESTE TRANSPORTE — a pergunta inteira, numa chamada.
|
|
47
|
+
*
|
|
48
|
+
* 📌 TROCAR DE TRANSPORTE TROCA A RESPOSTA SEM ESCREVER NADA, que é a cláusula 1 do ADR-0113 em código: o
|
|
49
|
+
* valor pertence ao mapeamento do controle, como um caps-lock, e mudar de controle é mudar de mapeamento.
|
|
50
|
+
*/
|
|
51
|
+
export function alternanciaGuardada(armazem, base, jogador, transporte, padrao) {
|
|
52
|
+
return alternanciaDe(transporte, leituraDaAlternancia(armazem, base, jogador, transporte, padrao));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* GRAVA A ESCOLHA DA CRIANÇA para o transporte em uso. Devolve se gravou.
|
|
56
|
+
*
|
|
57
|
+
* ⚠️ RECUSA NOS TRANSPORTES DE UM COMANDO, e a recusa é um `false` devolvido e não um lançamento: em olhos,
|
|
58
|
+
* rosto, gestos e fala a alternância é o que faz a entrada funcionar (ADR-0113 cláusula 3), então não há
|
|
59
|
+
* escolha a gravar. Quem chama usa a resposta para DESABILITAR o controle COM MOTIVO — que é a metade que
|
|
60
|
+
* falta e que vive na interface, não aqui.
|
|
61
|
+
*
|
|
62
|
+
* 📌 Gravar mesmo assim seria pior do que inútil: a criança mexeria no ícone, o valor iria para o disco, e o
|
|
63
|
+
* jogo continuaria a ignorá-lo — um controle que mente sobre ter funcionado.
|
|
64
|
+
*/
|
|
65
|
+
// ⚠️ RECEBE O ESCRITOR E NÃO UM ARMAZÉM, e a mudança é de 2026-09-08, quando o painel foi ligar-se a isto.
|
|
66
|
+
// O `ui/settings-motor` já tem um `store: { setBool }` injectado — exigir-lhe um objecto com `get`/`set`
|
|
67
|
+
// crus obrigaria a inventar um adaptador no ponto de uso, e um adaptador ali é onde uma segunda forma de
|
|
68
|
+
// escrever a mesma chave nasce. Uma função é o mínimo que a escrita precisa.
|
|
69
|
+
export function gravarAlternancia(escrever, base, jogador, transporte, ligada) {
|
|
70
|
+
if (!alternanciaEhEscolha(transporte))
|
|
71
|
+
return false;
|
|
72
|
+
escrever(chaveDaAlternancia(base, jogador, transporte), ligada);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { Transporte } from './transporte-em-uso.js';
|
|
3
|
+
/**
|
|
4
|
+
* A propriedade pendurada no evento.
|
|
5
|
+
*
|
|
6
|
+
* 📌 Prefixada e feia de propósito: é um expando num objecto que não é nosso, e um nome curto («origem»)
|
|
7
|
+
* podia colidir com o de outra biblioteca sem que nada o dissesse.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CHAVE_DE_ORIGEM = "__vpOrigem";
|
|
10
|
+
/**
|
|
11
|
+
* O mínimo que este módulo lê de um evento de tecla — ESTRUTURAL, para um `KeyboardEvent` real e um duplo de
|
|
12
|
+
* teste servirem os dois.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ `isTrusted` é OPCIONAL, e a ausência dele não é o mesmo que `false` por acaso: um duplo que não o declara
|
|
15
|
+
* está a dizer «não afirmei nada sobre isto», e a resposta certa a isso é `undefined` e não `'teclado'`. É a
|
|
16
|
+
* mesma regra do `origemDe` do `input/state`, aplicada uma camada acima.
|
|
17
|
+
*/
|
|
18
|
+
export interface EventoDeTeclaLike {
|
|
19
|
+
readonly isTrusted?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* DECLARA que este evento veio daquele aparelho. Devolve o próprio evento, para o despacho ficar numa linha.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ Carimba-se ANTES de despachar. Depois de `dispatchEvent` os ouvintes já correram, e o carimbo chegaria
|
|
25
|
+
* a um evento que ninguém mais vai ler.
|
|
26
|
+
*/
|
|
27
|
+
export declare function carimbarOrigem<T extends object>(ev: T, origem: Transporte): T;
|
|
28
|
+
/**
|
|
29
|
+
* QUEM PRODUZIU ESTE EVENTO? `undefined` quando não se sabe.
|
|
30
|
+
*
|
|
31
|
+
* A regra inteira, em três linhas e por esta ordem:
|
|
32
|
+
*
|
|
33
|
+
* 1. **Carimbo válido ganha.** Uma declaração explícita vence sempre uma inferência — inverter isto faria
|
|
34
|
+
* um evento REAL que alguém reatribuiu (um pedal, um interruptor de sopro que emite teclas de verdade)
|
|
35
|
+
* ser lido como teclado, apagando exactamente a informação que quem carimbou se deu ao trabalho de pôr.
|
|
36
|
+
* 2. **Sem carimbo mas de confiança → `'teclado'`.** É o que `isTrusted` significa: o navegador viu a
|
|
37
|
+
* pessoa carregar. É a única inferência que este módulo faz, e fá-la sobre a propriedade que não se forja.
|
|
38
|
+
* 3. **Sem carimbo e sem confiança → `undefined`.** Um evento sintético que ninguém assinou. Depois desta
|
|
39
|
+
* migração nada nesta engine produz um; quem o produz é código de fora, e código de fora não declarou.
|
|
40
|
+
* ⚠️ Devolver `'teclado'` aqui seria a erasão a voltar por outra porta, que é o defeito que o ADR-0109
|
|
41
|
+
* inteiro existe para fechar — e seria pior do que a erasão original, porque teria a forma de uma
|
|
42
|
+
* resposta.
|
|
43
|
+
*/
|
|
44
|
+
export declare function origemDoEvento(ev: EventoDeTeclaLike): Transporte | undefined;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/origem-sintetica.ts — QUEM DESPACHOU ESTA TECLA, quando não foi um dedo num teclado (ADR-0109).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O PONTO DIFÍCIL DA ARESTA, E ELE ESTAVA NOMEADO =========================
|
|
5
|
+
// O crivo `tests/origem-da-tecla` carrega esta frase na entrada do `input/keydown` desde que o
|
|
6
|
+
// estrangulamento começou: «a webcam despacha `KeyboardEvent` SINTÉTICO, entra pelo `keydown` e seria
|
|
7
|
+
// carimbada `teclado` — a erasão a voltar pela porta da frente». O `ui/webcam` constrói um `KeyboardEvent` e
|
|
8
|
+
// despacha-o na janela (`ui/webcam.ts`, `eyeSet`), e do lado de lá ele é indistinguível de uma tecla premida.
|
|
9
|
+
//
|
|
10
|
+
// `isTrusted` distingue PREMIDA de DESPACHADA — é a única propriedade que um script não consegue forjar — mas
|
|
11
|
+
// não diz QUAL transporte assistido despachou. Isso tem de chegar DECLARADO, e é o que este módulo é.
|
|
12
|
+
//
|
|
13
|
+
// ⚠️ POR QUE O CARIMBO VIAJA NO EVENTO, e não num «qual é a fonte sintética agora?» injectado. A resposta
|
|
14
|
+
// injectada é um estado GLOBAL, e a entrada não é global: uma criança que joga por olhar e tem um adulto a
|
|
15
|
+
// carregar numa tecla ao lado produz as duas arestas no mesmo instante, e o estado global carimbaria as duas
|
|
16
|
+
// como olhar. O evento não se confunde consigo próprio — a origem anda com a aresta a que pertence, que é a
|
|
17
|
+
// mesma razão por que o `origemDaTecla` é um mapa por CÓDIGO e não um campo só.
|
|
18
|
+
//
|
|
19
|
+
// 📌 E é aditivo por construção: um evento sem carimbo continua a funcionar. Foi isso que permitiu migrar os
|
|
20
|
+
// escritores um a um sem nenhum commit vermelho pelo meio.
|
|
21
|
+
import { ehTransporte } from './transporte-em-uso.js';
|
|
22
|
+
/**
|
|
23
|
+
* A propriedade pendurada no evento.
|
|
24
|
+
*
|
|
25
|
+
* 📌 Prefixada e feia de propósito: é um expando num objecto que não é nosso, e um nome curto («origem»)
|
|
26
|
+
* podia colidir com o de outra biblioteca sem que nada o dissesse.
|
|
27
|
+
*/
|
|
28
|
+
export const CHAVE_DE_ORIGEM = '__vpOrigem';
|
|
29
|
+
/**
|
|
30
|
+
* DECLARA que este evento veio daquele aparelho. Devolve o próprio evento, para o despacho ficar numa linha.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ Carimba-se ANTES de despachar. Depois de `dispatchEvent` os ouvintes já correram, e o carimbo chegaria
|
|
33
|
+
* a um evento que ninguém mais vai ler.
|
|
34
|
+
*/
|
|
35
|
+
export function carimbarOrigem(ev, origem) {
|
|
36
|
+
ev[CHAVE_DE_ORIGEM] = origem;
|
|
37
|
+
return ev;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* QUEM PRODUZIU ESTE EVENTO? `undefined` quando não se sabe.
|
|
41
|
+
*
|
|
42
|
+
* A regra inteira, em três linhas e por esta ordem:
|
|
43
|
+
*
|
|
44
|
+
* 1. **Carimbo válido ganha.** Uma declaração explícita vence sempre uma inferência — inverter isto faria
|
|
45
|
+
* um evento REAL que alguém reatribuiu (um pedal, um interruptor de sopro que emite teclas de verdade)
|
|
46
|
+
* ser lido como teclado, apagando exactamente a informação que quem carimbou se deu ao trabalho de pôr.
|
|
47
|
+
* 2. **Sem carimbo mas de confiança → `'teclado'`.** É o que `isTrusted` significa: o navegador viu a
|
|
48
|
+
* pessoa carregar. É a única inferência que este módulo faz, e fá-la sobre a propriedade que não se forja.
|
|
49
|
+
* 3. **Sem carimbo e sem confiança → `undefined`.** Um evento sintético que ninguém assinou. Depois desta
|
|
50
|
+
* migração nada nesta engine produz um; quem o produz é código de fora, e código de fora não declarou.
|
|
51
|
+
* ⚠️ Devolver `'teclado'` aqui seria a erasão a voltar por outra porta, que é o defeito que o ADR-0109
|
|
52
|
+
* inteiro existe para fechar — e seria pior do que a erasão original, porque teria a forma de uma
|
|
53
|
+
* resposta.
|
|
54
|
+
*/
|
|
55
|
+
export function origemDoEvento(ev) {
|
|
56
|
+
const declarada = ev[CHAVE_DE_ORIGEM];
|
|
57
|
+
if (ehTransporte(declarada))
|
|
58
|
+
return declarada;
|
|
59
|
+
return ev.isTrusted ? 'teclado' : undefined;
|
|
60
|
+
}
|