@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.
Files changed (532) hide show
  1. package/README.md +120 -78
  2. package/app/css/style.css +761 -377
  3. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  10. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  11. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  12. package/app/public/vendor/fonts.css +36 -84
  13. package/dist-pkg/boot/create-game.d.ts +499 -219
  14. package/dist-pkg/boot/create-game.js +3944 -562
  15. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  16. package/dist-pkg/core/a11y-sr.js +38 -19
  17. package/dist-pkg/core/accessible-label.d.ts +17 -0
  18. package/dist-pkg/core/accessible-label.js +37 -0
  19. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  20. package/dist-pkg/core/accommodation-subjects.js +32 -0
  21. package/dist-pkg/core/accommodations.d.ts +125 -0
  22. package/dist-pkg/core/accommodations.js +195 -0
  23. package/dist-pkg/core/actions.d.ts +108 -55
  24. package/dist-pkg/core/actions.js +122 -71
  25. package/dist-pkg/core/calm-mode.d.ts +31 -0
  26. package/dist-pkg/core/calm-mode.js +47 -0
  27. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  28. package/dist-pkg/core/camera-cycle.js +7 -0
  29. package/dist-pkg/core/caption-duration.d.ts +15 -0
  30. package/dist-pkg/core/caption-duration.js +19 -0
  31. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  32. package/dist-pkg/core/cartridge-problems.js +53 -0
  33. package/dist-pkg/core/constants.d.ts +0 -54
  34. package/dist-pkg/core/constants.js +9 -76
  35. package/dist-pkg/core/contract.d.ts +142 -164
  36. package/dist-pkg/core/contract.js +222 -208
  37. package/dist-pkg/core/dom-query.d.ts +4 -4
  38. package/dist-pkg/core/dom-query.js +12 -20
  39. package/dist-pkg/core/entity.d.ts +62 -77
  40. package/dist-pkg/core/entity.js +18 -32
  41. package/dist-pkg/core/escape-html.d.ts +9 -10
  42. package/dist-pkg/core/escape-html.js +16 -20
  43. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  44. package/dist-pkg/core/flash-threshold.js +113 -0
  45. package/dist-pkg/core/game-speed.d.ts +11 -0
  46. package/dist-pkg/core/game-speed.js +16 -0
  47. package/dist-pkg/core/genres.d.ts +20 -0
  48. package/dist-pkg/core/genres.js +44 -0
  49. package/dist-pkg/core/i18n.d.ts +71 -60
  50. package/dist-pkg/core/i18n.js +186 -200
  51. package/dist-pkg/core/loop.d.ts +17 -6
  52. package/dist-pkg/core/loop.js +26 -26
  53. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  54. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  55. package/dist-pkg/core/ring.d.ts +17 -0
  56. package/dist-pkg/core/ring.js +29 -0
  57. package/dist-pkg/core/rng.d.ts +7 -12
  58. package/dist-pkg/core/rng.js +23 -53
  59. package/dist-pkg/core/route.d.ts +26 -26
  60. package/dist-pkg/core/route.js +126 -121
  61. package/dist-pkg/core/scenes.d.ts +37 -40
  62. package/dist-pkg/core/scenes.js +41 -43
  63. package/dist-pkg/core/screens.d.ts +4 -4
  64. package/dist-pkg/core/screens.js +11 -16
  65. package/dist-pkg/core/session-clock.d.ts +23 -0
  66. package/dist-pkg/core/session-clock.js +37 -0
  67. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  68. package/dist-pkg/core/setting-defaults.js +66 -0
  69. package/dist-pkg/core/speech-rate.d.ts +29 -0
  70. package/dist-pkg/core/speech-rate.js +47 -0
  71. package/dist-pkg/core/state.d.ts +118 -118
  72. package/dist-pkg/core/state.js +322 -297
  73. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  74. package/dist-pkg/core/visual-cycles.js +52 -0
  75. package/dist-pkg/core/visual-state.d.ts +13 -0
  76. package/dist-pkg/core/visual-state.js +6 -0
  77. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  78. package/dist-pkg/educational/activities-registry.js +9 -8
  79. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  80. package/dist-pkg/educational/adaptive-engine.js +17 -17
  81. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  82. package/dist-pkg/educational/segment-bar.js +9 -9
  83. package/dist-pkg/i18n/en.js +324 -223
  84. package/dist-pkg/i18n/es.js +323 -223
  85. package/dist-pkg/i18n/pt.js +348 -226
  86. package/dist-pkg/input/default-bindings.d.ts +37 -38
  87. package/dist-pkg/input/default-bindings.js +127 -144
  88. package/dist-pkg/input/devices.d.ts +7 -7
  89. package/dist-pkg/input/devices.js +17 -16
  90. package/dist-pkg/input/edges.d.ts +23 -32
  91. package/dist-pkg/input/edges.js +23 -28
  92. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  93. package/dist-pkg/input/empathy-filter.js +34 -0
  94. package/dist-pkg/input/face-map.d.ts +96 -0
  95. package/dist-pkg/input/face-map.js +255 -0
  96. package/dist-pkg/input/face-signals.d.ts +40 -0
  97. package/dist-pkg/input/face-signals.js +48 -0
  98. package/dist-pkg/input/gamepad.d.ts +195 -145
  99. package/dist-pkg/input/gamepad.js +383 -556
  100. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  101. package/dist-pkg/input/gaze-cycle.js +133 -0
  102. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  103. package/dist-pkg/input/gaze-relative.js +142 -0
  104. package/dist-pkg/input/hand-map.d.ts +28 -0
  105. package/dist-pkg/input/hand-map.js +142 -0
  106. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  107. package/dist-pkg/input/input-cooldown.js +39 -0
  108. package/dist-pkg/input/key-default.d.ts +30 -0
  109. package/dist-pkg/input/key-default.js +60 -0
  110. package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
  111. package/dist-pkg/input/keyboard-runtime.js +23 -27
  112. package/dist-pkg/input/keyboard.d.ts +68 -63
  113. package/dist-pkg/input/keyboard.js +83 -105
  114. package/dist-pkg/input/keydown.d.ts +123 -137
  115. package/dist-pkg/input/keydown.js +284 -289
  116. package/dist-pkg/input/latch-edge.d.ts +33 -21
  117. package/dist-pkg/input/latch-edge.js +34 -35
  118. package/dist-pkg/input/latch-scope.d.ts +51 -50
  119. package/dist-pkg/input/latch-scope.js +61 -82
  120. package/dist-pkg/input/latch-store.d.ts +26 -30
  121. package/dist-pkg/input/latch-store.js +42 -50
  122. package/dist-pkg/input/latch-sync.d.ts +29 -29
  123. package/dist-pkg/input/latch-sync.js +25 -25
  124. package/dist-pkg/input/latch.d.ts +14 -17
  125. package/dist-pkg/input/latch.js +29 -37
  126. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  127. package/dist-pkg/input/pad-defaults.js +21 -28
  128. package/dist-pkg/input/pad-reading.d.ts +76 -0
  129. package/dist-pkg/input/pad-reading.js +169 -0
  130. package/dist-pkg/input/pad-wizard.d.ts +99 -0
  131. package/dist-pkg/input/pad-wizard.js +256 -0
  132. package/dist-pkg/input/pointer-space.d.ts +19 -20
  133. package/dist-pkg/input/pointer-space.js +27 -32
  134. package/dist-pkg/input/pointer.d.ts +39 -40
  135. package/dist-pkg/input/pointer.js +42 -45
  136. package/dist-pkg/input/state.d.ts +101 -89
  137. package/dist-pkg/input/state.js +54 -134
  138. package/dist-pkg/input/switch-scan.d.ts +59 -0
  139. package/dist-pkg/input/switch-scan.js +67 -0
  140. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  141. package/dist-pkg/input/synthetic-source.js +57 -0
  142. package/dist-pkg/input/touch-bindings.d.ts +127 -116
  143. package/dist-pkg/input/touch-bindings.js +158 -173
  144. package/dist-pkg/input/touch.d.ts +147 -72
  145. package/dist-pkg/input/touch.js +362 -126
  146. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  147. package/dist-pkg/input/transport-in-use.js +130 -0
  148. package/dist-pkg/input/transports.d.ts +90 -117
  149. package/dist-pkg/input/transports.js +104 -106
  150. package/dist-pkg/input/virtual-controller.d.ts +82 -0
  151. package/dist-pkg/input/virtual-controller.js +69 -0
  152. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  153. package/dist-pkg/input/vocabulary-migration.js +80 -79
  154. package/dist-pkg/input/voice-map.d.ts +59 -0
  155. package/dist-pkg/input/voice-map.js +123 -0
  156. package/dist-pkg/platform/audio-ambient.d.ts +13 -2
  157. package/dist-pkg/platform/audio-ambient.js +14 -14
  158. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  159. package/dist-pkg/platform/audio-earcons.js +51 -42
  160. package/dist-pkg/platform/audio-jingles.js +9 -10
  161. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  162. package/dist-pkg/platform/audio-mixer.js +31 -39
  163. package/dist-pkg/platform/audio-sonar.d.ts +49 -152
  164. package/dist-pkg/platform/audio-sonar.js +124 -302
  165. package/dist-pkg/platform/audio.d.ts +62 -19
  166. package/dist-pkg/platform/audio.js +193 -168
  167. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  168. package/dist-pkg/platform/choose-by-voice.js +157 -0
  169. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  170. package/dist-pkg/platform/flash-sampler.js +79 -0
  171. package/dist-pkg/platform/font-library.d.ts +77 -0
  172. package/dist-pkg/platform/font-library.js +98 -0
  173. package/dist-pkg/platform/font-library.json +721 -0
  174. package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
  175. package/dist-pkg/platform/heavy-catalogue.js +256 -0
  176. package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
  177. package/dist-pkg/platform/heavy-mirror.js +73 -0
  178. package/dist-pkg/platform/heavy.d.ts +136 -0
  179. package/dist-pkg/platform/heavy.js +324 -0
  180. package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
  181. package/dist-pkg/platform/interruptible-speech.js +77 -54
  182. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  183. package/dist-pkg/platform/kokoro-port.js +50 -0
  184. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  185. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  186. package/dist-pkg/platform/kokoro.d.ts +70 -0
  187. package/dist-pkg/platform/kokoro.js +133 -0
  188. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  189. package/dist-pkg/platform/listener-scope.js +90 -0
  190. package/dist-pkg/platform/locale-host.d.ts +14 -0
  191. package/dist-pkg/platform/locale-host.js +45 -0
  192. package/dist-pkg/platform/microphone.d.ts +51 -0
  193. package/dist-pkg/platform/microphone.js +118 -0
  194. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  195. package/dist-pkg/platform/onnx-runtime.js +42 -0
  196. package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
  197. package/dist-pkg/platform/reading-in-worker.js +125 -0
  198. package/dist-pkg/platform/reading-model.d.ts +94 -0
  199. package/dist-pkg/platform/reading-model.js +272 -0
  200. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  201. package/dist-pkg/platform/reading-runtime.js +164 -0
  202. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  203. package/dist-pkg/platform/reading-worker.js +58 -0
  204. package/dist-pkg/platform/reading.d.ts +80 -0
  205. package/dist-pkg/platform/reading.js +136 -0
  206. package/dist-pkg/platform/speech-recognition.d.ts +60 -0
  207. package/dist-pkg/platform/speech-recognition.js +104 -0
  208. package/dist-pkg/platform/speech.d.ts +23 -1
  209. package/dist-pkg/platform/speech.js +29 -18
  210. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  211. package/dist-pkg/platform/storage-keys.js +106 -0
  212. package/dist-pkg/platform/storage.d.ts +46 -111
  213. package/dist-pkg/platform/storage.js +104 -169
  214. package/dist-pkg/platform/tts.d.ts +85 -39
  215. package/dist-pkg/platform/tts.js +447 -127
  216. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  217. package/dist-pkg/platform/vision-loop.js +56 -0
  218. package/dist-pkg/platform/vision.d.ts +132 -0
  219. package/dist-pkg/platform/vision.js +117 -0
  220. package/dist-pkg/platform/voice-listener.d.ts +40 -0
  221. package/dist-pkg/platform/voice-listener.js +88 -0
  222. package/dist-pkg/platform/voice-plan.d.ts +15 -98
  223. package/dist-pkg/platform/voice-plan.js +56 -143
  224. package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
  225. package/dist-pkg/platform/vosk-runtime.js +151 -0
  226. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  227. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  228. package/dist-pkg/render/canvas.d.ts +13 -7
  229. package/dist-pkg/render/canvas.js +13 -13
  230. package/dist-pkg/render/crt.d.ts +48 -12
  231. package/dist-pkg/render/crt.js +78 -80
  232. package/dist-pkg/render/cvd-matrices.d.ts +22 -18
  233. package/dist-pkg/render/cvd-matrices.js +37 -38
  234. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  235. package/dist-pkg/render/hc-role-data.js +14 -14
  236. package/dist-pkg/render/high-contrast.d.ts +67 -56
  237. package/dist-pkg/render/high-contrast.js +226 -217
  238. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  239. package/dist-pkg/render/low-vision-drawing.js +46 -0
  240. package/dist-pkg/render/lq-filter.d.ts +32 -20
  241. package/dist-pkg/render/lq-filter.js +59 -65
  242. package/dist-pkg/render/port.d.ts +86 -88
  243. package/dist-pkg/render/port.js +17 -23
  244. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  245. package/dist-pkg/render/screen-pipeline.js +80 -87
  246. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  247. package/dist-pkg/render/sprite-fx.js +16 -14
  248. package/dist-pkg/render/viewports.d.ts +20 -15
  249. package/dist-pkg/render/viewports.js +83 -114
  250. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  251. package/dist-pkg/render/viz-axes-labels.js +88 -0
  252. package/dist-pkg/render/viz-axes.d.ts +89 -86
  253. package/dist-pkg/render/viz-axes.js +128 -116
  254. package/dist-pkg/render/viz-modes.d.ts +28 -29
  255. package/dist-pkg/render/viz-modes.js +40 -45
  256. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  257. package/dist-pkg/render/viz-refusal.js +57 -0
  258. package/dist-pkg/render/viz-setters.d.ts +104 -66
  259. package/dist-pkg/render/viz-setters.js +182 -191
  260. package/dist-pkg/ui/aac-sets.d.ts +45 -0
  261. package/dist-pkg/ui/aac-sets.js +41 -0
  262. package/dist-pkg/ui/audio-choices.d.ts +68 -0
  263. package/dist-pkg/ui/audio-choices.js +98 -0
  264. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  265. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  266. package/dist-pkg/ui/camera-control.d.ts +53 -0
  267. package/dist-pkg/ui/camera-control.js +48 -0
  268. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  269. package/dist-pkg/ui/changed-mark.js +22 -53
  270. package/dist-pkg/ui/control-choices.d.ts +37 -0
  271. package/dist-pkg/ui/control-choices.js +84 -0
  272. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  273. package/dist-pkg/ui/debug-panel.js +68 -69
  274. package/dist-pkg/ui/declared-words.d.ts +21 -0
  275. package/dist-pkg/ui/declared-words.js +66 -0
  276. package/dist-pkg/ui/dom.d.ts +10 -14
  277. package/dist-pkg/ui/dom.js +10 -47
  278. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  279. package/dist-pkg/ui/drawing-problems.js +94 -0
  280. package/dist-pkg/ui/eye-control.d.ts +36 -0
  281. package/dist-pkg/ui/eye-control.js +178 -0
  282. package/dist-pkg/ui/face-control.d.ts +25 -0
  283. package/dist-pkg/ui/face-control.js +183 -0
  284. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  285. package/dist-pkg/ui/focus-trap.js +56 -65
  286. package/dist-pkg/ui/fonts.d.ts +157 -68
  287. package/dist-pkg/ui/fonts.js +305 -115
  288. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  289. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  290. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  291. package/dist-pkg/ui/footer-scroll.js +55 -0
  292. package/dist-pkg/ui/game-options.d.ts +47 -0
  293. package/dist-pkg/ui/game-options.js +158 -0
  294. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  295. package/dist-pkg/ui/gaze-overlay.js +165 -0
  296. package/dist-pkg/ui/hand-control.d.ts +25 -0
  297. package/dist-pkg/ui/hand-control.js +143 -0
  298. package/dist-pkg/ui/help-panel.d.ts +87 -0
  299. package/dist-pkg/ui/help-panel.js +242 -0
  300. package/dist-pkg/ui/hud-bands.d.ts +62 -0
  301. package/dist-pkg/ui/hud-bands.js +146 -0
  302. package/dist-pkg/ui/hud-row.d.ts +34 -0
  303. package/dist-pkg/ui/hud-row.js +80 -0
  304. package/dist-pkg/ui/hud.d.ts +73 -73
  305. package/dist-pkg/ui/hud.js +94 -98
  306. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  307. package/dist-pkg/ui/item-announcement.js +25 -26
  308. package/dist-pkg/ui/layout.d.ts +103 -28
  309. package/dist-pkg/ui/layout.js +117 -99
  310. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  311. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  312. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  313. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  314. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  315. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  316. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  317. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  318. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  319. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  320. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  321. package/dist-pkg/ui/libras-glosses.js +161 -0
  322. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  323. package/dist-pkg/ui/locale-flags.js +30 -0
  324. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  325. package/dist-pkg/ui/loop-crash.js +33 -62
  326. package/dist-pkg/ui/menu-intent.d.ts +61 -0
  327. package/dist-pkg/ui/menu-intent.js +88 -0
  328. package/dist-pkg/ui/menu-items.d.ts +20 -0
  329. package/dist-pkg/ui/menu-items.js +51 -0
  330. package/dist-pkg/ui/menu-nav.d.ts +98 -92
  331. package/dist-pkg/ui/menu-nav.js +398 -286
  332. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  333. package/dist-pkg/ui/mobility-choices.js +41 -0
  334. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  335. package/dist-pkg/ui/motion-choices.js +68 -0
  336. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  337. package/dist-pkg/ui/motion-scene.js +29 -61
  338. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  339. package/dist-pkg/ui/mount-panel.js +61 -0
  340. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  341. package/dist-pkg/ui/panel-shell.js +92 -66
  342. package/dist-pkg/ui/panel-widgets.d.ts +150 -0
  343. package/dist-pkg/ui/panel-widgets.js +294 -0
  344. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  345. package/dist-pkg/ui/pause-buttons.js +66 -0
  346. package/dist-pkg/ui/pause-icons.d.ts +261 -347
  347. package/dist-pkg/ui/pause-icons.js +671 -601
  348. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  349. package/dist-pkg/ui/pause-markup.js +125 -0
  350. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  351. package/dist-pkg/ui/reach-notice.js +55 -58
  352. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  353. package/dist-pkg/ui/scan-overlay.js +50 -0
  354. package/dist-pkg/ui/screen-text.d.ts +25 -0
  355. package/dist-pkg/ui/screen-text.js +152 -0
  356. package/dist-pkg/ui/session-clock.d.ts +32 -0
  357. package/dist-pkg/ui/session-clock.js +67 -0
  358. package/dist-pkg/ui/settings-aac.d.ts +70 -0
  359. package/dist-pkg/ui/settings-aac.js +208 -0
  360. package/dist-pkg/ui/settings-audio.d.ts +97 -114
  361. package/dist-pkg/ui/settings-audio.js +493 -425
  362. package/dist-pkg/ui/settings-controls.d.ts +53 -96
  363. package/dist-pkg/ui/settings-controls.js +143 -215
  364. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  365. package/dist-pkg/ui/settings-empathy.js +53 -60
  366. package/dist-pkg/ui/settings-mobility.d.ts +192 -0
  367. package/dist-pkg/ui/settings-mobility.js +332 -0
  368. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  369. package/dist-pkg/ui/settings-motion.js +287 -198
  370. package/dist-pkg/ui/settings-panel.d.ts +58 -45
  371. package/dist-pkg/ui/settings-panel.js +189 -94
  372. package/dist-pkg/ui/settings-typo.d.ts +29 -78
  373. package/dist-pkg/ui/settings-typo.js +158 -129
  374. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  375. package/dist-pkg/ui/settings-visual.js +240 -193
  376. package/dist-pkg/ui/shell.d.ts +112 -135
  377. package/dist-pkg/ui/shell.js +148 -217
  378. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  379. package/dist-pkg/ui/simulation-list.js +76 -0
  380. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  381. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  382. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  383. package/dist-pkg/ui/simulation-refusal.js +8 -56
  384. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  385. package/dist-pkg/ui/switchable-control.js +19 -0
  386. package/dist-pkg/ui/title.d.ts +3 -12
  387. package/dist-pkg/ui/title.js +49 -18
  388. package/dist-pkg/ui/top-band.d.ts +26 -0
  389. package/dist-pkg/ui/top-band.js +96 -0
  390. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  391. package/dist-pkg/ui/typo-choices.js +85 -0
  392. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  393. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  394. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  395. package/dist-pkg/ui/visual-choices.js +107 -0
  396. package/dist-pkg/ui/vlibras.d.ts +77 -10
  397. package/dist-pkg/ui/vlibras.js +130 -81
  398. package/dist-pkg/ui/voice-control.d.ts +68 -0
  399. package/dist-pkg/ui/voice-control.js +276 -0
  400. package/dist-pkg/ui/voice-settings.d.ts +95 -0
  401. package/dist-pkg/ui/voice-settings.js +352 -0
  402. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  403. package/dist-pkg/ui/where-the-child-is.js +70 -0
  404. package/docs/CREDITS.md +300 -44
  405. package/docs/LICENSES.md +189 -145
  406. package/package.json +51 -27
  407. package/scripts/check-cartridge.mjs +78 -0
  408. package/scripts/game-build.d.mts +25 -0
  409. package/scripts/game-build.mjs +144 -0
  410. package/scripts/heavy-into-the-delivery.mjs +388 -0
  411. package/scripts/libras-avatar.json +680 -0
  412. package/scripts/libras-avatar.mjs +246 -0
  413. package/scripts/libras-glosses/gloss.py +95 -0
  414. package/scripts/libras-glosses/pyproject.toml +32 -0
  415. package/scripts/libras-glosses/uv.lock +781 -0
  416. package/scripts/libras-glosses.mjs +351 -0
  417. package/scripts/licences/Apache-2.0.txt +176 -0
  418. package/scripts/licences/GPL-3.0.txt +674 -0
  419. package/scripts/licences/MIT.txt +17 -0
  420. package/scripts/licences/OFL-1.1.txt +86 -0
  421. package/scripts/licences/UFL-1.0.txt +96 -0
  422. package/scripts/licences/fonts.mjs +389 -0
  423. package/scripts/licences/third-party.mjs +301 -0
  424. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  425. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  427. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  428. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  429. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  431. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  433. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  435. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  437. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  439. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  440. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  441. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  443. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  444. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  445. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  446. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  447. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  448. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  449. package/dist-pkg/core/anel.d.ts +0 -17
  450. package/dist-pkg/core/anel.js +0 -31
  451. package/dist-pkg/core/collision.d.ts +0 -21
  452. package/dist-pkg/core/collision.js +0 -62
  453. package/dist-pkg/core/layers.d.ts +0 -63
  454. package/dist-pkg/core/layers.js +0 -95
  455. package/dist-pkg/core/letter-grid.d.ts +0 -55
  456. package/dist-pkg/core/letter-grid.js +0 -116
  457. package/dist-pkg/core/password.d.ts +0 -34
  458. package/dist-pkg/core/password.js +0 -205
  459. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  460. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  461. package/dist-pkg/core/run-state.d.ts +0 -115
  462. package/dist-pkg/core/run-state.js +0 -40
  463. package/dist-pkg/core/tiles.d.ts +0 -12
  464. package/dist-pkg/core/tiles.js +0 -61
  465. package/dist-pkg/core/world.d.ts +0 -4
  466. package/dist-pkg/core/world.js +0 -58
  467. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  468. package/dist-pkg/input/origem-sintetica.js +0 -60
  469. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  470. package/dist-pkg/input/transporte-em-uso.js +0 -130
  471. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  472. package/dist-pkg/platform/audio-nav.js +0 -99
  473. package/dist-pkg/platform/guide-intensity.d.ts +0 -40
  474. package/dist-pkg/platform/guide-intensity.js +0 -73
  475. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  476. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  477. package/dist-pkg/platform/pesados.d.ts +0 -31
  478. package/dist-pkg/platform/pesados.js +0 -75
  479. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  480. package/dist-pkg/platform/vozes-prontas.js +0 -61
  481. package/dist-pkg/render/camera.d.ts +0 -58
  482. package/dist-pkg/render/camera.js +0 -105
  483. package/dist-pkg/render/cenario-data.d.ts +0 -127
  484. package/dist-pkg/render/cenario-data.js +0 -145
  485. package/dist-pkg/render/city-tex.d.ts +0 -63
  486. package/dist-pkg/render/city-tex.js +0 -228
  487. package/dist-pkg/render/city-tiles.d.ts +0 -11
  488. package/dist-pkg/render/city-tiles.js +0 -140
  489. package/dist-pkg/render/draw.d.ts +0 -149
  490. package/dist-pkg/render/draw.js +0 -231
  491. package/dist-pkg/render/fx.d.ts +0 -63
  492. package/dist-pkg/render/fx.js +0 -123
  493. package/dist-pkg/render/minimap.d.ts +0 -11
  494. package/dist-pkg/render/minimap.js +0 -94
  495. package/dist-pkg/render/parallax.d.ts +0 -85
  496. package/dist-pkg/render/parallax.js +0 -158
  497. package/dist-pkg/render/player-anim.d.ts +0 -71
  498. package/dist-pkg/render/player-anim.js +0 -152
  499. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  500. package/dist-pkg/render/recycling-tex.js +0 -164
  501. package/dist-pkg/render/scene-city.d.ts +0 -77
  502. package/dist-pkg/render/scene-city.js +0 -181
  503. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  504. package/dist-pkg/render/scene-parallax.js +0 -426
  505. package/dist-pkg/render/scene-sky.d.ts +0 -126
  506. package/dist-pkg/render/scene-sky.js +0 -295
  507. package/dist-pkg/render/set-cenario.d.ts +0 -54
  508. package/dist-pkg/render/set-cenario.js +0 -103
  509. package/dist-pkg/render/textures.d.ts +0 -75
  510. package/dist-pkg/render/textures.js +0 -240
  511. package/dist-pkg/render/title-scene.d.ts +0 -46
  512. package/dist-pkg/render/title-scene.js +0 -75
  513. package/dist-pkg/render/weather.d.ts +0 -96
  514. package/dist-pkg/render/weather.js +0 -171
  515. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  516. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  517. package/dist-pkg/render/world-tex.d.ts +0 -18
  518. package/dist-pkg/render/world-tex.js +0 -87
  519. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  520. package/dist-pkg/ui/activities-menu.js +0 -648
  521. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  522. package/dist-pkg/ui/caa-sets.js +0 -65
  523. package/dist-pkg/ui/latch-refusal.d.ts +0 -32
  524. package/dist-pkg/ui/latch-refusal.js +0 -60
  525. package/dist-pkg/ui/map-hub.d.ts +0 -56
  526. package/dist-pkg/ui/map-hub.js +0 -138
  527. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  528. package/dist-pkg/ui/settings-caa.js +0 -137
  529. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  530. package/dist-pkg/ui/settings-motor.js +0 -342
  531. package/dist-pkg/ui/webcam.d.ts +0 -7
  532. package/dist-pkg/ui/webcam.js +0 -93
@@ -0,0 +1,164 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // platform/reading-runtime — THE READING, RUN (ADR-0216 §2; issues #185, #200): sound in, the child's words out.
3
+ //
4
+ // The model of the child's language is opened from the delivery (`heavy/` on the page's own origin, ADR-0177) and run here:
5
+ // the encoder once, then the decoder token by token until it says it is done. What each graph is called, what it takes and what
6
+ // comes back is not guessed — `scratchpad/leitura-de-referencia-3.py` ran the real files and its trace is what
7
+ // `tests/reading-runtime.node.test.js` replays.
8
+ //
9
+ // THREE FAMILIES, and they differ in ways that matter:
10
+ // · Whisper (pt) eats a log-mel spectrogram and opens with four tokens — «transcribe, in Portuguese, without timestamps» — and
11
+ // refuses 88 tokens that are not speech. Without that opening it answers in whatever language it feels like.
12
+ // · Moonshine (es) eats the waveform, in whole frames of the length its config names, and opens with one token, in two decoder
13
+ // graphs like Whisper's.
14
+ // · Moonshine (en) is the same model with ONE MERGED decoder graph, told which pass it is on by `use_cache_branch` and fed empty
15
+ // caches on the first. It is the shape most easily written wrong from a guess, which is why it is traced too.
16
+ //
17
+ // ⚠️ THE CROSS-ATTENTION CACHE IS COMPUTED ONCE AND NEVER AGAIN. The first pass answers a `present.*.encoder.*` per layer that
18
+ // depends only on the sound; feeding back the new one each step is how a loop gets slower with every token AND drifts.
19
+ import { atDelivery, loadOnnxRuntime } from './onnx-runtime.js';
20
+ import { inWholeFrames, logMel, nextToken, readingModelFor, readingTextOf, suppressedTokens, tokenIdOf, waveFrameOf, WHISPER_BANDS, WHISPER_FRAMES, } from './reading-model.js';
21
+ const MAX_TOKENS = 200;
22
+ export async function loadReadingRuntime(d) {
23
+ const plan = readingModelFor(d.language);
24
+ if (!plan)
25
+ throw new Error(`reading: the project has no model for ${d.language}`);
26
+ const get = d.fetch;
27
+ const ort = d.ort ?? await loadOnnxRuntime({ base: d.base, importModule: d.importModule });
28
+ const asJson = async (id) => {
29
+ const r = await get(atDelivery(id, d.base));
30
+ if (!r.ok)
31
+ throw new Error(`reading: HTTP ${r.status} for ${id} — the delivery does not carry this language's model`);
32
+ return await r.json();
33
+ };
34
+ const asSession = async (id) => {
35
+ const r = await get(atDelivery(id, d.base));
36
+ if (!r.ok)
37
+ throw new Error(`reading: HTTP ${r.status} for ${id} — the delivery does not carry this language's model`);
38
+ return ort.InferenceSession.create(new Uint8Array(await r.arrayBuffer()), { executionProviders: ['wasm'] });
39
+ };
40
+ const [tokenizer, generation, config] = await Promise.all([
41
+ asJson(plan.tokenizer),
42
+ asJson(plan.generation),
43
+ asJson(plan.config),
44
+ ]);
45
+ const melFilters = plan.input === 'log-mel' && plan.preprocessor
46
+ ? (await asJson(plan.preprocessor)).mel_filters
47
+ : null;
48
+ const [encoder, decoder, decoderPast] = await Promise.all([
49
+ asSession(plan.encoder),
50
+ asSession(plan.decoder),
51
+ plan.decoderPast ? asSession(plan.decoderPast) : Promise.resolve(null),
52
+ ]);
53
+ const start = openingOf(plan, tokenizer, generation);
54
+ const eos = generation.eos_token_id ?? -1;
55
+ const { heads, width } = cacheSizeOf(config);
56
+ const ceiling = d.maxTokens ?? MAX_TOKENS;
57
+ const frame = waveFrameOf(config);
58
+ return {
59
+ async transcribe(samples) {
60
+ const encoded = await encoder.run(soundFor(ort, plan, samples, melFilters, frame));
61
+ const hidden = encoded[encoder.outputNames[0]];
62
+ const shape = {
63
+ ort, start, hidden, heads, width,
64
+ frames: hidden.dims?.[1] ?? 0,
65
+ // the merged graph wants the caches present and EMPTY on the first pass, and the flag that says which pass this is
66
+ merged: !decoderPast,
67
+ };
68
+ const past = {};
69
+ const ids = [];
70
+ for (let step = 0; step < ceiling; step++) {
71
+ const first = step === 0;
72
+ const session = first || shape.merged ? decoder : decoderPast;
73
+ const answered = await session.run(stepInputs(shape, session, first, ids, past));
74
+ keepPast(past, session, answered, first);
75
+ const chosen = nextToken(lastLogits(answered[session.outputNames[0]]), suppressedTokens(generation, first));
76
+ // no token at all (a row of NaN) is an end, like the end-of-text: pushing it would ask for a word the vocabulary lacks
77
+ if (chosen < 0 || chosen === eos)
78
+ break;
79
+ ids.push(chosen);
80
+ }
81
+ return readingTextOf(tokenizer, ids).trim();
82
+ },
83
+ };
84
+ }
85
+ /**
86
+ * WHAT THE ENCODER HEARS. Whisper a log-mel picture of 30 s drawn with the model's own filters (80 bands × 3000 frames), Moonshine
87
+ * the wave itself, every sample, filled with silence to the whole frames its encoder cuts (`inWholeFrames`) — the microphone's
88
+ * length is not the model's. The names are the graphs' own: a real session refuses any other.
89
+ */
90
+ function soundFor(ort, plan, samples, melFilters, frame) {
91
+ if (plan.input === 'log-mel') {
92
+ return { input_features: new ort.Tensor('float32', logMel(samples, melFilters ?? []), [1, WHISPER_BANDS, WHISPER_FRAMES]) };
93
+ }
94
+ const wave = inWholeFrames(samples, frame);
95
+ return { input_values: new ort.Tensor('float32', wave, [1, wave.length]) };
96
+ }
97
+ /** What one decoder step is handed: the opening on the first, then only the newest token, and the caches so far. */
98
+ function stepInputs(shape, session, first, ids, past) {
99
+ const { ort } = shape;
100
+ const inputIds = first ? shape.start : [ids[ids.length - 1]];
101
+ const inputs = {
102
+ input_ids: new ort.Tensor('int64', BigInt64Array.from(inputIds, BigInt), [1, inputIds.length]),
103
+ encoder_hidden_states: shape.hidden,
104
+ ...(first && shape.merged ? emptyCaches(ort, session, shape.heads, shape.width, shape.frames) : past),
105
+ };
106
+ if (shape.merged)
107
+ inputs.use_cache_branch = new ort.Tensor('bool', Uint8Array.of(first ? 0 : 1), [1]);
108
+ return inputs;
109
+ }
110
+ /**
111
+ * The caches carried to the next step: each `present.*` the decoder answered, renamed to the `past_key_values.*` it takes. The
112
+ * `logits` beside them are the answer, never a cache.
113
+ * ⚠️ ONLY the self-attention half grows. The cross-attention half is the sound, and the sound does not change.
114
+ */
115
+ function keepPast(past, session, answered, first) {
116
+ for (const name of session.outputNames) {
117
+ if (!name.startsWith('present.'))
118
+ continue;
119
+ if (first || name.includes('.decoder.'))
120
+ past[name.replace('present.', 'past_key_values.')] = answered[name];
121
+ }
122
+ }
123
+ /**
124
+ * HOW THE DECODER IS TOLD WHAT TO DO. Moonshine opens with one token and reads what it hears. Whisper opens with four, and they
125
+ * are an instruction: transcribe (not translate), in THIS language, without timestamps. 📏 Measured: without them it answers in
126
+ * whatever language it decides, and with `<|translate|>` it answers in English — a child reading Portuguese would be corrected
127
+ * against an English transcript.
128
+ */
129
+ function openingOf(plan, tokenizer, generation) {
130
+ const first = generation.decoder_start_token_id ?? 0;
131
+ if (plan.input !== 'log-mel')
132
+ return [first];
133
+ const named = [`<|${plan.language}|>`, '<|transcribe|>', '<|notimestamps|>'].map((c) => tokenIdOf(tokenizer, c));
134
+ if (named.some((id) => id === null))
135
+ throw new Error(`reading: this model has no token for ${plan.language} transcription`);
136
+ return [first, ...named];
137
+ }
138
+ /**
139
+ * How big one cache is, from `config.json`: the KEY-VALUE heads (a model with grouped attention keeps fewer than it attends with),
140
+ * and a width of `hidden_size / heads`, 64 where the config does not say.
141
+ */
142
+ function cacheSizeOf(config) {
143
+ return {
144
+ heads: config.num_key_value_heads ?? config.num_attention_heads ?? 0,
145
+ width: config.hidden_size && config.num_attention_heads ? config.hidden_size / config.num_attention_heads : 64,
146
+ };
147
+ }
148
+ /** The caches a merged decoder wants on its first pass: the self-attention halves empty, the cross-attention ones the sound's. */
149
+ function emptyCaches(ort, session, heads, width, frames) {
150
+ const out = {};
151
+ for (const name of session.inputNames ?? []) {
152
+ if (!name.startsWith('past_key_values.'))
153
+ continue;
154
+ const length = name.includes('.decoder.') ? 0 : frames;
155
+ out[name] = new ort.Tensor('float32', new Float32Array(heads * length * width), [1, heads, length, width]);
156
+ }
157
+ return out;
158
+ }
159
+ /** The logits of the LAST position: the decoder answers one row per token it was given, and only the newest one is a choice. */
160
+ function lastLogits(tensor) {
161
+ const t = tensor;
162
+ const vocab = t.dims?.[2] ?? t.data.length;
163
+ return t.data.subarray(t.data.length - vocab);
164
+ }
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { loadReadingRuntime } from './reading-runtime.js';
3
+ /** What the client asks for. */
4
+ export type ReadingRequest = {
5
+ readonly kind: 'load';
6
+ readonly base: string;
7
+ readonly language: string;
8
+ } | {
9
+ readonly kind: 'transcribe';
10
+ readonly id: number;
11
+ readonly samples: Float32Array;
12
+ };
13
+ /** What the worker answers. `id` is absent only on the answer to `load`, which nobody can ask twice. */
14
+ export type ReadingAnswer = {
15
+ readonly kind: 'ready';
16
+ } | {
17
+ readonly kind: 'text';
18
+ readonly id: number;
19
+ readonly text: string;
20
+ } | {
21
+ readonly kind: 'failed';
22
+ readonly id?: number;
23
+ readonly message: string;
24
+ };
25
+ /** The side of the conversation this file needs, so a gate can run the whole protocol without a thread. */
26
+ export interface WorkerScope {
27
+ onmessage: ((event: {
28
+ data: ReadingRequest;
29
+ }) => void) | null;
30
+ postMessage(answer: ReadingAnswer): void;
31
+ /**
32
+ * The worker realm's own `fetch`, which the model's files are fetched with (ADR-0232 D4): the runtime receives it from the
33
+ * scope, so the worker's entry reaches no bare global either.
34
+ */
35
+ fetch(url: string): Promise<Response>;
36
+ }
37
+ /**
38
+ * Wires the protocol onto a scope. Exported — and not only run at import — so the gate exercises the REAL handler instead of a
39
+ * copy of it written in the test, which is the difference between measuring the protocol and measuring a description of it.
40
+ */
41
+ export declare function serveReading(scope: WorkerScope, load?: typeof loadReadingRuntime): void;
@@ -0,0 +1,58 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // platform/reading-worker — THE TRANSCRIPTION, OFF THE MAIN THREAD (ADR-0216 §2; issue #185).
3
+ //
4
+ // 🔴 WHY A THREAD OF ITS OWN, and it is measured and not hygiene: `platform/reading-runtime` runs an encoder and then a decoder
5
+ // token by token, each one a wasm call of tens of milliseconds. On the main thread that is the thread that also draws the game
6
+ // and feeds the microphone — 📏 measured in the lab, Whisper transcribing on the main thread CUT THE AUDIO IN GAPS OF 4 s; the
7
+ // same work in a worker left a biggest gap of 264 ms. A child who reads aloud while the page transcribes loses the words she
8
+ // said during the pause, and nothing anywhere says so.
9
+ //
10
+ // The protocol is three messages and no state the caller cannot see:
11
+ // · `{ kind: 'load', base, language }` → `{ kind: 'ready' }` or `{ kind: 'failed', message }`
12
+ // · `{ kind: 'transcribe', id, samples }` → `{ kind: 'text', id, text }` or `{ kind: 'failed', id, message }`
13
+ // Every answer carries the `id` of the question, because a second reading may be asked for while the first is still running and
14
+ // an answer that cannot be matched to its question is an answer given to the wrong child.
15
+ //
16
+ // ⚠️ THE MODEL IS OPENED HERE, INSIDE THE WORKER, and that is the whole point: `loadReadingRuntime` fetches the graphs from the
17
+ // delivery and the ONNX runtime compiles them. Doing that on the main thread freezes the game for as long as it takes to open
18
+ // 378 MiB of model, which is the other half of the defect this file exists to remove.
19
+ import { loadReadingRuntime } from './reading-runtime.js';
20
+ const reason = (e) => (e instanceof Error ? e.message : String(e));
21
+ /**
22
+ * Wires the protocol onto a scope. Exported — and not only run at import — so the gate exercises the REAL handler instead of a
23
+ * copy of it written in the test, which is the difference between measuring the protocol and measuring a description of it.
24
+ */
25
+ export function serveReading(scope, load = loadReadingRuntime) {
26
+ let transcriber = null;
27
+ scope.onmessage = (event) => {
28
+ const msg = event.data;
29
+ if (msg.kind === 'load') {
30
+ void (async () => {
31
+ try {
32
+ // a METHOD call on the scope: `fetch` detached from its realm throws «Illegal invocation»
33
+ transcriber = await load({ base: msg.base, language: msg.language, fetch: (url) => scope.fetch(url) });
34
+ scope.postMessage({ kind: 'ready' });
35
+ }
36
+ catch (e) {
37
+ scope.postMessage({ kind: 'failed', message: reason(e) });
38
+ }
39
+ })();
40
+ return;
41
+ }
42
+ // ⚠️ A REQUEST BEFORE THE MODEL IS OPEN IS ANSWERED, not dropped: a dropped message leaves the caller's promise pending for
43
+ // ever, and a child waiting for words that will never come has no way to know she should try again.
44
+ void (async () => {
45
+ try {
46
+ if (!transcriber)
47
+ throw new Error('reading worker: asked to transcribe before the model was opened');
48
+ scope.postMessage({ kind: 'text', id: msg.id, text: await transcriber.transcribe(msg.samples) });
49
+ }
50
+ catch (e) {
51
+ scope.postMessage({ kind: 'failed', id: msg.id, message: reason(e) });
52
+ }
53
+ })();
54
+ };
55
+ }
56
+ if (typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' && self instanceof WorkerGlobalScope) {
57
+ serveReading(self);
58
+ }
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { recognitionRoute, type RecognitionApi, type OnDeviceAvailability, type RecognitionInstance } from './speech-recognition.js';
3
+ import type { VoiceChooser, ChooseSettings, ChoiceHeard } from './choose-by-voice.js';
4
+ /** The browser's recognition object, with the parts a reading uses. Assigned handlers, as the API has them. */
5
+ export interface ListeningSession extends RecognitionInstance {
6
+ start(): void;
7
+ stop(): void;
8
+ onresult: ((ev: {
9
+ readonly results: ArrayLike<ArrayLike<{
10
+ readonly transcript: string;
11
+ }> & {
12
+ readonly isFinal: boolean;
13
+ }>;
14
+ }) => void) | null;
15
+ onerror: ((ev: {
16
+ readonly error?: string;
17
+ }) => void) | null;
18
+ onend: (() => void) | null;
19
+ }
20
+ /** What a reading gives back. `route` is which recogniser heard it — for `problems` and for a lab, never for the game's logic. */
21
+ export interface Heard {
22
+ readonly text: string;
23
+ readonly route: 'webspeech-local' | 'model';
24
+ /** The reading stopped because the child went quiet, because the time ran out, or because someone asked it to stop. */
25
+ readonly ended: 'silence' | 'timeout' | 'asked';
26
+ }
27
+ export interface ReadingDeps {
28
+ /** The BCP-47 tag of the child's language (`core/i18n.bcp47`). */
29
+ readonly language: () => string;
30
+ /** The browser's `SpeechRecognition` constructor, or nothing where there is none. */
31
+ readonly api?: RecognitionApi | null;
32
+ readonly now: () => number;
33
+ readonly every: (fn: () => void, ms: number) => unknown;
34
+ readonly stopEvery: (h: unknown) => void;
35
+ /** A line for `problems`, written once per reason (ADR-0169). */
36
+ readonly report: (line: string) => void;
37
+ /**
38
+ * The engine's own recogniser, when there is one to load — the models of ADR-0201, lazily (`platform/reading-runtime`).
39
+ * Absent: the refusal below.
40
+ */
41
+ readonly model?: (language: string) => Promise<{
42
+ transcribe(samples: Float32Array): Promise<string>;
43
+ }>;
44
+ /**
45
+ * The microphone, as 16 kHz mono samples. The model route needs sound, and the browser route does not: the browser's own
46
+ * recogniser opens the microphone itself. ⚠️ Absent with a model present is a reading that REFUSES and says which half is
47
+ * missing — never one that answers an empty sentence, which a game would show a child as «you read nothing».
48
+ */
49
+ readonly record?: (options: ListenOptions) => Promise<Float32Array>;
50
+ readonly route?: typeof recognitionRoute;
51
+ readonly createSession?: (api: RecognitionApi, language: string) => ListeningSession;
52
+ /**
53
+ * The closed-grammar recogniser a choice among shown options is heard by (`platform/choose-by-voice`, ADR-0256). Absent: a
54
+ * choice is REFUSED with its reason, like a reading with nothing to hear it.
55
+ */
56
+ readonly chooser?: VoiceChooser;
57
+ }
58
+ export interface ListenOptions {
59
+ /** How long a silence ends the reading. Default 1.5 s: shorter cuts a child who is thinking between words. */
60
+ readonly silenceMs?: number;
61
+ /** The ceiling, so a forgotten microphone does not listen for ever. Default 30 s, the window Whisper reads at once. */
62
+ readonly maxMs?: number;
63
+ }
64
+ export interface Reading {
65
+ /** Listens until the child goes quiet, and answers with what was read. */
66
+ listen(options?: ListenOptions): Promise<Heard>;
67
+ /**
68
+ * THE CHILD SAYS ONE OF THE OPTIONS THE GAME SHOWS (ADR-0256): pass them as written, get back which one was said — heard by a
69
+ * recogniser that can only answer with one of them. For choosing; `listen()` is for reading aloud.
70
+ */
71
+ choose(options: readonly string[], settings?: ChooseSettings): Promise<ChoiceHeard>;
72
+ /** Gives the microphone back now; the reading or the choice in hand answers with what it has. */
73
+ stop(): void;
74
+ /** Can this device serve the child's language at all? Answers with the reason when it cannot. */
75
+ ready(): Promise<{
76
+ readonly can: boolean;
77
+ readonly why: OnDeviceAvailability | 'sem-api' | 'sem-processamento-local' | 'no-model';
78
+ }>;
79
+ }
80
+ export declare function createReading(d: ReadingDeps): Reading;
@@ -0,0 +1,136 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // platform/reading — THE CHILD READS ALOUD, AND THE GAME RECEIVES TEXT (ADR-0216, issue #200).
3
+ //
4
+ // The Dev, 2026-09-21: «a engine deve oferecer objetos de leitura e TTS. O jogo não deve precisar saber como isso funciona, apenas
5
+ // deve pedir para ouvir e receber o texto.» So this module answers ONE question — what did the child say — and keeps the rest:
6
+ // which recogniser heard it, in which language, and whether it may run at all.
7
+ //
8
+ // · ON THE DEVICE OR NOT AT ALL (ADR-0200 erratum). The browser's recogniser is used only where it says it recognises on the device
9
+ // AND the language is installed there (`platform/speech-recognition`). Anything else waits for the models the project carries
10
+ // (Whisper for pt, Moonshine for en and es — ADR-0201), which arrive through `engine` below and are not built yet: until then a
11
+ // request is REFUSED with its reason, never answered by a recogniser that would send a child's voice to a server.
12
+ // · A reading ends when the child stops talking (`silenceMs`) or when the time runs out (`maxMs`), whichever comes first, and what
13
+ // was heard so far is what the game gets — a child who stops mid-sentence is not an error.
14
+ // · Asking twice does not open two microphones: the second caller waits on the same reading.
15
+ import { recognitionRoute, createOnDeviceRecognition } from './speech-recognition.js';
16
+ const SILENCE_MS = 1500;
17
+ const MAX_MS = 30_000;
18
+ export function createReading(d) {
19
+ const routeOf = d.route ?? recognitionRoute;
20
+ const make = d.createSession ?? ((api, language) => createOnDeviceRecognition(api, language));
21
+ const said = new Set();
22
+ const once = (key, line) => { if (!said.has(key)) {
23
+ said.add(key);
24
+ d.report(line);
25
+ } };
26
+ let inFlight = null;
27
+ let askToStop = null;
28
+ async function ready() {
29
+ const { route: route, status: state } = await routeOf(d.language(), d.api);
30
+ if (route === 'webspeech-local')
31
+ return { can: true, why: state };
32
+ return { can: !!d.model, why: d.model ? state : 'no-model' };
33
+ }
34
+ function byBrowser(api, options) {
35
+ const silenceMs = options.silenceMs ?? SILENCE_MS;
36
+ const maxMs = options.maxMs ?? MAX_MS;
37
+ return new Promise((resolve, reject) => {
38
+ let session;
39
+ try {
40
+ session = make(api, d.language());
41
+ }
42
+ catch (e) {
43
+ reject(e instanceof Error ? e : new Error(String(e)));
44
+ return;
45
+ }
46
+ let text = '', lastSound = d.now(), done = false;
47
+ const ticker = d.every(() => {
48
+ if (done)
49
+ return;
50
+ const quietFor = d.now() - lastSound;
51
+ if (quietFor >= silenceMs && text)
52
+ finish('silence');
53
+ else if (d.now() - startedAt >= maxMs)
54
+ finish('timeout');
55
+ }, 100);
56
+ const startedAt = d.now();
57
+ const finish = (ended) => {
58
+ if (done)
59
+ return;
60
+ done = true;
61
+ d.stopEvery(ticker);
62
+ askToStop = null;
63
+ try {
64
+ session.stop();
65
+ }
66
+ catch { /* a session that already ended is not a failure */ }
67
+ resolve({ text: text.trim(), route: 'webspeech-local', ended });
68
+ };
69
+ askToStop = () => finish('asked');
70
+ session.onresult = (ev) => {
71
+ let together = '';
72
+ for (let i = 0; i < ev.results.length; i++)
73
+ together += `${ev.results[i][0].transcript} `;
74
+ text = together;
75
+ lastSound = d.now();
76
+ };
77
+ session.onerror = (ev) => {
78
+ // ⚠️ A refusal is not a silence: a child waiting to be heard must not wait for ever, and the adult must read why.
79
+ once('error', `reading: the browser's recogniser stopped (${ev.error ?? 'unknown'}) — the child reads and nothing is heard; `
80
+ + 'allow the microphone for this page, or install the language on the device');
81
+ if (!done) {
82
+ done = true;
83
+ d.stopEvery(ticker);
84
+ askToStop = null;
85
+ reject(new Error(ev.error ?? 'recognition failed'));
86
+ }
87
+ };
88
+ session.onend = () => { if (!done)
89
+ finish(text ? 'silence' : 'asked'); };
90
+ session.start();
91
+ });
92
+ }
93
+ return {
94
+ ready,
95
+ stop() { askToStop?.('asked'); d.chooser?.stop(); },
96
+ async choose(options, settings) {
97
+ if (!d.chooser) {
98
+ once('sem-escolha', 'choosing by voice: nothing on this device hears a choice among options — the child answers with the '
99
+ + 'arrows only; the engine lends a chooser where it can open the command recogniser (ADR-0256)');
100
+ throw new Error('choosing by voice is not available here');
101
+ }
102
+ return d.chooser.choose(options, settings);
103
+ },
104
+ async listen(options = {}) {
105
+ if (inFlight)
106
+ return inFlight; // one microphone, one reading: the second caller waits on the first
107
+ const { route: route, status: state } = await routeOf(d.language(), d.api);
108
+ if (route !== 'webspeech-local') {
109
+ if (!d.model) {
110
+ once('sem-route', `reading: nothing on this device can hear ${d.language()} (${state}) — the child's reading is not assessed; `
111
+ + 'install the language in the browser, or build a delivery that carries the reading model for this language');
112
+ throw new Error(`reading is not available here: ${state}`);
113
+ }
114
+ if (!d.record) {
115
+ once('sem-microfone', `reading: the model for ${d.language()} is here and nothing captures the child's voice — `
116
+ + 'the reading cannot run; pass `record`, the 16 kHz samples the model reads');
117
+ throw new Error('reading has no microphone here');
118
+ }
119
+ const capture = d.record;
120
+ inFlight = (async () => {
121
+ const [transcriber, samples] = await Promise.all([d.model(d.language()), capture(options)]);
122
+ return { text: (await transcriber.transcribe(samples)).trim(), route: 'model', ended: 'silence' };
123
+ })();
124
+ }
125
+ else {
126
+ inFlight = byBrowser(d.api, options);
127
+ }
128
+ try {
129
+ return await inFlight;
130
+ }
131
+ finally {
132
+ inFlight = null;
133
+ }
134
+ },
135
+ };
136
+ }
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** What the browser answers about recognising a language on the device (Chrome's `SpeechRecognition.available`). */
3
+ export type OnDeviceAvailability = 'available' | 'downloadable' | 'downloading' | 'unavailable';
4
+ /** The part of the browser's `SpeechRecognition` constructor this module reads. */
5
+ export interface RecognitionApi {
6
+ new (): RecognitionInstance;
7
+ readonly prototype: object;
8
+ readonly available?: (options: {
9
+ langs: readonly string[];
10
+ processLocally: boolean;
11
+ }) => Promise<OnDeviceAvailability>;
12
+ }
13
+ export interface RecognitionInstance {
14
+ lang: string;
15
+ processLocally?: boolean;
16
+ continuous: boolean;
17
+ interimResults: boolean;
18
+ }
19
+ export interface RecognitionRoute {
20
+ /** `webspeech-local`: the browser recognises on the device. `recuo`: the engine's own recogniser. */
21
+ readonly route: 'webspeech-local' | 'recuo';
22
+ /** Why: the browser's answer, or what is missing. */
23
+ readonly status: OnDeviceAvailability | 'sem-api' | 'sem-processamento-local';
24
+ }
25
+ /**
26
+ * THE ROUTE, measured on the device (ADR-0200 §3). ⚠️ A browser whose recognition object has no `processLocally` would take the
27
+ * flag as an unknown property and recognise on its servers — it is never the route, whatever it says about availability.
28
+ */
29
+ export declare function recognitionRoute(language: string, api: RecognitionApi | null | undefined): Promise<RecognitionRoute>;
30
+ /** A recognition object set to recognise on the device, continuously, with partial hypotheses. Refuses a browser that cannot. */
31
+ export declare function createOnDeviceRecognition(api: RecognitionApi, language: string): RecognitionInstance;
32
+ /** A command heard: a direction word, or the name of an item of the open menu. */
33
+ export type HeardCommand = {
34
+ readonly kind: 'palavra';
35
+ readonly word: string;
36
+ } | {
37
+ readonly kind: 'item';
38
+ readonly name: string;
39
+ };
40
+ /**
41
+ * A name or a heard text as a recogniser writes it: lower case, letters, digits and single spaces — and the ACCENTS KEPT, because
42
+ * a closed grammar word spelled without them is not in the model's vocabulary and is dropped (`input/voice-map`, «ação»).
43
+ * The digit stays so «Jogador 1» and «Jogador 2» never collapse into one name.
44
+ */
45
+ export declare const spokenText: (t: string) => string;
46
+ export interface CommandReader {
47
+ /** The menu's item names now on screen (already as spoken). */
48
+ items(names: readonly string[]): void;
49
+ /**
50
+ * One hypothesis of utterance `index`: returns the commands it completes that were not returned before for that utterance. A
51
+ * partial one holds back its last name while another item's name continues it («voltar» may become «voltar ao jogo»).
52
+ */
53
+ read(index: number, text: string, final: boolean): readonly HeardCommand[];
54
+ }
55
+ /**
56
+ * What two spoken forms are COMPARED by: `spokenText` without its accents. The grammar asks the model for «ação» (it would drop
57
+ * «acao»), but a recogniser that writes «acao» back — or a browser recogniser with no closed vocabulary — still said «ação».
58
+ */
59
+ export declare const compareKey: (t: string) => string;
60
+ export declare function createCommandReader(vocabulary: readonly string[]): CommandReader;
@@ -0,0 +1,104 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // platform/speech-recognition — which recogniser hears the child, and what a command is in what was heard (ADR-0200 and its
3
+ // erratum, ADR-0193, ADR-0194; issue #190).
4
+ //
5
+ // The browser's recognition comes first, and ONLY on the device: the Dev, «Só local, voz da criança não deve sair do aparelho». So
6
+ // Web Speech is the route only where the browser can say it recognises on the device (`processLocally`) AND the language is
7
+ // installed there; anything else — no API, a browser that would ignore the flag and send the voice to its servers, a language to
8
+ // download — falls back to the recognisers the engine carries (Vosk for commands). The words heard, whoever heard them, become
9
+ // commands the same way: the direction words and the open menu's item names, the longest name first, a partial hypothesis firing
10
+ // what it already holds and a name another item's name continues waiting for the end (ADR-0194 §3).
11
+ /**
12
+ * THE ROUTE, measured on the device (ADR-0200 §3). ⚠️ A browser whose recognition object has no `processLocally` would take the
13
+ * flag as an unknown property and recognise on its servers — it is never the route, whatever it says about availability.
14
+ */
15
+ export async function recognitionRoute(language, api) {
16
+ if (!api)
17
+ return { route: 'recuo', status: 'sem-api' };
18
+ if (!('processLocally' in api.prototype) || typeof api.available !== 'function')
19
+ return { route: 'recuo', status: 'sem-processamento-local' };
20
+ let availability;
21
+ try {
22
+ availability = await api.available({ langs: [language], processLocally: true });
23
+ }
24
+ catch {
25
+ return { route: 'recuo', status: 'unavailable' };
26
+ }
27
+ return { route: availability === 'available' ? 'webspeech-local' : 'recuo', status: availability };
28
+ }
29
+ /** A recognition object set to recognise on the device, continuously, with partial hypotheses. Refuses a browser that cannot. */
30
+ export function createOnDeviceRecognition(api, language) {
31
+ if (!('processLocally' in api.prototype))
32
+ throw new Error('speech recognition on the device is not supported here: the voice would leave the device');
33
+ const rec = new api();
34
+ rec.lang = language;
35
+ rec.processLocally = true;
36
+ rec.continuous = true;
37
+ rec.interimResults = true;
38
+ return rec;
39
+ }
40
+ /**
41
+ * A name or a heard text as a recogniser writes it: lower case, letters, digits and single spaces — and the ACCENTS KEPT, because
42
+ * a closed grammar word spelled without them is not in the model's vocabulary and is dropped (`input/voice-map`, «ação»).
43
+ * The digit stays so «Jogador 1» and «Jogador 2» never collapse into one name.
44
+ */
45
+ export const spokenText = (t) => t.toLowerCase().normalize('NFC').replace(/[^\p{L}\p{N}\s]/gu, ' ').replace(/\s+/g, ' ').trim();
46
+ /**
47
+ * What two spoken forms are COMPARED by: `spokenText` without its accents. The grammar asks the model for «ação» (it would drop
48
+ * «acao»), but a recogniser that writes «acao» back — or a browser recogniser with no closed vocabulary — still said «ação».
49
+ */
50
+ export const compareKey = (t) => spokenText(t).normalize('NFD').replace(/\p{M}/gu, '');
51
+ /** The phrases a reader knows, by comparison key → the spoken form it answers with (the first one to claim the key). */
52
+ function byKey(phrases) {
53
+ const m = new Map();
54
+ for (const p of phrases) {
55
+ const k = compareKey(p);
56
+ if (k && !m.has(k))
57
+ m.set(k, spokenText(p));
58
+ }
59
+ return m;
60
+ }
61
+ export function createCommandReader(vocabulary) {
62
+ const words = byKey(vocabulary);
63
+ let items = new Map();
64
+ const firedCount = new Map();
65
+ const phrasesIn = (text) => {
66
+ const p = compareKey(text).split(' ').filter(Boolean);
67
+ const found = [];
68
+ for (let i = 0; i < p.length;) {
69
+ let matched = null;
70
+ for (let n = p.length - i; n >= 1; n--) {
71
+ const f = p.slice(i, i + n).join(' ');
72
+ if (items.has(f) || words.has(f)) {
73
+ matched = f;
74
+ i += n;
75
+ break;
76
+ }
77
+ }
78
+ if (matched)
79
+ found.push(matched);
80
+ else
81
+ i++;
82
+ }
83
+ return found;
84
+ };
85
+ return {
86
+ items(names) { items = byKey(names); },
87
+ read(index, text, final) {
88
+ const found = phrasesIn(text);
89
+ const alreadyFired = firedCount.get(index) ?? 0;
90
+ const newCommands = [];
91
+ for (let i = alreadyFired; i < found.length; i++) {
92
+ const f = found[i];
93
+ if (!final && i === found.length - 1 && [...items.keys()].some((o) => o !== f && o.startsWith(f + ' ')))
94
+ break;
95
+ const item = items.get(f);
96
+ newCommands.push(item !== undefined ? { kind: 'item', name: item } : { kind: 'palavra', word: words.get(f) });
97
+ firedCount.set(index, i + 1);
98
+ }
99
+ if (final)
100
+ firedCount.delete(index);
101
+ return newCommands;
102
+ },
103
+ };
104
+ }