@the-inclusionist/engine 9.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -78
- package/app/css/style.css +761 -377
- package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
- package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
- package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
- package/app/public/vendor/fonts.css +36 -84
- package/dist-pkg/boot/create-game.d.ts +499 -219
- package/dist-pkg/boot/create-game.js +3944 -562
- package/dist-pkg/core/a11y-sr.d.ts +25 -3
- package/dist-pkg/core/a11y-sr.js +38 -19
- package/dist-pkg/core/accessible-label.d.ts +17 -0
- package/dist-pkg/core/accessible-label.js +37 -0
- package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
- package/dist-pkg/core/accommodation-subjects.js +32 -0
- package/dist-pkg/core/accommodations.d.ts +125 -0
- package/dist-pkg/core/accommodations.js +195 -0
- package/dist-pkg/core/actions.d.ts +108 -55
- package/dist-pkg/core/actions.js +122 -71
- package/dist-pkg/core/calm-mode.d.ts +31 -0
- package/dist-pkg/core/calm-mode.js +47 -0
- package/dist-pkg/core/camera-cycle.d.ts +15 -0
- package/dist-pkg/core/camera-cycle.js +7 -0
- package/dist-pkg/core/caption-duration.d.ts +15 -0
- package/dist-pkg/core/caption-duration.js +19 -0
- package/dist-pkg/core/cartridge-problems.d.ts +34 -0
- package/dist-pkg/core/cartridge-problems.js +53 -0
- package/dist-pkg/core/constants.d.ts +0 -54
- package/dist-pkg/core/constants.js +9 -76
- package/dist-pkg/core/contract.d.ts +142 -164
- package/dist-pkg/core/contract.js +222 -208
- package/dist-pkg/core/dom-query.d.ts +4 -4
- package/dist-pkg/core/dom-query.js +12 -20
- package/dist-pkg/core/entity.d.ts +62 -77
- package/dist-pkg/core/entity.js +18 -32
- package/dist-pkg/core/escape-html.d.ts +9 -10
- package/dist-pkg/core/escape-html.js +16 -20
- package/dist-pkg/core/flash-threshold.d.ts +17 -0
- package/dist-pkg/core/flash-threshold.js +113 -0
- package/dist-pkg/core/game-speed.d.ts +11 -0
- package/dist-pkg/core/game-speed.js +16 -0
- package/dist-pkg/core/genres.d.ts +20 -0
- package/dist-pkg/core/genres.js +44 -0
- package/dist-pkg/core/i18n.d.ts +71 -60
- package/dist-pkg/core/i18n.js +186 -200
- package/dist-pkg/core/loop.d.ts +17 -6
- package/dist-pkg/core/loop.js +26 -26
- package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
- package/dist-pkg/core/pause-icon-catalogue.js +54 -0
- package/dist-pkg/core/ring.d.ts +17 -0
- package/dist-pkg/core/ring.js +29 -0
- package/dist-pkg/core/rng.d.ts +7 -12
- package/dist-pkg/core/rng.js +23 -53
- package/dist-pkg/core/route.d.ts +26 -26
- package/dist-pkg/core/route.js +126 -121
- package/dist-pkg/core/scenes.d.ts +37 -40
- package/dist-pkg/core/scenes.js +41 -43
- package/dist-pkg/core/screens.d.ts +4 -4
- package/dist-pkg/core/screens.js +11 -16
- package/dist-pkg/core/session-clock.d.ts +23 -0
- package/dist-pkg/core/session-clock.js +37 -0
- package/dist-pkg/core/setting-defaults.d.ts +61 -0
- package/dist-pkg/core/setting-defaults.js +66 -0
- package/dist-pkg/core/speech-rate.d.ts +29 -0
- package/dist-pkg/core/speech-rate.js +47 -0
- package/dist-pkg/core/state.d.ts +118 -118
- package/dist-pkg/core/state.js +322 -297
- package/dist-pkg/core/visual-cycles.d.ts +30 -0
- package/dist-pkg/core/visual-cycles.js +52 -0
- package/dist-pkg/core/visual-state.d.ts +13 -0
- package/dist-pkg/core/visual-state.js +6 -0
- package/dist-pkg/educational/activities-registry.d.ts +1 -1
- package/dist-pkg/educational/activities-registry.js +9 -8
- package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
- package/dist-pkg/educational/adaptive-engine.js +17 -17
- package/dist-pkg/educational/segment-bar.d.ts +12 -12
- package/dist-pkg/educational/segment-bar.js +9 -9
- package/dist-pkg/i18n/en.js +324 -223
- package/dist-pkg/i18n/es.js +323 -223
- package/dist-pkg/i18n/pt.js +348 -226
- package/dist-pkg/input/default-bindings.d.ts +37 -38
- package/dist-pkg/input/default-bindings.js +127 -144
- package/dist-pkg/input/devices.d.ts +7 -7
- package/dist-pkg/input/devices.js +17 -16
- package/dist-pkg/input/edges.d.ts +23 -32
- package/dist-pkg/input/edges.js +23 -28
- package/dist-pkg/input/empathy-filter.d.ts +22 -0
- package/dist-pkg/input/empathy-filter.js +34 -0
- package/dist-pkg/input/face-map.d.ts +96 -0
- package/dist-pkg/input/face-map.js +255 -0
- package/dist-pkg/input/face-signals.d.ts +40 -0
- package/dist-pkg/input/face-signals.js +48 -0
- package/dist-pkg/input/gamepad.d.ts +195 -145
- package/dist-pkg/input/gamepad.js +383 -556
- package/dist-pkg/input/gaze-cycle.d.ts +64 -0
- package/dist-pkg/input/gaze-cycle.js +133 -0
- package/dist-pkg/input/gaze-relative.d.ts +68 -0
- package/dist-pkg/input/gaze-relative.js +142 -0
- package/dist-pkg/input/hand-map.d.ts +28 -0
- package/dist-pkg/input/hand-map.js +142 -0
- package/dist-pkg/input/input-cooldown.d.ts +17 -0
- package/dist-pkg/input/input-cooldown.js +39 -0
- package/dist-pkg/input/key-default.d.ts +30 -0
- package/dist-pkg/input/key-default.js +60 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
- package/dist-pkg/input/keyboard-runtime.js +23 -27
- package/dist-pkg/input/keyboard.d.ts +68 -63
- package/dist-pkg/input/keyboard.js +83 -105
- package/dist-pkg/input/keydown.d.ts +123 -137
- package/dist-pkg/input/keydown.js +284 -289
- package/dist-pkg/input/latch-edge.d.ts +33 -21
- package/dist-pkg/input/latch-edge.js +34 -35
- package/dist-pkg/input/latch-scope.d.ts +51 -50
- package/dist-pkg/input/latch-scope.js +61 -82
- package/dist-pkg/input/latch-store.d.ts +26 -30
- package/dist-pkg/input/latch-store.js +42 -50
- package/dist-pkg/input/latch-sync.d.ts +29 -29
- package/dist-pkg/input/latch-sync.js +25 -25
- package/dist-pkg/input/latch.d.ts +14 -17
- package/dist-pkg/input/latch.js +29 -37
- package/dist-pkg/input/pad-defaults.d.ts +13 -16
- package/dist-pkg/input/pad-defaults.js +21 -28
- package/dist-pkg/input/pad-reading.d.ts +76 -0
- package/dist-pkg/input/pad-reading.js +169 -0
- package/dist-pkg/input/pad-wizard.d.ts +99 -0
- package/dist-pkg/input/pad-wizard.js +256 -0
- package/dist-pkg/input/pointer-space.d.ts +19 -20
- package/dist-pkg/input/pointer-space.js +27 -32
- package/dist-pkg/input/pointer.d.ts +39 -40
- package/dist-pkg/input/pointer.js +42 -45
- package/dist-pkg/input/state.d.ts +101 -89
- package/dist-pkg/input/state.js +54 -134
- package/dist-pkg/input/switch-scan.d.ts +59 -0
- package/dist-pkg/input/switch-scan.js +67 -0
- package/dist-pkg/input/synthetic-source.d.ts +42 -0
- package/dist-pkg/input/synthetic-source.js +57 -0
- package/dist-pkg/input/touch-bindings.d.ts +127 -116
- package/dist-pkg/input/touch-bindings.js +158 -173
- package/dist-pkg/input/touch.d.ts +147 -72
- package/dist-pkg/input/touch.js +362 -126
- package/dist-pkg/input/transport-in-use.d.ts +107 -0
- package/dist-pkg/input/transport-in-use.js +130 -0
- package/dist-pkg/input/transports.d.ts +90 -117
- package/dist-pkg/input/transports.js +104 -106
- package/dist-pkg/input/virtual-controller.d.ts +82 -0
- package/dist-pkg/input/virtual-controller.js +69 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
- package/dist-pkg/input/vocabulary-migration.js +80 -79
- package/dist-pkg/input/voice-map.d.ts +59 -0
- package/dist-pkg/input/voice-map.js +123 -0
- package/dist-pkg/platform/audio-ambient.d.ts +13 -2
- package/dist-pkg/platform/audio-ambient.js +14 -14
- package/dist-pkg/platform/audio-earcons.d.ts +20 -19
- package/dist-pkg/platform/audio-earcons.js +51 -42
- package/dist-pkg/platform/audio-jingles.js +9 -10
- package/dist-pkg/platform/audio-mixer.d.ts +14 -2
- package/dist-pkg/platform/audio-mixer.js +31 -39
- package/dist-pkg/platform/audio-sonar.d.ts +49 -152
- package/dist-pkg/platform/audio-sonar.js +124 -302
- package/dist-pkg/platform/audio.d.ts +62 -19
- package/dist-pkg/platform/audio.js +193 -168
- package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
- package/dist-pkg/platform/choose-by-voice.js +157 -0
- package/dist-pkg/platform/flash-sampler.d.ts +19 -0
- package/dist-pkg/platform/flash-sampler.js +79 -0
- package/dist-pkg/platform/font-library.d.ts +77 -0
- package/dist-pkg/platform/font-library.js +98 -0
- package/dist-pkg/platform/font-library.json +721 -0
- package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
- package/dist-pkg/platform/heavy-catalogue.js +256 -0
- package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
- package/dist-pkg/platform/heavy-mirror.js +73 -0
- package/dist-pkg/platform/heavy.d.ts +136 -0
- package/dist-pkg/platform/heavy.js +324 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
- package/dist-pkg/platform/interruptible-speech.js +77 -54
- package/dist-pkg/platform/kokoro-port.d.ts +27 -0
- package/dist-pkg/platform/kokoro-port.js +50 -0
- package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
- package/dist-pkg/platform/kokoro-runtime.js +52 -0
- package/dist-pkg/platform/kokoro.d.ts +70 -0
- package/dist-pkg/platform/kokoro.js +133 -0
- package/dist-pkg/platform/listener-scope.d.ts +12 -0
- package/dist-pkg/platform/listener-scope.js +90 -0
- package/dist-pkg/platform/locale-host.d.ts +14 -0
- package/dist-pkg/platform/locale-host.js +45 -0
- package/dist-pkg/platform/microphone.d.ts +51 -0
- package/dist-pkg/platform/microphone.js +118 -0
- package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
- package/dist-pkg/platform/onnx-runtime.js +42 -0
- package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
- package/dist-pkg/platform/reading-in-worker.js +125 -0
- package/dist-pkg/platform/reading-model.d.ts +94 -0
- package/dist-pkg/platform/reading-model.js +272 -0
- package/dist-pkg/platform/reading-runtime.d.ts +24 -0
- package/dist-pkg/platform/reading-runtime.js +164 -0
- package/dist-pkg/platform/reading-worker.d.ts +41 -0
- package/dist-pkg/platform/reading-worker.js +58 -0
- package/dist-pkg/platform/reading.d.ts +80 -0
- package/dist-pkg/platform/reading.js +136 -0
- package/dist-pkg/platform/speech-recognition.d.ts +60 -0
- package/dist-pkg/platform/speech-recognition.js +104 -0
- package/dist-pkg/platform/speech.d.ts +23 -1
- package/dist-pkg/platform/speech.js +29 -18
- package/dist-pkg/platform/storage-keys.d.ts +96 -0
- package/dist-pkg/platform/storage-keys.js +106 -0
- package/dist-pkg/platform/storage.d.ts +46 -111
- package/dist-pkg/platform/storage.js +104 -169
- package/dist-pkg/platform/tts.d.ts +85 -39
- package/dist-pkg/platform/tts.js +447 -127
- package/dist-pkg/platform/vision-loop.d.ts +23 -0
- package/dist-pkg/platform/vision-loop.js +56 -0
- package/dist-pkg/platform/vision.d.ts +132 -0
- package/dist-pkg/platform/vision.js +117 -0
- package/dist-pkg/platform/voice-listener.d.ts +40 -0
- package/dist-pkg/platform/voice-listener.js +88 -0
- package/dist-pkg/platform/voice-plan.d.ts +15 -98
- package/dist-pkg/platform/voice-plan.js +56 -143
- package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
- package/dist-pkg/platform/vosk-runtime.js +151 -0
- package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
- package/dist-pkg/platform/vosk-vocabulary.js +178 -0
- package/dist-pkg/render/canvas.d.ts +13 -7
- package/dist-pkg/render/canvas.js +13 -13
- package/dist-pkg/render/crt.d.ts +48 -12
- package/dist-pkg/render/crt.js +78 -80
- package/dist-pkg/render/cvd-matrices.d.ts +22 -18
- package/dist-pkg/render/cvd-matrices.js +37 -38
- package/dist-pkg/render/hc-role-data.d.ts +7 -7
- package/dist-pkg/render/hc-role-data.js +14 -14
- package/dist-pkg/render/high-contrast.d.ts +67 -56
- package/dist-pkg/render/high-contrast.js +226 -217
- package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
- package/dist-pkg/render/low-vision-drawing.js +46 -0
- package/dist-pkg/render/lq-filter.d.ts +32 -20
- package/dist-pkg/render/lq-filter.js +59 -65
- package/dist-pkg/render/port.d.ts +86 -88
- package/dist-pkg/render/port.js +17 -23
- package/dist-pkg/render/screen-pipeline.d.ts +42 -42
- package/dist-pkg/render/screen-pipeline.js +80 -87
- package/dist-pkg/render/sprite-fx.d.ts +2 -1
- package/dist-pkg/render/sprite-fx.js +16 -14
- package/dist-pkg/render/viewports.d.ts +20 -15
- package/dist-pkg/render/viewports.js +83 -114
- package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
- package/dist-pkg/render/viz-axes-labels.js +88 -0
- package/dist-pkg/render/viz-axes.d.ts +89 -86
- package/dist-pkg/render/viz-axes.js +128 -116
- package/dist-pkg/render/viz-modes.d.ts +28 -29
- package/dist-pkg/render/viz-modes.js +40 -45
- package/dist-pkg/render/viz-refusal.d.ts +32 -0
- package/dist-pkg/render/viz-refusal.js +57 -0
- package/dist-pkg/render/viz-setters.d.ts +104 -66
- package/dist-pkg/render/viz-setters.js +182 -191
- package/dist-pkg/ui/aac-sets.d.ts +45 -0
- package/dist-pkg/ui/aac-sets.js +41 -0
- package/dist-pkg/ui/audio-choices.d.ts +68 -0
- package/dist-pkg/ui/audio-choices.js +98 -0
- package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
- package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
- package/dist-pkg/ui/camera-control.d.ts +53 -0
- package/dist-pkg/ui/camera-control.js +48 -0
- package/dist-pkg/ui/changed-mark.d.ts +9 -8
- package/dist-pkg/ui/changed-mark.js +22 -53
- package/dist-pkg/ui/control-choices.d.ts +37 -0
- package/dist-pkg/ui/control-choices.js +84 -0
- package/dist-pkg/ui/debug-panel.d.ts +59 -43
- package/dist-pkg/ui/debug-panel.js +68 -69
- package/dist-pkg/ui/declared-words.d.ts +21 -0
- package/dist-pkg/ui/declared-words.js +66 -0
- package/dist-pkg/ui/dom.d.ts +10 -14
- package/dist-pkg/ui/dom.js +10 -47
- package/dist-pkg/ui/drawing-problems.d.ts +25 -0
- package/dist-pkg/ui/drawing-problems.js +94 -0
- package/dist-pkg/ui/eye-control.d.ts +36 -0
- package/dist-pkg/ui/eye-control.js +178 -0
- package/dist-pkg/ui/face-control.d.ts +25 -0
- package/dist-pkg/ui/face-control.js +183 -0
- package/dist-pkg/ui/focus-trap.d.ts +39 -49
- package/dist-pkg/ui/focus-trap.js +56 -65
- package/dist-pkg/ui/fonts.d.ts +157 -68
- package/dist-pkg/ui/fonts.js +305 -115
- package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
- package/dist-pkg/ui/footer-scroll-driver.js +201 -0
- package/dist-pkg/ui/footer-scroll.d.ts +38 -0
- package/dist-pkg/ui/footer-scroll.js +55 -0
- package/dist-pkg/ui/game-options.d.ts +47 -0
- package/dist-pkg/ui/game-options.js +158 -0
- package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
- package/dist-pkg/ui/gaze-overlay.js +165 -0
- package/dist-pkg/ui/hand-control.d.ts +25 -0
- package/dist-pkg/ui/hand-control.js +143 -0
- package/dist-pkg/ui/help-panel.d.ts +87 -0
- package/dist-pkg/ui/help-panel.js +242 -0
- package/dist-pkg/ui/hud-bands.d.ts +62 -0
- package/dist-pkg/ui/hud-bands.js +146 -0
- package/dist-pkg/ui/hud-row.d.ts +34 -0
- package/dist-pkg/ui/hud-row.js +80 -0
- package/dist-pkg/ui/hud.d.ts +73 -73
- package/dist-pkg/ui/hud.js +94 -98
- package/dist-pkg/ui/item-announcement.d.ts +13 -12
- package/dist-pkg/ui/item-announcement.js +25 -26
- package/dist-pkg/ui/layout.d.ts +103 -28
- package/dist-pkg/ui/layout.js +117 -99
- package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
- package/dist-pkg/ui/libras-avatar-clip.js +86 -0
- package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
- package/dist-pkg/ui/libras-avatar-load.js +134 -0
- package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
- package/dist-pkg/ui/libras-avatar-plan.js +211 -0
- package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
- package/dist-pkg/ui/libras-avatar-player.js +226 -0
- package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
- package/dist-pkg/ui/libras-avatar-stage.js +208 -0
- package/dist-pkg/ui/libras-glosses.d.ts +28 -0
- package/dist-pkg/ui/libras-glosses.js +161 -0
- package/dist-pkg/ui/locale-flags.d.ts +16 -0
- package/dist-pkg/ui/locale-flags.js +30 -0
- package/dist-pkg/ui/loop-crash.d.ts +17 -17
- package/dist-pkg/ui/loop-crash.js +33 -62
- package/dist-pkg/ui/menu-intent.d.ts +61 -0
- package/dist-pkg/ui/menu-intent.js +88 -0
- package/dist-pkg/ui/menu-items.d.ts +20 -0
- package/dist-pkg/ui/menu-items.js +51 -0
- package/dist-pkg/ui/menu-nav.d.ts +98 -92
- package/dist-pkg/ui/menu-nav.js +398 -286
- package/dist-pkg/ui/mobility-choices.d.ts +26 -0
- package/dist-pkg/ui/mobility-choices.js +41 -0
- package/dist-pkg/ui/motion-choices.d.ts +31 -0
- package/dist-pkg/ui/motion-choices.js +68 -0
- package/dist-pkg/ui/motion-scene.d.ts +24 -19
- package/dist-pkg/ui/motion-scene.js +29 -61
- package/dist-pkg/ui/mount-panel.d.ts +81 -0
- package/dist-pkg/ui/mount-panel.js +61 -0
- package/dist-pkg/ui/panel-shell.d.ts +65 -31
- package/dist-pkg/ui/panel-shell.js +92 -66
- package/dist-pkg/ui/panel-widgets.d.ts +150 -0
- package/dist-pkg/ui/panel-widgets.js +294 -0
- package/dist-pkg/ui/pause-buttons.d.ts +35 -0
- package/dist-pkg/ui/pause-buttons.js +66 -0
- package/dist-pkg/ui/pause-icons.d.ts +261 -347
- package/dist-pkg/ui/pause-icons.js +671 -601
- package/dist-pkg/ui/pause-markup.d.ts +86 -0
- package/dist-pkg/ui/pause-markup.js +125 -0
- package/dist-pkg/ui/reach-notice.d.ts +22 -23
- package/dist-pkg/ui/reach-notice.js +55 -58
- package/dist-pkg/ui/scan-overlay.d.ts +23 -0
- package/dist-pkg/ui/scan-overlay.js +50 -0
- package/dist-pkg/ui/screen-text.d.ts +25 -0
- package/dist-pkg/ui/screen-text.js +152 -0
- package/dist-pkg/ui/session-clock.d.ts +32 -0
- package/dist-pkg/ui/session-clock.js +67 -0
- package/dist-pkg/ui/settings-aac.d.ts +70 -0
- package/dist-pkg/ui/settings-aac.js +208 -0
- package/dist-pkg/ui/settings-audio.d.ts +97 -114
- package/dist-pkg/ui/settings-audio.js +493 -425
- package/dist-pkg/ui/settings-controls.d.ts +53 -96
- package/dist-pkg/ui/settings-controls.js +143 -215
- package/dist-pkg/ui/settings-empathy.d.ts +39 -30
- package/dist-pkg/ui/settings-empathy.js +53 -60
- package/dist-pkg/ui/settings-mobility.d.ts +192 -0
- package/dist-pkg/ui/settings-mobility.js +332 -0
- package/dist-pkg/ui/settings-motion.d.ts +91 -70
- package/dist-pkg/ui/settings-motion.js +287 -198
- package/dist-pkg/ui/settings-panel.d.ts +58 -45
- package/dist-pkg/ui/settings-panel.js +189 -94
- package/dist-pkg/ui/settings-typo.d.ts +29 -78
- package/dist-pkg/ui/settings-typo.js +158 -129
- package/dist-pkg/ui/settings-visual.d.ts +29 -87
- package/dist-pkg/ui/settings-visual.js +240 -193
- package/dist-pkg/ui/shell.d.ts +112 -135
- package/dist-pkg/ui/shell.js +148 -217
- package/dist-pkg/ui/simulation-list.d.ts +27 -0
- package/dist-pkg/ui/simulation-list.js +76 -0
- package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
- package/dist-pkg/ui/simulation-over-the-world.js +131 -0
- package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
- package/dist-pkg/ui/simulation-refusal.js +8 -56
- package/dist-pkg/ui/switchable-control.d.ts +11 -0
- package/dist-pkg/ui/switchable-control.js +19 -0
- package/dist-pkg/ui/title.d.ts +3 -12
- package/dist-pkg/ui/title.js +49 -18
- package/dist-pkg/ui/top-band.d.ts +26 -0
- package/dist-pkg/ui/top-band.js +96 -0
- package/dist-pkg/ui/typo-choices.d.ts +71 -0
- package/dist-pkg/ui/typo-choices.js +85 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
- package/dist-pkg/ui/visual-axes-panel.js +12 -94
- package/dist-pkg/ui/visual-choices.d.ts +77 -0
- package/dist-pkg/ui/visual-choices.js +107 -0
- package/dist-pkg/ui/vlibras.d.ts +77 -10
- package/dist-pkg/ui/vlibras.js +130 -81
- package/dist-pkg/ui/voice-control.d.ts +68 -0
- package/dist-pkg/ui/voice-control.js +276 -0
- package/dist-pkg/ui/voice-settings.d.ts +95 -0
- package/dist-pkg/ui/voice-settings.js +352 -0
- package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
- package/dist-pkg/ui/where-the-child-is.js +70 -0
- package/docs/CREDITS.md +300 -44
- package/docs/LICENSES.md +189 -145
- package/package.json +51 -27
- package/scripts/check-cartridge.mjs +78 -0
- package/scripts/game-build.d.mts +25 -0
- package/scripts/game-build.mjs +144 -0
- package/scripts/heavy-into-the-delivery.mjs +388 -0
- package/scripts/libras-avatar.json +680 -0
- package/scripts/libras-avatar.mjs +246 -0
- package/scripts/libras-glosses/gloss.py +95 -0
- package/scripts/libras-glosses/pyproject.toml +32 -0
- package/scripts/libras-glosses/uv.lock +781 -0
- package/scripts/libras-glosses.mjs +351 -0
- package/scripts/licences/Apache-2.0.txt +176 -0
- package/scripts/licences/GPL-3.0.txt +674 -0
- package/scripts/licences/MIT.txt +17 -0
- package/scripts/licences/OFL-1.1.txt +86 -0
- package/scripts/licences/UFL-1.0.txt +96 -0
- package/scripts/licences/fonts.mjs +389 -0
- package/scripts/licences/third-party.mjs +301 -0
- package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
- package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
- 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/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/dist-pkg/core/anel.d.ts +0 -17
- package/dist-pkg/core/anel.js +0 -31
- package/dist-pkg/core/collision.d.ts +0 -21
- package/dist-pkg/core/collision.js +0 -62
- package/dist-pkg/core/layers.d.ts +0 -63
- package/dist-pkg/core/layers.js +0 -95
- package/dist-pkg/core/letter-grid.d.ts +0 -55
- package/dist-pkg/core/letter-grid.js +0 -116
- package/dist-pkg/core/password.d.ts +0 -34
- package/dist-pkg/core/password.js +0 -205
- package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
- package/dist-pkg/core/rotulo-acessivel.js +0 -41
- package/dist-pkg/core/run-state.d.ts +0 -115
- package/dist-pkg/core/run-state.js +0 -40
- package/dist-pkg/core/tiles.d.ts +0 -12
- package/dist-pkg/core/tiles.js +0 -61
- package/dist-pkg/core/world.d.ts +0 -4
- package/dist-pkg/core/world.js +0 -58
- package/dist-pkg/input/origem-sintetica.d.ts +0 -44
- package/dist-pkg/input/origem-sintetica.js +0 -60
- package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
- package/dist-pkg/input/transporte-em-uso.js +0 -130
- package/dist-pkg/platform/audio-nav.d.ts +0 -51
- package/dist-pkg/platform/audio-nav.js +0 -99
- package/dist-pkg/platform/guide-intensity.d.ts +0 -40
- package/dist-pkg/platform/guide-intensity.js +0 -73
- package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
- package/dist-pkg/platform/pesados-catalogo.js +0 -177
- package/dist-pkg/platform/pesados.d.ts +0 -31
- package/dist-pkg/platform/pesados.js +0 -75
- package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
- package/dist-pkg/platform/vozes-prontas.js +0 -61
- package/dist-pkg/render/camera.d.ts +0 -58
- package/dist-pkg/render/camera.js +0 -105
- package/dist-pkg/render/cenario-data.d.ts +0 -127
- package/dist-pkg/render/cenario-data.js +0 -145
- package/dist-pkg/render/city-tex.d.ts +0 -63
- package/dist-pkg/render/city-tex.js +0 -228
- package/dist-pkg/render/city-tiles.d.ts +0 -11
- package/dist-pkg/render/city-tiles.js +0 -140
- package/dist-pkg/render/draw.d.ts +0 -149
- package/dist-pkg/render/draw.js +0 -231
- package/dist-pkg/render/fx.d.ts +0 -63
- package/dist-pkg/render/fx.js +0 -123
- package/dist-pkg/render/minimap.d.ts +0 -11
- package/dist-pkg/render/minimap.js +0 -94
- package/dist-pkg/render/parallax.d.ts +0 -85
- package/dist-pkg/render/parallax.js +0 -158
- package/dist-pkg/render/player-anim.d.ts +0 -71
- package/dist-pkg/render/player-anim.js +0 -152
- package/dist-pkg/render/recycling-tex.d.ts +0 -48
- package/dist-pkg/render/recycling-tex.js +0 -164
- package/dist-pkg/render/scene-city.d.ts +0 -77
- package/dist-pkg/render/scene-city.js +0 -181
- package/dist-pkg/render/scene-parallax.d.ts +0 -72
- package/dist-pkg/render/scene-parallax.js +0 -426
- package/dist-pkg/render/scene-sky.d.ts +0 -126
- package/dist-pkg/render/scene-sky.js +0 -295
- package/dist-pkg/render/set-cenario.d.ts +0 -54
- package/dist-pkg/render/set-cenario.js +0 -103
- package/dist-pkg/render/textures.d.ts +0 -75
- package/dist-pkg/render/textures.js +0 -240
- package/dist-pkg/render/title-scene.d.ts +0 -46
- package/dist-pkg/render/title-scene.js +0 -75
- package/dist-pkg/render/weather.d.ts +0 -96
- package/dist-pkg/render/weather.js +0 -171
- package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
- package/dist-pkg/render/wheelchair-sprites.js +0 -69
- package/dist-pkg/render/world-tex.d.ts +0 -18
- package/dist-pkg/render/world-tex.js +0 -87
- package/dist-pkg/ui/activities-menu.d.ts +0 -258
- package/dist-pkg/ui/activities-menu.js +0 -648
- package/dist-pkg/ui/caa-sets.d.ts +0 -39
- package/dist-pkg/ui/caa-sets.js +0 -65
- package/dist-pkg/ui/latch-refusal.d.ts +0 -32
- package/dist-pkg/ui/latch-refusal.js +0 -60
- package/dist-pkg/ui/map-hub.d.ts +0 -56
- package/dist-pkg/ui/map-hub.js +0 -138
- package/dist-pkg/ui/settings-caa.d.ts +0 -45
- package/dist-pkg/ui/settings-caa.js +0 -137
- package/dist-pkg/ui/settings-motor.d.ts +0 -175
- package/dist-pkg/ui/settings-motor.js +0 -342
- package/dist-pkg/ui/webcam.d.ts +0 -7
- package/dist-pkg/ui/webcam.js +0 -93
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/choose-by-voice — A CHILD ANSWERS BY SAYING ONE OF THE OPTIONS THE GAME SHOWS (ADR-0256).
|
|
3
|
+
//
|
|
4
|
+
// The game passes the options it is showing and gets back which one was said. The recogniser hears a grammar of exactly those
|
|
5
|
+
// options plus `[unk]`, so it can only answer with one of them or with «none of these» — never with the nearest option to a word
|
|
6
|
+
// that was not one. 📏 Why not the reading (Whisper): the lab measured it at 1 of 7 on words said alone, and an option is one or
|
|
7
|
+
// two words; the command recogniser, closed, heard 7 of 7.
|
|
8
|
+
//
|
|
9
|
+
// 🔴 TWO OPTIONS IN ONE SENTENCE ARE NOT AN ANSWER (the quiz's own rule, which moved here with the matching): a child who says
|
|
10
|
+
// «gato ou galinha» is thinking out loud, and picking one of them would answer FOR her — and mark it wrong. So the choice is
|
|
11
|
+
// decided at the END of a sentence, never on its first word: one option chooses, two or more end the choice unanswered, none
|
|
12
|
+
// goes on listening. 📏 It costs the end of the sentence — the lab measured 1.3 s after the word against 0.4 s on the partial.
|
|
13
|
+
//
|
|
14
|
+
// What this module decides, and nothing else: the grammar an option set becomes, which options a sentence names, when the
|
|
15
|
+
// listening ends, and that one choice at a time holds the microphone. Opening the recogniser and the microphone is the root's
|
|
16
|
+
// (`open`), which lends the delivery, the cache and the browser (ADR-0232 D4).
|
|
17
|
+
import { spokenText, compareKey } from './speech-recognition.js';
|
|
18
|
+
import { loadVoskRuntime } from './vosk-runtime.js';
|
|
19
|
+
import { startVoiceListening } from './voice-listener.js';
|
|
20
|
+
const MAX_MS = 10_000;
|
|
21
|
+
/**
|
|
22
|
+
* THE `open` A CHOOSER IS GIVEN WHERE THE COMMAND RECOGNISER IS THE EAR: the language's model from the delivery, then the
|
|
23
|
+
* microphone with the grammar. A model that opened for a microphone that did not is let go before the refusal travels.
|
|
24
|
+
*/
|
|
25
|
+
export function openWithCommandRecogniser(d) {
|
|
26
|
+
return async (language, grammar, onHeard) => {
|
|
27
|
+
// a device with no microphone is refused before 31 MiB of model are opened for nothing
|
|
28
|
+
if (!d.getUserMedia)
|
|
29
|
+
throw new Error('this device cannot open a microphone');
|
|
30
|
+
const load = await (d.load ?? loadVoskRuntime)({ ...d, language });
|
|
31
|
+
if (!load.ok)
|
|
32
|
+
throw new Error(`${load.missing.join(', ')} not on this device`);
|
|
33
|
+
try {
|
|
34
|
+
const listening = await startVoiceListening({
|
|
35
|
+
model: load.model, grammar, getUserMedia: d.getUserMedia, createContext: d.createContext,
|
|
36
|
+
onPartial: (text) => onHeard(text, false), onFinal: (text) => onHeard(text, true),
|
|
37
|
+
});
|
|
38
|
+
return { stop: () => listening.stop(), ...(load.vocabulary ? { vocabulary: load.vocabulary } : {}) };
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
load.model.terminate?.();
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The options a sentence names, in the order said, each once: read word by word, the LONGEST option that starts at each word —
|
|
48
|
+
* so «cavalo marinho» is one option and not also «cavalo» — accents and case aside, whole words only.
|
|
49
|
+
*/
|
|
50
|
+
export function optionsNamed(options, sentence) {
|
|
51
|
+
const keys = options.map((o) => compareKey(o).split(' ').filter(Boolean));
|
|
52
|
+
const said = compareKey(sentence).split(' ').filter(Boolean);
|
|
53
|
+
const named = [];
|
|
54
|
+
for (let i = 0; i < said.length;) {
|
|
55
|
+
let best = -1;
|
|
56
|
+
keys.forEach((k, o) => {
|
|
57
|
+
if (k.length && k.every((w, j) => said[i + j] === w) && (best < 0 || k.length > keys[best].length))
|
|
58
|
+
best = o;
|
|
59
|
+
});
|
|
60
|
+
if (best < 0) {
|
|
61
|
+
i += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!named.includes(best))
|
|
65
|
+
named.push(best);
|
|
66
|
+
i += keys[best].length;
|
|
67
|
+
}
|
|
68
|
+
return named;
|
|
69
|
+
}
|
|
70
|
+
export function createVoiceChooser(d) {
|
|
71
|
+
const said = new Set();
|
|
72
|
+
const once = (kind, line) => { if (!said.has(kind)) {
|
|
73
|
+
said.add(kind);
|
|
74
|
+
d.report(line);
|
|
75
|
+
} };
|
|
76
|
+
let inFlight = null;
|
|
77
|
+
let askToStop = null;
|
|
78
|
+
/** An option with a word the model does not know can never be heard: said to the adult, once per option and language. */
|
|
79
|
+
const reportUnsayable = async (opened, options, language) => {
|
|
80
|
+
const known = await opened.vocabulary?.().catch(() => null);
|
|
81
|
+
if (!known)
|
|
82
|
+
return;
|
|
83
|
+
for (const option of options) {
|
|
84
|
+
const lacking = spokenText(option).split(' ').filter((w) => w && !known.has(w));
|
|
85
|
+
if (lacking.length) {
|
|
86
|
+
once(`unsayable:${language}:${option}`, `choosing by voice: the option "${option}" has ${lacking.length === 1 ? 'a word' : 'words'} `
|
|
87
|
+
+ `the ${language} speech model does not know (${lacking.map((w) => `"${w}"`).join(', ')}) — the child cannot choose it by saying `
|
|
88
|
+
+ 'it, only with the arrows; reword it with words the model knows (ADR-0256)');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const run = async (options, settings) => {
|
|
93
|
+
const language = settings.language ?? d.language();
|
|
94
|
+
const grammar = [...new Set(options.map((o) => spokenText(o)).filter(Boolean))];
|
|
95
|
+
let heard = '';
|
|
96
|
+
let settle = () => { };
|
|
97
|
+
const answer = new Promise((resolve) => { settle = resolve; });
|
|
98
|
+
const end = (chosen, ended) => settle({ chosen, heard, ended });
|
|
99
|
+
// asked to stop while the microphone is still opening counts too: it is let go as soon as it opens
|
|
100
|
+
askToStop = () => end(null, 'asked');
|
|
101
|
+
const onHeard = (text, final) => {
|
|
102
|
+
const clean = text.replace(/\[unk\]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
103
|
+
// 🔴 THE END OF A SENTENCE IS WHAT WAS HEARD, even empty: a partial the recogniser took back («happy», then nothing) is not
|
|
104
|
+
// an answer to show the child — measured on 2026-09-27, the quiz said «I heard happy» for a word the model had withdrawn
|
|
105
|
+
if (final || clean)
|
|
106
|
+
heard = clean;
|
|
107
|
+
if (!final || !clean)
|
|
108
|
+
return;
|
|
109
|
+
const named = optionsNamed(options, clean);
|
|
110
|
+
if (named.length === 1)
|
|
111
|
+
end(named[0], 'chosen');
|
|
112
|
+
else if (named.length > 1)
|
|
113
|
+
end(null, 'unclear');
|
|
114
|
+
};
|
|
115
|
+
const borrowed = d.borrow?.(language, grammar, onHeard) ?? null;
|
|
116
|
+
if (!borrowed)
|
|
117
|
+
d.pause?.();
|
|
118
|
+
try {
|
|
119
|
+
let opened;
|
|
120
|
+
try {
|
|
121
|
+
opened = borrowed ?? await d.open(language, grammar, onHeard);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
const why = e instanceof Error ? e.message : String(e);
|
|
125
|
+
once(`open:${language}`, `choosing by voice: the recogniser did not open for ${language} (${why}) — the child cannot answer an `
|
|
126
|
+
+ `option by saying it; build the delivery with \`--commands ${language.split('-')[0]}\` and allow the microphone (ADR-0256)`);
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
void reportUnsayable(opened, options, language);
|
|
130
|
+
// a game's listener that throws must not take the child's answer with it
|
|
131
|
+
try {
|
|
132
|
+
settings.onListening?.();
|
|
133
|
+
}
|
|
134
|
+
catch { /* the game's own line, not the choice */ }
|
|
135
|
+
const timer = d.after(() => end(null, 'timeout'), settings.maxMs ?? MAX_MS);
|
|
136
|
+
const result = await answer;
|
|
137
|
+
d.cancel(timer);
|
|
138
|
+
await opened.stop().catch(() => { });
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
askToStop = null;
|
|
143
|
+
if (!borrowed)
|
|
144
|
+
d.resume?.();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
choose(options, settings = {}) {
|
|
149
|
+
// one microphone, one choice: a second caller waits on the first
|
|
150
|
+
if (inFlight)
|
|
151
|
+
return inFlight;
|
|
152
|
+
inFlight = run(options, settings).finally(() => { inFlight = null; });
|
|
153
|
+
return inFlight;
|
|
154
|
+
},
|
|
155
|
+
stop() { askToStop?.(); },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** What a measurement found. `passes` and `worstSecond` exist only when the canvas was actually read. */
|
|
3
|
+
export interface FlashMeasurement {
|
|
4
|
+
readonly measured: boolean;
|
|
5
|
+
readonly reason?: string;
|
|
6
|
+
readonly passes?: boolean;
|
|
7
|
+
readonly worstSecond?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface FlashSamplerCtx {
|
|
10
|
+
/** The world's canvas, read at the moment of the call — a `mount()` may have replaced the world. */
|
|
11
|
+
readonly canvas: () => HTMLCanvasElement | null;
|
|
12
|
+
/** A scratch canvas of our own: the world's is never resized, only copied from. */
|
|
13
|
+
readonly scratch: () => HTMLCanvasElement;
|
|
14
|
+
/** `requestAnimationFrame`, absent in a host that cannot animate — then there is nothing to sample over time. */
|
|
15
|
+
readonly frame?: (cb: (t: number) => void) => void;
|
|
16
|
+
/** Where a failure goes, so `problems` carries it for whoever reads the page's diagnosis. */
|
|
17
|
+
readonly report: (line: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function sampleFlashes(ctx: FlashSamplerCtx, ms: number): Promise<FlashMeasurement>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// platform/flash-sampler — READING THE WORLD'S CANVAS TO ANSWER WCAG 2.3.1 (study item B2; ADR-0027; issue #203).
|
|
3
|
+
//
|
|
4
|
+
// The pure half of this has lived in `core/flash-threshold` since it was written: given the luminance of a grid over time, it
|
|
5
|
+
// says whether the general flash threshold is crossed. THIS is the half that has to touch a browser — copy the world's canvas
|
|
6
|
+
// into a small one, read its pixels frame by frame, and turn them into the grid the pure half expects. It lived inside
|
|
7
|
+
// `boot/create-game`, and it left for the reason ADR-0221 gives: 📏 that file reached 2185 lines and 330 decision nodes, and
|
|
8
|
+
// the debt is paid by SUBJECT.
|
|
9
|
+
//
|
|
10
|
+
// ⚠️ IT ONLY RUNS WHEN IT IS CALLED, and that is a decision and not an omission: sampling a canvas every frame would cost a
|
|
11
|
+
// school's machine the very thing the engine exists to protect. `Engine.measureFlashes(ms)` is how an adult asks.
|
|
12
|
+
//
|
|
13
|
+
// 📌 THE THREE REFUSALS ARE AS MUCH THE ANSWER AS THE MEASUREMENT, and each names what a developer must do: a world that is not
|
|
14
|
+
// a canvas cannot be read at all; a host without `getImageData` or `requestAnimationFrame` cannot sample; and a canvas that
|
|
15
|
+
// reads transparent in every frame is the WebGL case, where the buffer is thrown away unless `preserveDrawingBuffer` asked it
|
|
16
|
+
// not to be. A silent `false` would have sent somebody looking for a flash that was never measured.
|
|
17
|
+
import { analyseFlashes, COLUMNS, ROWS } from '../core/flash-threshold.js';
|
|
18
|
+
/** Ten samples per cell of the pure half's grid — enough for an average, small enough to read every frame. */
|
|
19
|
+
const SAMPLE_COLS = COLUMNS * 10;
|
|
20
|
+
const SAMPLE_ROWS = ROWS * 10;
|
|
21
|
+
/** sRGB → linear light, which is what a luminance average may be taken over (WCAG's own definition). */
|
|
22
|
+
const linear = (v) => { const c = v / 255; return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; };
|
|
23
|
+
export function sampleFlashes(ctx, ms) {
|
|
24
|
+
const canvas = ctx.canvas();
|
|
25
|
+
if (!canvas)
|
|
26
|
+
return Promise.resolve({ measured: false, reason: 'the world is not a canvas and contains none to read' });
|
|
27
|
+
const copy = ctx.scratch();
|
|
28
|
+
copy.width = SAMPLE_COLS;
|
|
29
|
+
copy.height = SAMPLE_ROWS;
|
|
30
|
+
const c2d = copy.getContext('2d', { willReadFrequently: true });
|
|
31
|
+
const frame = ctx.frame;
|
|
32
|
+
if (!c2d || typeof frame !== 'function')
|
|
33
|
+
return Promise.resolve({ measured: false, reason: 'this host cannot sample canvases' });
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const frames = [];
|
|
36
|
+
let start = -1;
|
|
37
|
+
let anyPixel = false;
|
|
38
|
+
const step = (now) => {
|
|
39
|
+
if (start < 0)
|
|
40
|
+
start = now;
|
|
41
|
+
let px;
|
|
42
|
+
try {
|
|
43
|
+
c2d.clearRect(0, 0, SAMPLE_COLS, SAMPLE_ROWS);
|
|
44
|
+
c2d.drawImage(canvas, 0, 0, SAMPLE_COLS, SAMPLE_ROWS);
|
|
45
|
+
px = c2d.getImageData(0, 0, SAMPLE_COLS, SAMPLE_ROWS).data;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
resolve({ measured: false, reason: 'the page may not read the world\'s canvas (an image from another origin drew on it)' });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const grid = new Float32Array(COLUMNS * ROWS);
|
|
52
|
+
for (let y = 0; y < SAMPLE_ROWS; y++) {
|
|
53
|
+
for (let x = 0; x < SAMPLE_COLS; x++) {
|
|
54
|
+
const i = (y * SAMPLE_COLS + x) * 4;
|
|
55
|
+
if (px[i + 3])
|
|
56
|
+
anyPixel = true;
|
|
57
|
+
const l = 0.2126 * linear(px[i]) + 0.7152 * linear(px[i + 1]) + 0.0722 * linear(px[i + 2]);
|
|
58
|
+
grid[Math.floor(y / 10) * COLUMNS + Math.floor(x / 10)] += l / 100;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
frames.push({ t: now - start, luminances: grid });
|
|
62
|
+
if (now - start < ms) {
|
|
63
|
+
frame(step);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!anyPixel) {
|
|
67
|
+
resolve({ measured: false, reason: 'the world\'s canvas read transparent in every frame (WebGL canvases need preserveDrawingBuffer)' });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { passes: passed, worstSecond: worst } = analyseFlashes(frames);
|
|
71
|
+
if (!passed) {
|
|
72
|
+
ctx.report(`the world's canvas flashed ${worst} times in one second within a 10-degree field `
|
|
73
|
+
+ '(WCAG 2.3.1 allows 3): it can trigger a seizure in a child with photosensitive epilepsy — slow or dim it');
|
|
74
|
+
}
|
|
75
|
+
resolve({ measured: true, passes: passed, worstSecond: worst });
|
|
76
|
+
};
|
|
77
|
+
frame(step);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { HeavyFile } from './heavy-catalogue.js';
|
|
3
|
+
/** One face of a family: one file, and what its `@font-face` rule says of it. */
|
|
4
|
+
export interface LibraryFace {
|
|
5
|
+
readonly file: string;
|
|
6
|
+
/** A weight (`400`) or a variable range (`100 900`), as `font-weight` takes it. */
|
|
7
|
+
readonly weight: string;
|
|
8
|
+
readonly style: string;
|
|
9
|
+
/** A key of `FontLibrary.ranges`; absent = the file covers the whole family (an author's original, ADR-0254). */
|
|
10
|
+
readonly range?: string;
|
|
11
|
+
readonly bytes: number;
|
|
12
|
+
readonly sha256: string;
|
|
13
|
+
/** The author's original this file was compressed from, for a family with a Reserved Font Name (ADR-0254). */
|
|
14
|
+
readonly from?: string;
|
|
15
|
+
}
|
|
16
|
+
/** A family of the library: its folder in the mirror, the licence its fonts state, and its faces. */
|
|
17
|
+
export interface LibraryFamily {
|
|
18
|
+
readonly folder: string;
|
|
19
|
+
readonly licence: string;
|
|
20
|
+
readonly faces: readonly LibraryFace[];
|
|
21
|
+
}
|
|
22
|
+
/** The catalogue: the mirror's address, the unicode-ranges the faces share, and the families by name. */
|
|
23
|
+
export interface FontLibrary {
|
|
24
|
+
readonly mirror: string;
|
|
25
|
+
readonly ranges: Readonly<Record<string, string>>;
|
|
26
|
+
readonly families: Readonly<Record<string, LibraryFamily>>;
|
|
27
|
+
}
|
|
28
|
+
/** The heavy-file id of a face: `font:<folder>:<file>`. */
|
|
29
|
+
export declare const libraryFaceId: (family: LibraryFamily, face: LibraryFace) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Each FILE of these families as a heavy file: its address, its bytes and the sha256 its body must have to be kept. Once each —
|
|
32
|
+
* a variable file may be declared at two weights, and is kept once.
|
|
33
|
+
*/
|
|
34
|
+
export declare function libraryFiles(library: FontLibrary, families: readonly string[]): HeavyFile[];
|
|
35
|
+
/**
|
|
36
|
+
* The `@font-face` rules of these families, each `src` at `href(address)` — the delivery's `heavy/` path, resolved against the
|
|
37
|
+
* page, where the service worker answers from the checked cache. A face with no range covers the whole family (ADR-0254).
|
|
38
|
+
*/
|
|
39
|
+
export declare function libraryFaceRules(library: FontLibrary, families: readonly string[], href: (url: string) => string): string;
|
|
40
|
+
/**
|
|
41
|
+
* THE FAMILIES A CARTRIDGE DECLARED (`uses.fonts`), less the engine's own — they are packaged, and declaring one asks for
|
|
42
|
+
* nothing. `problem` names a declaration that is not a list of family names, which the engine cannot read.
|
|
43
|
+
*/
|
|
44
|
+
export declare function declaredFamilies(value: unknown, engineFamilies: ReadonlySet<string>): {
|
|
45
|
+
readonly asked: string[];
|
|
46
|
+
readonly problem: string | null;
|
|
47
|
+
};
|
|
48
|
+
/** The line of `problems` for a declared family the library does not hold (ADR-0169: the subject, the cost, the fix). */
|
|
49
|
+
export declare function unknownFamilyLine(family: string): string;
|
|
50
|
+
/** What the root lends the library's start: where the rules go, how a delivery path becomes an address, and where lines go. */
|
|
51
|
+
export interface LibraryFontsPorts {
|
|
52
|
+
/** The families declared, the engine's own left out (`declaredFamilies`). */
|
|
53
|
+
readonly asked: readonly string[];
|
|
54
|
+
/** Writes the rules into the document (a `<style>` of the root's). */
|
|
55
|
+
readonly declare: (css: string) => void;
|
|
56
|
+
/** An upstream address → the address the page asks for it at (`heavy/` under the page). */
|
|
57
|
+
readonly href: (url: string) => string;
|
|
58
|
+
/** A line of `problems`. */
|
|
59
|
+
readonly report: (line: string) => void;
|
|
60
|
+
/** The catalogue; `loadFontLibrary` unless a case hands its own. */
|
|
61
|
+
readonly load?: () => Promise<FontLibrary>;
|
|
62
|
+
}
|
|
63
|
+
/** What the start answers: the files to keep in the checked cache, and the listener that turns their refusals into lines. */
|
|
64
|
+
export interface LibraryFontsStarted {
|
|
65
|
+
readonly files: readonly HeavyFile[];
|
|
66
|
+
readonly onReport: (r: {
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly outcome: string;
|
|
69
|
+
readonly error?: string;
|
|
70
|
+
}) => void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* DECLARES THE ASKED FAMILIES' FACES, and answers the files to keep. A family the library does not hold is said in `problems`;
|
|
74
|
+
* a file the delivery did not carry, or whose sha256 differs, is said once per family when its report arrives. Never throws: a
|
|
75
|
+
* catalogue that does not load is one line, and the game plays in its stack's next face.
|
|
76
|
+
*/
|
|
77
|
+
export declare function startLibraryFonts(p: LibraryFontsPorts): Promise<LibraryFontsStarted>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** Loads the catalogue — its own chunk, fetched from the page's origin the first time a cartridge declares a family. */
|
|
3
|
+
async function loadFontLibrary() {
|
|
4
|
+
return (await import('./font-library.json')).default;
|
|
5
|
+
}
|
|
6
|
+
/** The address a face is kept under in the checked cache — the mirror's, as every heavy file's upstream address is. */
|
|
7
|
+
function libraryFaceUrl(library, family, face) {
|
|
8
|
+
return `${library.mirror}/${family.folder}/${face.file}`;
|
|
9
|
+
}
|
|
10
|
+
/** The heavy-file id of a face: `font:<folder>:<file>`. */
|
|
11
|
+
export const libraryFaceId = (family, face) => `font:${family.folder}:${face.file}`;
|
|
12
|
+
/**
|
|
13
|
+
* Each FILE of these families as a heavy file: its address, its bytes and the sha256 its body must have to be kept. Once each —
|
|
14
|
+
* a variable file may be declared at two weights, and is kept once.
|
|
15
|
+
*/
|
|
16
|
+
export function libraryFiles(library, families) {
|
|
17
|
+
const files = families.flatMap((name) => {
|
|
18
|
+
const family = library.families[name];
|
|
19
|
+
return family ? family.faces.map((face) => ({
|
|
20
|
+
id: libraryFaceId(family, face), url: libraryFaceUrl(library, family, face), bytes: face.bytes, sha256: face.sha256,
|
|
21
|
+
})) : [];
|
|
22
|
+
});
|
|
23
|
+
return files.filter((f, i) => files.findIndex((g) => g.id === f.id) === i);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The `@font-face` rules of these families, each `src` at `href(address)` — the delivery's `heavy/` path, resolved against the
|
|
27
|
+
* page, where the service worker answers from the checked cache. A face with no range covers the whole family (ADR-0254).
|
|
28
|
+
*/
|
|
29
|
+
export function libraryFaceRules(library, families, href) {
|
|
30
|
+
return families.flatMap((name) => {
|
|
31
|
+
const family = library.families[name];
|
|
32
|
+
if (!family)
|
|
33
|
+
return [];
|
|
34
|
+
return family.faces.map((face) => {
|
|
35
|
+
const range = face.range ? `unicode-range:${library.ranges[face.range]};` : '';
|
|
36
|
+
return `@font-face{font-family:'${name}';font-style:${face.style};font-weight:${face.weight};font-display:swap;`
|
|
37
|
+
+ `src:url('${href(libraryFaceUrl(library, family, face))}') format('woff2');${range}}`;
|
|
38
|
+
});
|
|
39
|
+
}).join('\n');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* THE FAMILIES A CARTRIDGE DECLARED (`uses.fonts`), less the engine's own — they are packaged, and declaring one asks for
|
|
43
|
+
* nothing. `problem` names a declaration that is not a list of family names, which the engine cannot read.
|
|
44
|
+
*/
|
|
45
|
+
export function declaredFamilies(value, engineFamilies) {
|
|
46
|
+
if (value === undefined || value === null)
|
|
47
|
+
return { asked: [], problem: null };
|
|
48
|
+
if (!Array.isArray(value) || value.some((f) => typeof f !== 'string' || !f.trim())) {
|
|
49
|
+
return { asked: [], problem: `fonts: \`uses.fonts\` is ${JSON.stringify(value)}, not a list of family names — no library face was `
|
|
50
|
+
+ 'loaded, so the text the game draws in them falls back to the next face of its stack; declare `uses: { fonts: [\'Lato\'] }`' };
|
|
51
|
+
}
|
|
52
|
+
return { asked: [...new Set(value.map((f) => f.trim()))].filter((f) => !engineFamilies.has(f)), problem: null };
|
|
53
|
+
}
|
|
54
|
+
/** The line of `problems` for a declared family the library does not hold (ADR-0169: the subject, the cost, the fix). */
|
|
55
|
+
export function unknownFamilyLine(family) {
|
|
56
|
+
return `font «${family}»: the game declares it in \`uses.fonts\`, and the engine's font library has no such family — the text `
|
|
57
|
+
+ 'the game draws in it falls back to the next face of its stack; declare a family the library holds '
|
|
58
|
+
+ '(`platform/font-library.json`), or leave it out';
|
|
59
|
+
}
|
|
60
|
+
/** The line of `problems` for a declared family the delivery did not carry, or whose file was refused. */
|
|
61
|
+
function undeliveredLine(family, error) {
|
|
62
|
+
return `font «${family}»: the game declares it in \`uses.fonts\`, and it was not kept (${error}) — the text the game draws in it `
|
|
63
|
+
+ 'falls back to the next face of its stack, and on a day without a network the face is not there at all; build the delivery '
|
|
64
|
+
+ `with \`inclusionist-heavy <folder> --fonts "${family}"\``;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* DECLARES THE ASKED FAMILIES' FACES, and answers the files to keep. A family the library does not hold is said in `problems`;
|
|
68
|
+
* a file the delivery did not carry, or whose sha256 differs, is said once per family when its report arrives. Never throws: a
|
|
69
|
+
* catalogue that does not load is one line, and the game plays in its stack's next face.
|
|
70
|
+
*/
|
|
71
|
+
export async function startLibraryFonts(p) {
|
|
72
|
+
try {
|
|
73
|
+
const library = await (p.load ?? loadFontLibrary)();
|
|
74
|
+
const held = p.asked.filter((f) => library.families[f]);
|
|
75
|
+
for (const f of p.asked)
|
|
76
|
+
if (!library.families[f])
|
|
77
|
+
p.report(unknownFamilyLine(f));
|
|
78
|
+
if (held.length)
|
|
79
|
+
p.declare(libraryFaceRules(library, held, p.href));
|
|
80
|
+
const familyOf = new Map(held.flatMap((f) => library.families[f].faces.map((face) => [libraryFaceId(library.families[f], face), f])));
|
|
81
|
+
const said = new Set();
|
|
82
|
+
return {
|
|
83
|
+
files: libraryFiles(library, held),
|
|
84
|
+
onReport: (r) => {
|
|
85
|
+
const family = familyOf.get(r.id);
|
|
86
|
+
if (!family || r.outcome !== 'falhou' || said.has(family))
|
|
87
|
+
return;
|
|
88
|
+
said.add(family);
|
|
89
|
+
p.report(undeliveredLine(family, r.error ?? 'refused'));
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
for (const f of p.asked)
|
|
95
|
+
p.report(undeliveredLine(f, `the font catalogue did not load: ${e instanceof Error ? e.message : String(e)}`));
|
|
96
|
+
return { files: [], onReport: () => { } };
|
|
97
|
+
}
|
|
98
|
+
}
|