@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
@@ -1,171 +1,491 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // platform/tts — narração por voz. Motor NEURAL pt-BR carregado LAZY pela PORTA que o jogo fornece (ADR-0094; modelo
3
- // em cache OPFS → a 2ª sessão fala offline), com FALLBACK imediato p/ a voz nativa do navegador (Web Speech) — que é
4
- // também o que se ouve quando não há porta nenhuma, e é por isso que a ausência dela não cala nada. narrate() é o
5
- // ponto de entrada, gated pelo toggle 'Narração (TTS)' do mixer (audioCat.tts.on) — independe das legendas. As funções de
6
- // PAINEL (populateTTSEngines/Voices/reflectTTS) ficam no game.js (→ ui/settings-audio, #38→#54) e usam get/setEngineSel +
7
- // get/setVoiceObj daqui. Injeção por closure. Ver docs/plano-tts-fase-f5.md + docs/5-Refactoring/plano-modularizacao-mapa.md.
8
- import * as store from './storage.js';
9
- import { t, bcp47 } from '../core/i18n.js';
10
- import { criarFalaInterrompivel } from './interruptible-speech.js';
11
- // ⚠️ MIGRAÇÃO PENDENTE (ADR-0022): a implementação neural de hoje é o @mintplex-labs/piper-tts-web e será SUBSTITUÍDA
12
- // por sherpa-onnx-wasm (loader universal, modelos VITS/Piper + Kokoro-multi-lang carregados de qq URL R2 em runtime via
13
- // FS.writeFile; lazy-fetch; eSpeak/Web Speech de fallback).
14
- // 🔴 O MOTIVO ESCRITO AQUI ERA «@mintplex-labs foi descontinuado e só carrega 2 vozes pt-BR», E AS DUAS METADES
15
- // FORAM MEDIDAS FALSAS EM 2026-09-09 — não pela leitura de um registo, que é como elas se propagaram por seis
16
- // sítios, mas pelo registo npm e pelo pacote instalado:
17
- // · DESCONTINUADO: nenhuma versão tem campo `deprecated`, e a 1.0.5 foi publicada em 2026-08-11 — depois de
18
- // o ADR-0022 (2026-07-06) a dar por morta. A frase é de Julho e está atribuída ao Dev; o registo não a nega
19
- // no passado, nega-a HOJE.
20
- // · DUAS VOZES pt-BR: o `VoiceId` da 1.0.4 instalada traz 118 vozes, e entre elas as QUATRO exactas do
21
- // ADR-0110 — `pt_BR-faber-medium`, `en_US-ryan-medium`, `en_US-amy-medium`, `es_MX-claude-high`.
22
- // ⚠️ O QUE CONTINUA VERDADEIRO, e é mais afiado do que o motivo velho: o bundle prende `HF_BASE` a
23
- // `huggingface.co/diffusionstudio/piper-voices` COM UMA GUARDA (`if (!url.match("https://huggingface.co")) return`),
24
- // logo nenhum espelho de escola é alcançável por ele; e ele busca o PRÓPRIO runtime de cdnjs/jsDelivr por
25
- // omissão, que é o que o ADR-0114 retira. A montante, o Piper mudou para `OHF-Voice/piper1-gpl` e a Open Home
26
- // Foundation procura mantenedores — o risco existe, mas está noutro sítio.
27
- // 📌 A ESCOLHA CONTINUA A SER A #129, e este comentário não a toma: descreve o que foi medido para que o
28
- // próximo leitor não herde o motivo velho como se fosse medição.
29
- // ⚠️ E DESDE O ADR-0094 ESTE MÓDULO NÃO NOMEIA FORNECEDOR NENHUM — quem o nomeia é o JOGO, por `ctx.carregarVozNeural`.
30
- // O nome estava aqui num `import()` e o pacote em `devDependencies`, o que publicou uma engine que não compilava
31
- // (ADR-0093); pô-lo em `dependencies` consertava o build e obrigava todo consumidor a 135,4 MB de `onnxruntime-web`,
32
- // que o fornecedor traz como peer não-opcional. A porta é o que faz a troca do ADR-0022 não ser um segundo abalo.
33
- /**
34
- * Voz NEURAL por idioma, indexada pela etiqueta BCP-47 de core/i18n. Só o pt-BR tem uma hoje, e essa ausência
35
- * é agora CONSULTÁVEL em vez de presumida: `loadTTS` pergunta à tabela se existe voz para o idioma corrente,
36
- * em vez de carregar a portuguesa aconteça o que acontecer.
37
- *
38
- * Era `TTS_SOURCES['pt-BR']` escrito à mão no ponto de uso, o que fazia o jogo em inglês baixar 25,6 MB de
39
- * voz portuguesa para ler texto em inglês com fonética errada — pior que não ter voz neural nenhuma, porque
40
- * gasta a banda da escola para entregar algo ininteligível.
41
- */
42
- const TTS_SOURCES = {
43
- 'pt-BR': { engine: 'piper', voice: 'pt_BR-faber-medium' },
44
- };
2
+ // platform/tts — spoken narration. The browser's voice speaks first (Web Speech, ADR-0200); the neural fallback is Kokoro, which
3
+ // THE ENGINE ITSELF loads from the delivery (ADR-0216 §1) the first time a child picks it — the game only says it wants one; the
4
+ // loader, the browser's speech, the clock and the `<audio>` maker are the root's, received here (ADR-0232 D4). narrate()
5
+ // is the entry point, gated by the mixer's narration toggle (audioCat.tts.on) — independent of captions. The PANEL's side (the
6
+ // engine and voice lists) lives in `ui/voice-settings` and reads and writes the choice through this module. Injection by closure.
7
+ //
8
+ // A SPOKEN TEXT CARRIES ITS LANGUAGE (ADR-0243): `narrate` takes a text, in the interface's language, or parts spoken in order —
9
+ // the frame in the interface's voice, the content in a voice of its own language (`voice-plan.voiceOfLanguage`).
10
+ import { KEYS } from './storage-keys.js';
11
+ import { createInterruptibleSpeech } from './interruptible-speech.js';
12
+ import { voicesForLocale, voiceOfLanguage, speaksLanguageOf, languageName } from './voice-plan.js';
13
+ import { spokenWords, speechSeconds, speechPlaybackRate } from '../core/speech-rate.js';
14
+ import { KOKORO_VOICES, tokenize, sentenceStyle, eFala, wavDe, } from './kokoro.js';
15
+ // 📌 The only neural engine is Kokoro (ADR-0207): the engine's earlier neural engine left when the licence chain of its voices came
16
+ // to light. 📌 And ADR-0094's fear — «a provider named here lands its runtime in every consumer's `node_modules`» — is answered by the
17
+ // delivery and not by a port: the runtime is catalogued, fetched by the build and read from `heavy/`, never imported from npm.
18
+ /** An engine the child may have stored before it left the engine (ADR-0207): read as no explicit choice, so the voice in use speaks. */
19
+ const ENGINES_THAT_LEFT = ['piper'];
45
20
  export function createTts(ctx) {
46
- let ttsEngine = null, ttsLoading = false, ttsFailed = false, _ttsPct = 0, _narrateCount = 0;
47
- let _ttsVoiceObj = null; // voz do Web Speech selecionada
48
- let ttsEngineSel = store.get(store.KEYS.ttsEngine, null) || 'webspeech'; // webspeech | piper | kokoro | kitten | espeak
21
+ const { t, bcp47 } = ctx.translator;
22
+ let ttsEngine = null, ttsLoading = false, ttsFailed = false, _narrateCount = 0;
23
+ let _ttsVoiceObj = null; // the selected Web Speech voice
24
+ // An engine set explicitly (stored, or by the panel) wins; otherwise the engine of the voice in use, which is the browser's when it
25
+ // offers one for the language (ADR-0200) — measured on the device at every call, since the browser lists its voices late.
26
+ const stored = ctx.store.get(KEYS.ttsEngine, null) || null; // webspeech | kokoro | kitten | espeak
27
+ let ttsEngineSelExplicito = stored && !ENGINES_THAT_LEFT.includes(stored) ? stored : null;
28
+ const engineSel = () => {
29
+ if (ttsEngineSelExplicito)
30
+ return ttsEngineSelExplicito;
31
+ const e = voiceInUse()?.engine;
32
+ // a fallback this game did not ask for is not the default: undeclared, the browser's voice (no «not bundled» alert)
33
+ return e === 'kokoro' && ctx.neuralVoice ? e : 'webspeech';
34
+ };
35
+ let kokoroBackend = null;
36
+ let loadedVoice = null; // the voice the loaded engine speaks; another choice reloads it
37
+ /** The loaded Kokoro model's speaker, any of its voices; null until the model loads. */
38
+ let neural = null;
39
+ /** A narration's number: a newer one ends the parts an older one has still to speak (ADR-0243 §1). */
40
+ let narrationTurn = 0;
41
+ // Each voice's words per minute, averaged over the utterances long enough to measure (`core/speech-rate`): what a short
42
+ // utterance («Voltar») is played against. Neural voices by id; browser voices by name.
43
+ const voiceAverage = new Map();
44
+ const media = (voiceName, ppm) => {
45
+ if (!ppm)
46
+ return;
47
+ const before = voiceAverage.get(voiceName);
48
+ voiceAverage.set(voiceName, before ? before * 0.7 + ppm * 0.3 : ppm);
49
+ };
50
+ /**
51
+ * WEB SPEECH FIRST (ADR-0200; issue #190): the browser's voices for the language lead the list, so where the device already speaks
52
+ * no neural voice is loaded; then Kokoro's where the game declared it speaks neurally (ADR-0216 §3) — the fallback.
53
+ */
54
+ /** Every voice the browser lists, in every language: what a part in another language is matched against (ADR-0243 §2). */
55
+ const deviceVoices = () => {
56
+ let list = [];
57
+ try {
58
+ list = ctx.speech.synth()?.getVoices() ?? [];
59
+ }
60
+ catch { /* no speech synthesis here */ }
61
+ return list.map((v) => ({ locale: v.lang.replace('_', '-'), engine: 'webspeech', voice: 'webspeech:' + v.name }));
62
+ };
63
+ const availableVoices = () => [...voicesForLocale(bcp47(), deviceVoices()), ...(ctx.neuralVoice ? voicesForLocale(bcp47(), KOKORO_VOICES) : [])];
64
+ /** The browser voice an entry names, or null. */
65
+ const browserVoice = (id) => {
66
+ if (!id?.startsWith('webspeech:'))
67
+ return null;
68
+ try {
69
+ return ctx.speech.synth()?.getVoices().find((v) => 'webspeech:' + v.name === id) ?? null;
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ };
75
+ /**
76
+ * The interface's voice (ADR-0243 §2 over the language of now): the stored choice, else `voice-plan`'s `VOICE_ORDER` — the exact
77
+ * tag before the region, a browser voice before a neural one.
78
+ */
79
+ function voiceInUse() {
80
+ return voiceOfLanguage(bcp47(), availableVoices(), [ctx.store.get(KEYS.ttsVoz, null)]);
81
+ }
82
+ function chooseVoice(id) {
83
+ if (!availableVoices().some((v) => v.voice === id))
84
+ return false;
85
+ ctx.store.set(KEYS.ttsVoz, id);
86
+ ttsEngineSelExplicito = availableVoices().find((v) => v.voice === id).engine;
87
+ _ttsVoiceObj = browserVoice(id);
88
+ return true;
89
+ }
90
+ /**
91
+ * NARRATION FOLLOWS THE LANGUAGE OF NOW (ADR-0243 §5): the page's voice object when it speaks the language of now; one picked in
92
+ * another language is dropped at the first utterance after the switch, and the voice in use for the new language speaks instead.
93
+ */
94
+ function voiceObjectOfNow() {
95
+ if (_ttsVoiceObj && !speaksLanguageOf(_ttsVoiceObj.lang.replace('_', '-'), bcp47()))
96
+ _ttsVoiceObj = null;
97
+ return _ttsVoiceObj;
98
+ }
99
+ /**
100
+ * One browser utterance: `lang` is the tag the text is in and `voice` a voice of it, or null for the browser to choose by the tag.
101
+ * ADR-0183 §1: the browser's `rate` is a multiplier. The voice's words a minute at rate 1 is measured on its own utterances (start
102
+ * to end, so the silent ends count — an approximation the neural path does not need); until one is measured, 1.
103
+ */
104
+ function browserUtterance(text, lang, voice) {
105
+ const u = ctx.speech.utterance(text);
106
+ u.lang = lang;
107
+ if (voice)
108
+ u.voice = voice;
109
+ u.volume = Math.min(1, ctx.getVolume() * 1.4);
110
+ const voiceIdOf = 'webspeech:' + (voice?.name ?? lang);
111
+ const words = spokenWords(text);
112
+ const voiceBase = voiceAverage.get(voiceIdOf) ?? null;
113
+ u.rate = ctx.getSpeechPpm && voiceBase ? speechPlaybackRate(0, 0, ctx.getSpeechPpm(), voiceBase).rate : 1;
114
+ let startAt = 0;
115
+ u.onstart = () => { startAt = ctx.now(); };
116
+ u.onend = () => {
117
+ const seconds = startAt ? (ctx.now() - startAt) / 1000 : 0;
118
+ const measured = speechPlaybackRate(words, seconds, 150, null).voiceWpm;
119
+ if (measured)
120
+ media(voiceIdOf, measured / u.rate);
121
+ };
122
+ return u;
123
+ }
124
+ /**
125
+ * An utterance in the interface's language. `u.lang` follows the game's language: fixed at pt-BR, a game in English or Spanish
126
+ * would ask the browser for a PORTUGUESE voice for a text that is not Portuguese — and the result is not an accent, it is
127
+ * unintelligible: the wrong phonetics applied to the wrong letters.
128
+ */
129
+ const interfaceUtterance = (text) => browserUtterance(text, bcp47(), voiceObjectOfNow() ?? browserVoice(voiceInUse()?.voice));
49
130
  function speakWebSpeech(text) {
50
131
  try {
51
- const ss = window.speechSynthesis;
132
+ const ss = ctx.speech.synth();
52
133
  if (!ss)
53
134
  return false;
54
135
  ss.cancel();
55
- // `u.lang` era 'pt-BR' fixo. Com o jogo em inglês ou espanhol isso pedia ao navegador uma voz
56
- // PORTUGUESA para um texto que não é português — e o resultado não é sotaque, é ininteligível: a
57
- // fonética errada aplicada às letras erradas. Segue o idioma do jogo.
58
- const u = new SpeechSynthesisUtterance(text);
59
- u.lang = bcp47();
60
- if (_ttsVoiceObj)
61
- u.voice = _ttsVoiceObj;
62
- u.rate = 1;
63
- u.volume = Math.min(1, ctx.getVolume() * 1.4);
64
- ss.speak(u);
136
+ ss.speak(interfaceUtterance(text));
65
137
  return true;
66
138
  }
67
139
  catch (e) {
68
140
  return false;
69
141
  }
70
142
  }
143
+ /**
144
+ * A neural voice's utterances, whatever made the WAV (ADR-0183 §1): every utterance is measured — its words over its speech time,
145
+ * silent ends trimmed — and played at the child's rate over the voice's, through a media element that keeps the pitch. Each
146
+ * request names its voice, so one loaded model reads the parts of every language it has a voice for (ADR-0243 §2).
147
+ */
148
+ function speakByWav(produce) {
149
+ return createInterruptibleSpeech({
150
+ synthesize: async (request) => {
151
+ const { text } = request, voiceId = request.voice.voice;
152
+ const bytes = await produce(request);
153
+ const ac = ctx.ensureAC();
154
+ if (!ac)
155
+ throw new Error('AudioContext unavailable'); // the `catch` there handles it: this item is silent, the engine lives
156
+ let speedRatio = 1;
157
+ if (ctx.getSpeechPpm) {
158
+ const buf = await ac.decodeAudioData(bytes.slice(0));
159
+ const measure = speechPlaybackRate(spokenWords(text), speechSeconds(buf.getChannelData(0), buf.sampleRate), ctx.getSpeechPpm(), voiceAverage.get(voiceId) ?? null);
160
+ media(voiceId, measure.voiceWpm);
161
+ speedRatio = measure.rate;
162
+ }
163
+ return { url: URL.createObjectURL(new Blob([bytes], { type: 'audio/wav' })), rate: speedRatio };
164
+ },
165
+ play: (sound, onFinish) => {
166
+ const ac = ctx.ensureAC();
167
+ if (!ac) {
168
+ URL.revokeObjectURL(sound.url);
169
+ return null;
170
+ }
171
+ const el = ctx.createAudio();
172
+ el.preservesPitch = true;
173
+ el.src = sound.url;
174
+ el.playbackRate = sound.rate;
175
+ try {
176
+ ac.createMediaElementSource(el).connect(ctx.catNode('tts') || ctx.audioOut() || ac.destination);
177
+ }
178
+ catch { /* already routed */ }
179
+ el.onended = () => { URL.revokeObjectURL(sound.url); onFinish(); };
180
+ void el.play().catch(() => { URL.revokeObjectURL(sound.url); onFinish(); });
181
+ return el;
182
+ },
183
+ stop: (el) => { el.pause(); URL.revokeObjectURL(el.src); },
184
+ });
185
+ }
186
+ /**
187
+ * KOKORO (ADR-0186, ADR-0216; issues #181, #200): the voice's text becomes espeak-ng phonemes in its language, then the model's
188
+ * ids and a waveform. WebGPU only where a short test synthesis is speech — measured, WebGPU can run and return noise — and WASM
189
+ * with threads otherwise (ADR-0192).
190
+ */
191
+ function loadKokoroEngine(forVoice) {
192
+ const load = ctx.loadKokoro;
193
+ const kv = KOKORO_VOICES.find((v) => v.voice === forVoice.voice);
194
+ if (!kv) {
195
+ ttsFailed = true;
196
+ ctx.srAlert(t('sr.tts.neuralNotBundled'));
197
+ return;
198
+ }
199
+ ttsLoading = true;
200
+ loadedVoice = forVoice.voice;
201
+ const t0 = ctx.now();
202
+ ctx.srSay(t('sr.tts.downloading'));
203
+ load().then(async (mod) => {
204
+ const [symbolIds, table] = await Promise.all([mod.vocabulary(), mod.voice(kv.voice)]);
205
+ // Each voice's style table, fetched the first time an utterance asks for that voice; a failed fetch is tried again.
206
+ const tables = new Map([[kv.voice, Promise.resolve(table)]]);
207
+ const tableOf = (voice) => {
208
+ let styles = tables.get(voice.voice);
209
+ if (!styles) {
210
+ styles = mod.voice(voice.voice);
211
+ tables.set(voice.voice, styles);
212
+ styles.catch(() => tables.delete(voice.voice));
213
+ }
214
+ return styles;
215
+ };
216
+ const synthesiseWith = async (kokoroSession, text, voice) => {
217
+ const ids = tokenize(await mod.phonemize(text, voice.espeak), symbolIds);
218
+ return kokoroSession.synthesize(ids, sentenceStyle(await tableOf(voice), ids.length - 2));
219
+ };
220
+ let kokoroSession = null;
221
+ try {
222
+ const gpu = await mod.session('webgpu');
223
+ if (eFala(await synthesiseWith(gpu, t('tts.kokoro.teste'), kv))) {
224
+ kokoroSession = gpu;
225
+ kokoroBackend = 'webgpu';
226
+ }
227
+ }
228
+ catch { /* no WebGPU here: WASM below */ }
229
+ if (!kokoroSession) {
230
+ kokoroSession = await mod.session('wasm');
231
+ kokoroBackend = 'wasm';
232
+ }
233
+ const used = kokoroSession;
234
+ const speaker = speakByWav(async (request) => wavDe(await synthesiseWith(used, request.text, request.voice)));
235
+ neural = speaker;
236
+ ttsEngine = { id: 'kokoro', speak: (text, finished) => { speaker.speak({ text, voice: kv }, finished); } };
237
+ ttsLoading = false;
238
+ try {
239
+ ctx.speech.synth()?.cancel();
240
+ }
241
+ catch (e) { /* noop */ }
242
+ narrate(t('sr.tts.ready', { s: ((ctx.now() - t0) / 1000).toFixed(0) }));
243
+ }).catch(() => { ttsLoading = false; ttsFailed = true; ctx.srAlert(t('sr.tts.loadFailed')); });
244
+ }
71
245
  function loadTTS() {
72
246
  if (ttsEngine || ttsLoading || ttsFailed)
73
247
  return;
74
- if (ttsEngineSel !== 'piper') { // Kokoro/Kitten/eSpeak NG ainda não entraram
75
- if (ttsEngineSel !== 'webspeech')
248
+ const sel = engineSel();
249
+ if (sel !== 'kokoro') { // Kitten and eSpeak NG are not built
250
+ if (sel !== 'webspeech')
76
251
  ctx.srAlert(t('sr.tts.engineNoLanguage'));
77
252
  return;
78
253
  }
79
- // ESTA MONTAGEM NÃO TRAZ MOTOR NEURAL (ADR-0094). Vem ANTES da pergunta do idioma de propósito: sem
80
- // porta, não há voz neural em idioma nenhum, e dizer «não há voz para o teu idioma» faria a criança
81
- // pensar que trocar de idioma resolveria. `ttsFailed` para não repetir a pergunta a cada fala.
82
- const carregar = ctx.carregarVozNeural;
83
- if (!carregar) {
254
+ // THIS GAME DID NOT ASK FOR A NEURAL VOICE (ADR-0216 §3). It comes BEFORE the language question on purpose: without
255
+ // the declaration there is no neural voice in any language — the delivery does not carry the model — and saying "there
256
+ // is no voice for your language" would make the child think switching language would fix it. `ttsFailed` so it is not
257
+ // repeated on every utterance.
258
+ if (!ctx.neuralVoice) {
84
259
  ttsFailed = true;
85
260
  ctx.srAlert(t('sr.tts.neuralNotBundled'));
86
261
  return;
87
262
  }
88
- // O Piper só tem voz para os idiomas em TTS_SOURCES. Sem voz para o idioma corrente, NÃO se baixa a de
89
- // outro idioma: avisa e segue na voz do navegador, que fala a língua certa.
90
- const fonte = TTS_SOURCES[bcp47()];
91
- if (!fonte) {
263
+ // The voice in use for the current language (ADR-0185); a browser voice in use with Kokoro set explicitly gives way to the
264
+ // language's first Kokoro voice. None: another language's voice is NOT fetched — it would read this text with the wrong phonetics.
265
+ const current = voiceInUse();
266
+ const forVoice = current?.engine === 'kokoro' ? current : voicesForLocale(bcp47(), KOKORO_VOICES)[0] ?? null;
267
+ if (!forVoice) {
92
268
  ctx.srAlert(t('sr.tts.noNeuralForLanguage'));
93
269
  return;
94
270
  }
95
- ttsLoading = true;
96
- const t0 = performance.now();
97
- ctx.srSay(t('sr.tts.downloading'));
98
- carregar().then(async (mod) => {
99
- const session = await mod.TtsSession.create({ voiceId: fonte.voice,
100
- progress: (p) => { if (!p || !p.total)
101
- return; const pct = Math.round(p.loaded * 100 / p.total); if (pct >= _ttsPct + 25 && pct < 100) {
102
- _ttsPct = pct;
103
- ctx.srSay(t('sr.tts.progress', { pct }));
104
- } },
105
- logger: () => { } });
106
- // A FILA DE UM SAIU (ADR-0044, item 2). Ela era `if (busy) next = text; else speakNow(text)`, e o
107
- // efeito, varrendo cinco itens de menu, era ouvir o PRIMEIRO inteiro e depois o ÚLTIMO — os três do
108
- // meio sumiam, porque cada pedido sobrescrevia o `next`. Lento e lacunar, e quem não enxerga navega
109
- // POR ESCUTA: a escuta ficava vários itens atrás do foco.
110
- //
111
- // A política agora mora em `platform/interruptible-speech`, pura e testada com falsos. Aqui só se
112
- // diz COMO sintetizar, tocar e parar — o quando é lá, com a guarda de geração que impede uma síntese
113
- // lenta de atropelar a mais nova.
114
- const fala = criarFalaInterrompivel({
115
- sintetizar: async (texto) => {
116
- const wav = await session.predict(texto);
117
- const ac = ctx.ensureAC();
118
- if (!ac)
119
- throw new Error('AudioContext unavailable'); // o `catch` de lá trata: silêncio deste item, motor vivo
120
- return ac.decodeAudioData(await wav.arrayBuffer());
121
- },
122
- tocar: (buf, aoTerminar) => {
123
- const ac = ctx.ensureAC();
124
- if (!ac)
125
- return null;
126
- const src = ac.createBufferSource();
127
- src.buffer = buf;
128
- src.connect(ctx.catNode('tts') || ctx.audioOut() || ac.destination);
129
- src.onended = aoTerminar;
130
- src.start();
131
- return src;
132
- },
133
- parar: (src) => { src.stop(); },
134
- });
135
- ttsEngine = { id: 'piper', speak: (text) => { fala.falar(text); } };
136
- ttsLoading = false;
271
+ loadKokoroEngine(forVoice);
272
+ }
273
+ /** The loaded neural engine when the interface speaks neurally; else it starts loading, and the browser speaks meanwhile. */
274
+ function interfaceEngine() {
275
+ if (engineSel() === 'webspeech')
276
+ return null;
277
+ // another voice picked, or the language changed, since the engine loaded: load the voice in use
278
+ const inUse = voiceInUse();
279
+ if (ttsEngine && inUse?.engine !== 'webspeech' && loadedVoice !== inUse?.voice) {
280
+ ttsEngine = null;
281
+ ttsFailed = false;
282
+ }
283
+ if (!ttsEngine)
284
+ loadTTS(); // the neural engine is downloading or unavailable → falls back
285
+ return ttsEngine;
286
+ }
287
+ function ttsSpeak(text) {
288
+ const engine = interfaceEngine();
289
+ if (engine) {
137
290
  try {
138
- window.speechSynthesis && window.speechSynthesis.cancel();
291
+ engine.speak(text);
139
292
  }
140
293
  catch (e) { /* noop */ }
141
- narrate(t('sr.tts.ready', { s: ((performance.now() - t0) / 1000).toFixed(0) })); // já sai NA voz nova
142
- }).catch(() => { ttsLoading = false; ttsFailed = true; ctx.srAlert(t('sr.tts.loadFailed')); });
294
+ return true;
295
+ }
296
+ neural?.silence(); // a neural part still speaking gives way: the latest narration is the one that counts
297
+ return speakWebSpeech(text); // immediate fallback: Web Speech, in the game's language
143
298
  }
144
- function ttsSpeak(text) {
145
- if (ttsEngineSel !== 'webspeech') {
146
- if (ttsEngine && ttsEngine.id === ttsEngineSel && ttsEngine.speak) {
147
- try {
148
- ttsEngine.speak(text);
149
- }
150
- catch (e) { /* noop */ }
151
- return true;
299
+ /** A FRAME part (ADR-0243 §1): the interface's voice, as a text narrated alone; none while narration is locked (ADR-0185 §4). */
300
+ function frameSegment(text) {
301
+ if (availableVoices().length === 0)
302
+ return null;
303
+ const engine = interfaceEngine();
304
+ if (engine)
305
+ return { neural: (finished) => { engine.speak(text, finished); } };
306
+ return { browser: () => interfaceUtterance(text) };
307
+ }
308
+ /**
309
+ * A CONTENT part (ADR-0243 §2): a voice of its language in `voice-plan`'s `VOICE_ORDER` — the exact tag before the region, a
310
+ * browser voice before the neural ones this game asked for, which read only once the model is loaded; while it loads, the part starts the load and a browser voice of the language reads it if
311
+ * the device has one. `null`: the device has no voice for the language, and no voice of another language reads it instead.
312
+ */
313
+ function contentSegment(text, language) {
314
+ const chosen = chosenVoices();
315
+ const neuralVoices = ctx.neuralVoice && !ttsFailed ? KOKORO_VOICES : [];
316
+ const best = voiceOfLanguage(language, [...deviceVoices(), ...neuralVoices], chosen);
317
+ if (!best)
318
+ return noVoiceSegment(language);
319
+ const kokoro = KOKORO_VOICES.find((v) => v.voice === best.voice);
320
+ return kokoro ? neuralSegment(text, language, kokoro, chosen) : browserSegment(text, language, best);
321
+ }
322
+ /** The languages this root has already said it has no voice for, by primary language (ADR-0243 §3). */
323
+ const toldNoVoice = new Set();
324
+ /**
325
+ * NO VOICE FOR IT, NO WRONG VOICE (ADR-0243 §3): the part is not spoken — the text stays on the screen. Once per language, the
326
+ * interface's voice says so in its place, and `problems` gets one line naming the language and the fix.
327
+ */
328
+ function noVoiceSegment(language) {
329
+ const english = languageName(language, 'en');
330
+ const code = language.split('-')[0].toLowerCase();
331
+ if (toldNoVoice.has(code))
332
+ return null;
333
+ toldNoVoice.add(code);
334
+ ctx.report?.(`narration: the device lacks a voice for ${english} (${code}), so a part a game gave in ${english} is not read aloud `
335
+ + `and a child who listens does not hear it (the text stays on the screen) — install a voice for ${english} on the device, `
336
+ + `in its text-to-speech settings`);
337
+ return frameSegment(t('sr.tts.noVoiceForLanguage', { language: languageName(language, bcp47()) }));
338
+ }
339
+ /** The voices the child chose, in order of precedence: the page's voice object, then the stored choice (ADR-0243 §2). */
340
+ const chosenVoices = () => [_ttsVoiceObj ? 'webspeech:' + _ttsVoiceObj.name : null, ctx.store.get(KEYS.ttsVoz, null)];
341
+ const browserSegment = (text, language, voice) => ({ browser: () => browserUtterance(text, language, browserVoice(voice.voice)) });
342
+ /** A part a Kokoro voice reads; while the model loads, a browser voice of the language reads it if the device has one. */
343
+ function neuralSegment(text, language, voice, chosen) {
344
+ const speaker = neural;
345
+ if (speaker)
346
+ return { neural: (finished) => { speaker.speak({ text, voice }, finished); } };
347
+ if (!ttsLoading)
348
+ loadKokoroEngine(voice);
349
+ const meanwhile = voiceOfLanguage(language, deviceVoices(), chosen);
350
+ return meanwhile ? browserSegment(text, language, meanwhile) : null;
351
+ }
352
+ /**
353
+ * The utterances a narration waits on, held until their end is heard: an utterance nothing holds may be collected by the
354
+ * browser before it ends, and then its end is never heard — and a player's sonar would wait behind it forever (ADR-0234).
355
+ */
356
+ const heldUntilOver = new Set();
357
+ /** Resolves when an utterance is over, however it ends — set before it is queued. */
358
+ const utteranceOver = (u) => new Promise((over) => {
359
+ heldUntilOver.add(u);
360
+ const done = () => { heldUntilOver.delete(u); over(); };
361
+ const measure = u.onend;
362
+ u.onend = function (e) { measure?.call(this, e); done(); };
363
+ u.onerror = done;
364
+ });
365
+ /**
366
+ * Queues the browser parts from `from` on at once; `over` resolves when the last is over, when a part follows them or the
367
+ * caller waits for the end (`untilOver`). A device that refuses to speak has nothing to wait for: the rest goes on.
368
+ */
369
+ function queueBrowserParts(ss, segments, from, untilOver) {
370
+ const queued = [];
371
+ let i = from;
372
+ for (let s = segments[i]; s && 'browser' in s; s = segments[++i])
373
+ queued.push(s.browser());
374
+ const last = queued.at(-1);
375
+ const over = ss && last && (i < segments.length || untilOver) ? utteranceOver(last) : null;
376
+ try {
377
+ for (const u of queued)
378
+ ss?.speak(u);
379
+ }
380
+ catch (e) {
381
+ return { next: i, over: null };
382
+ }
383
+ return { next: i, over };
384
+ }
385
+ /**
386
+ * THE PARTS, IN ORDER (ADR-0243 §1). Browser utterances go into the browser's own queue at once, each with its tag and voice; a
387
+ * neural part waits for what is before it to end, and what follows waits for it. A newer narration ends the rest. `untilOver`:
388
+ * resolves only when the last part is over, for a narration waiting its turn behind this one.
389
+ */
390
+ async function speakInOrder(segments, turn, untilOver = false) {
391
+ const ss = ctx.speech.synth();
392
+ try {
393
+ ss?.cancel();
394
+ }
395
+ catch (e) { /* noop */ }
396
+ neural?.silence();
397
+ let i = 0;
398
+ while (i < segments.length && turn === narrationTurn) {
399
+ const first = segments[i];
400
+ if ('neural' in first) {
401
+ await new Promise((over) => { first.neural(over); });
402
+ i++;
403
+ continue;
152
404
  }
153
- loadTTS(); // motor neural (baixando/indisponível) → cai no fallback
405
+ const queuedParts = queueBrowserParts(ss, segments, i, untilOver);
406
+ i = queuedParts.next;
407
+ if (queuedParts.over)
408
+ await queuedParts.over;
409
+ }
410
+ }
411
+ /** The players' narrations waiting behind the one being spoken (ADR-0234), at most one per player, in the order they asked. */
412
+ const waiting = [];
413
+ /** The `narrationTurn` of the player's narration being spoken, and whose it is; any narration since has taken the turn. */
414
+ let turnSpeaking = -1, seatSpeaking = -1;
415
+ /**
416
+ * A PLAYER'S NARRATION TAKES TURNS (ADR-0234, errata 2026-09-26): behind ANOTHER player's still being spoken it waits — replacing
417
+ * that player's own waiting one, in its place — and is spoken when the line reaches it. With the SAME player's being spoken, or
418
+ * none, it starts at once and cuts what is spoken now, as every narration does; the others waiting keep waiting behind it. A
419
+ * narration that names no player takes the turn, so the next player's finds none being spoken and nobody waiting.
420
+ */
421
+ function narrateInTurn(seat, segments) {
422
+ _narrateCount++;
423
+ const speakingNow = turnSpeaking === narrationTurn;
424
+ if (speakingNow && seat !== seatSpeaking) {
425
+ const own = waiting.find((w) => w.seat === seat);
426
+ if (own)
427
+ own.segments = segments;
428
+ else
429
+ waiting.push({ seat, segments });
430
+ return;
154
431
  }
155
- return speakWebSpeech(text); // fallback imediato: Web Speech, no idioma do jogo
432
+ if (!speakingNow)
433
+ waiting.length = 0; // their turn was ended by a narration that named no player
434
+ void speakInTurn({ seat, segments }, ++narrationTurn);
156
435
  }
157
- function narrate(text) {
436
+ async function speakInTurn(first, turn) {
437
+ turnSpeaking = turn;
438
+ for (let next = first; next; next = waiting.shift()) {
439
+ seatSpeaking = next.seat;
440
+ await speakInOrder(next.segments, turn, true);
441
+ if (turn !== narrationTurn)
442
+ return; // cut: the line is the newer narration's now
443
+ }
444
+ turnSpeaking = -1;
445
+ seatSpeaking = -1;
446
+ }
447
+ function narrate(text, how = {}) {
158
448
  const cat = ctx.getAudioCat();
159
- if (!ctx.getSoundOn() || !cat || !cat.tts || !cat.tts.on || !text)
449
+ if (!ctx.getSoundOn() || !cat || !cat.tts || !cat.tts.on)
450
+ return;
451
+ if (typeof text !== 'string') {
452
+ narrateParts(text, how);
160
453
  return;
454
+ }
455
+ if (!text)
456
+ return;
457
+ if (availableVoices().length === 0)
458
+ return; // no voice speaks this language: narration is locked (ADR-0185 §4)
459
+ if (how.seat !== undefined) {
460
+ const frame = frameSegment(text);
461
+ if (frame)
462
+ narrateInTurn(how.seat, [frame]);
463
+ return;
464
+ }
465
+ narrationTurn++;
161
466
  _narrateCount++;
162
467
  ttsSpeak(text);
163
468
  }
469
+ function narrateParts(parts, how) {
470
+ const segments = parts.filter((p) => p.text)
471
+ .map((p) => (p.language ? contentSegment(p.text, p.language) : frameSegment(p.text)))
472
+ .filter((s) => s !== null);
473
+ if (!segments.length)
474
+ return;
475
+ if (how.seat !== undefined) {
476
+ narrateInTurn(how.seat, segments);
477
+ return;
478
+ }
479
+ _narrateCount++;
480
+ void speakInOrder(segments, ++narrationTurn);
481
+ }
164
482
  return {
165
483
  narrate, ttsSpeak, loadTTS, speakWebSpeech,
166
- getEngineSel: () => ttsEngineSel, setEngineSel: (v) => { ttsEngineSel = v; },
484
+ getEngineSel: engineSel, setEngineSel: (v) => { ttsEngineSelExplicito = v; },
167
485
  getEngine: () => ttsEngine, getVoiceObj: () => _ttsVoiceObj, setVoiceObj: (v) => { _ttsVoiceObj = v; },
168
486
  get loading() { return ttsLoading; }, get failed() { return ttsFailed; }, get narrateCount() { return _narrateCount; },
169
- get neuralDisponivel() { return !!ctx.carregarVozNeural; },
487
+ get neuralAvailable() { return !!ctx.neuralVoice; },
488
+ get kokoroDevice() { return kokoroBackend; },
489
+ voices: availableVoices, currentVoice: voiceInUse, setVoice: chooseVoice,
170
490
  };
171
491
  }