sci2-ts 0.0.0-stage → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/apps/viewer/editor.html +43 -0
  5. package/apps/viewer/explore.html +27 -0
  6. package/apps/viewer/index.html +26 -0
  7. package/apps/viewer/package.json +27 -0
  8. package/apps/viewer/play.html +26 -0
  9. package/apps/viewer/plugin.d.ts +18 -0
  10. package/apps/viewer/plugin.js +75 -0
  11. package/apps/viewer/plugin.ts +84 -0
  12. package/apps/viewer/src/adlib.d.ts +20 -0
  13. package/apps/viewer/src/adlib.js +55 -0
  14. package/apps/viewer/src/adlib.ts +62 -0
  15. package/apps/viewer/src/app.d.ts +5 -0
  16. package/apps/viewer/src/app.js +5 -0
  17. package/apps/viewer/src/app.ts +5 -0
  18. package/apps/viewer/src/asset-index.d.ts +22 -0
  19. package/apps/viewer/src/asset-index.js +74 -0
  20. package/apps/viewer/src/asset-index.ts +90 -0
  21. package/apps/viewer/src/audio.d.ts +28 -0
  22. package/apps/viewer/src/audio.js +102 -0
  23. package/apps/viewer/src/audio.ts +110 -0
  24. package/apps/viewer/src/editor-handles.d.ts +63 -0
  25. package/apps/viewer/src/editor-handles.js +332 -0
  26. package/apps/viewer/src/editor-handles.ts +368 -0
  27. package/apps/viewer/src/editor-languages.d.ts +23 -0
  28. package/apps/viewer/src/editor-languages.js +157 -0
  29. package/apps/viewer/src/editor-languages.ts +135 -0
  30. package/apps/viewer/src/editor.css +213 -0
  31. package/apps/viewer/src/editor.d.ts +1 -0
  32. package/apps/viewer/src/editor.js +393 -0
  33. package/apps/viewer/src/editor.ts +412 -0
  34. package/apps/viewer/src/explore/classes.d.ts +7 -0
  35. package/apps/viewer/src/explore/classes.js +63 -0
  36. package/apps/viewer/src/explore/classes.ts +61 -0
  37. package/apps/viewer/src/explore/context.d.ts +33 -0
  38. package/apps/viewer/src/explore/context.js +96 -0
  39. package/apps/viewer/src/explore/context.ts +124 -0
  40. package/apps/viewer/src/explore/explore.css +93 -0
  41. package/apps/viewer/src/explore/explore.d.ts +1 -0
  42. package/apps/viewer/src/explore/explore.js +102 -0
  43. package/apps/viewer/src/explore/explore.ts +104 -0
  44. package/apps/viewer/src/explore/inspect.d.ts +5 -0
  45. package/apps/viewer/src/explore/inspect.js +208 -0
  46. package/apps/viewer/src/explore/inspect.ts +279 -0
  47. package/apps/viewer/src/explore/live.d.ts +22 -0
  48. package/apps/viewer/src/explore/live.js +318 -0
  49. package/apps/viewer/src/explore/live.ts +365 -0
  50. package/apps/viewer/src/explore/map.d.ts +11 -0
  51. package/apps/viewer/src/explore/map.js +182 -0
  52. package/apps/viewer/src/explore/map.ts +193 -0
  53. package/apps/viewer/src/files.d.ts +21 -0
  54. package/apps/viewer/src/files.js +69 -0
  55. package/apps/viewer/src/files.ts +72 -0
  56. package/apps/viewer/src/game-profile.d.ts +19 -0
  57. package/apps/viewer/src/game-profile.js +4 -0
  58. package/apps/viewer/src/game-profile.ts +26 -0
  59. package/apps/viewer/src/main.d.ts +1 -0
  60. package/apps/viewer/src/main.js +473 -0
  61. package/apps/viewer/src/main.ts +527 -0
  62. package/apps/viewer/src/music.d.ts +14 -0
  63. package/apps/viewer/src/music.js +32 -0
  64. package/apps/viewer/src/music.ts +34 -0
  65. package/apps/viewer/src/opl-worklet.d.ts +1 -0
  66. package/apps/viewer/src/opl-worklet.js +47 -0
  67. package/apps/viewer/src/opl-worklet.ts +55 -0
  68. package/apps/viewer/src/overlay.d.ts +31 -0
  69. package/apps/viewer/src/overlay.js +103 -0
  70. package/apps/viewer/src/overlay.ts +123 -0
  71. package/apps/viewer/src/play.css +14 -0
  72. package/apps/viewer/src/play.d.ts +1 -0
  73. package/apps/viewer/src/play.js +179 -0
  74. package/apps/viewer/src/play.ts +177 -0
  75. package/apps/viewer/src/saves.d.ts +10 -0
  76. package/apps/viewer/src/saves.js +50 -0
  77. package/apps/viewer/src/saves.ts +51 -0
  78. package/apps/viewer/src/session.d.ts +91 -0
  79. package/apps/viewer/src/session.js +301 -0
  80. package/apps/viewer/src/session.ts +321 -0
  81. package/apps/viewer/src/sound-preview.d.ts +7 -0
  82. package/apps/viewer/src/sound-preview.js +214 -0
  83. package/apps/viewer/src/sound-preview.ts +228 -0
  84. package/apps/viewer/src/style.css +64 -0
  85. package/apps/viewer/vite.config.ts +25 -0
  86. package/bin/sci-ts.mjs +9 -0
  87. package/lib/994.sc +49 -0
  88. package/lib/995.sc +53 -0
  89. package/lib/996.sc +299 -0
  90. package/lib/997.sc +198 -0
  91. package/lib/998.sc +338 -0
  92. package/lib/999.sc +189 -0
  93. package/lib/resources.d.ts +2 -0
  94. package/lib/resources.js +84 -0
  95. package/lib/resources.ts +88 -0
  96. package/lib/system.sh +62 -0
  97. package/package.json +75 -4
  98. package/packages/content/room.schema.json +267 -0
  99. package/packages/content/src/index.d.ts +3 -0
  100. package/packages/content/src/index.js +3 -0
  101. package/packages/content/src/index.ts +3 -0
  102. package/packages/content/src/room.d.ts +111 -0
  103. package/packages/content/src/room.js +886 -0
  104. package/packages/content/src/room.ts +1011 -0
  105. package/packages/content/src/target.d.ts +80 -0
  106. package/packages/content/src/target.js +1 -0
  107. package/packages/content/src/target.ts +60 -0
  108. package/packages/content/src/yarn.d.ts +90 -0
  109. package/packages/content/src/yarn.js +214 -0
  110. package/packages/content/src/yarn.ts +261 -0
  111. package/packages/sci/src/analysis/game.d.ts +90 -0
  112. package/packages/sci/src/analysis/game.js +221 -0
  113. package/packages/sci/src/analysis/game.ts +305 -0
  114. package/packages/sci/src/analysis/runtime.d.ts +58 -0
  115. package/packages/sci/src/analysis/runtime.js +133 -0
  116. package/packages/sci/src/analysis/runtime.ts +163 -0
  117. package/packages/sci/src/audio/adlib.d.ts +73 -0
  118. package/packages/sci/src/audio/adlib.js +280 -0
  119. package/packages/sci/src/audio/adlib.ts +329 -0
  120. package/packages/sci/src/audio/index.d.ts +43 -0
  121. package/packages/sci/src/audio/index.js +99 -0
  122. package/packages/sci/src/audio/index.ts +122 -0
  123. package/packages/sci/src/audio/midi.d.ts +92 -0
  124. package/packages/sci/src/audio/midi.js +242 -0
  125. package/packages/sci/src/audio/midi.ts +262 -0
  126. package/packages/sci/src/audio/opl2.d.ts +74 -0
  127. package/packages/sci/src/audio/opl2.js +283 -0
  128. package/packages/sci/src/audio/opl2.ts +283 -0
  129. package/packages/sci/src/audio/sol.d.ts +32 -0
  130. package/packages/sci/src/audio/sol.js +81 -0
  131. package/packages/sci/src/audio/sol.ts +100 -0
  132. package/packages/sci/src/audio/write.d.ts +35 -0
  133. package/packages/sci/src/audio/write.js +91 -0
  134. package/packages/sci/src/audio/write.ts +108 -0
  135. package/packages/sci/src/gfx/cel.d.ts +36 -0
  136. package/packages/sci/src/gfx/cel.js +82 -0
  137. package/packages/sci/src/gfx/cel.ts +110 -0
  138. package/packages/sci/src/gfx/palette.d.ts +24 -0
  139. package/packages/sci/src/gfx/palette.js +49 -0
  140. package/packages/sci/src/gfx/palette.ts +56 -0
  141. package/packages/sci/src/gfx/pic-file.d.ts +46 -0
  142. package/packages/sci/src/gfx/pic-file.js +114 -0
  143. package/packages/sci/src/gfx/pic-file.ts +155 -0
  144. package/packages/sci/src/gfx/pic.d.ts +27 -0
  145. package/packages/sci/src/gfx/pic.js +34 -0
  146. package/packages/sci/src/gfx/pic.ts +53 -0
  147. package/packages/sci/src/gfx/quantize.d.ts +42 -0
  148. package/packages/sci/src/gfx/quantize.js +199 -0
  149. package/packages/sci/src/gfx/quantize.ts +218 -0
  150. package/packages/sci/src/gfx/view-file.d.ts +62 -0
  151. package/packages/sci/src/gfx/view-file.js +182 -0
  152. package/packages/sci/src/gfx/view-file.ts +233 -0
  153. package/packages/sci/src/gfx/view.d.ts +28 -0
  154. package/packages/sci/src/gfx/view.js +45 -0
  155. package/packages/sci/src/gfx/view.ts +62 -0
  156. package/packages/sci/src/index.d.ts +38 -0
  157. package/packages/sci/src/index.js +38 -0
  158. package/packages/sci/src/index.ts +38 -0
  159. package/packages/sci/src/motion/pathfinding.d.ts +40 -0
  160. package/packages/sci/src/motion/pathfinding.js +140 -0
  161. package/packages/sci/src/motion/pathfinding.ts +147 -0
  162. package/packages/sci/src/resource/archive.d.ts +16 -0
  163. package/packages/sci/src/resource/archive.js +51 -0
  164. package/packages/sci/src/resource/archive.ts +56 -0
  165. package/packages/sci/src/resource/lzs.d.ts +12 -0
  166. package/packages/sci/src/resource/lzs.js +64 -0
  167. package/packages/sci/src/resource/lzs.ts +66 -0
  168. package/packages/sci/src/resource/manager.d.ts +45 -0
  169. package/packages/sci/src/resource/manager.js +158 -0
  170. package/packages/sci/src/resource/manager.ts +184 -0
  171. package/packages/sci/src/resource/map.d.ts +18 -0
  172. package/packages/sci/src/resource/map.js +34 -0
  173. package/packages/sci/src/resource/map.ts +44 -0
  174. package/packages/sci/src/resource/types.d.ts +37 -0
  175. package/packages/sci/src/resource/types.js +45 -0
  176. package/packages/sci/src/resource/types.ts +57 -0
  177. package/packages/sci/src/script/assembly.d.ts +67 -0
  178. package/packages/sci/src/script/assembly.js +748 -0
  179. package/packages/sci/src/script/assembly.ts +714 -0
  180. package/packages/sci/src/script/compiler.d.ts +75 -0
  181. package/packages/sci/src/script/compiler.js +993 -0
  182. package/packages/sci/src/script/compiler.ts +1040 -0
  183. package/packages/sci/src/script/disasm.d.ts +81 -0
  184. package/packages/sci/src/script/disasm.js +318 -0
  185. package/packages/sci/src/script/disasm.ts +323 -0
  186. package/packages/sci/src/script/kernel-names.d.ts +6 -0
  187. package/packages/sci/src/script/kernel-names.js +165 -0
  188. package/packages/sci/src/script/kernel-names.ts +165 -0
  189. package/packages/sci/src/script/opcodes.d.ts +22 -0
  190. package/packages/sci/src/script/opcodes.js +45 -0
  191. package/packages/sci/src/script/opcodes.ts +72 -0
  192. package/packages/sci/src/script/script.d.ts +56 -0
  193. package/packages/sci/src/script/script.js +63 -0
  194. package/packages/sci/src/script/script.ts +111 -0
  195. package/packages/sci/src/script/sexpr.d.ts +45 -0
  196. package/packages/sci/src/script/sexpr.js +151 -0
  197. package/packages/sci/src/script/sexpr.ts +139 -0
  198. package/packages/sci/src/script/vocab.d.ts +15 -0
  199. package/packages/sci/src/script/vocab.js +44 -0
  200. package/packages/sci/src/script/vocab.ts +48 -0
  201. package/packages/sci/src/script/world.d.ts +25 -0
  202. package/packages/sci/src/script/world.js +65 -0
  203. package/packages/sci/src/script/world.ts +70 -0
  204. package/packages/sci/src/text/font.d.ts +37 -0
  205. package/packages/sci/src/text/font.js +103 -0
  206. package/packages/sci/src/text/font.ts +125 -0
  207. package/packages/sci/src/text/message.d.ts +69 -0
  208. package/packages/sci/src/text/message.js +171 -0
  209. package/packages/sci/src/text/message.ts +205 -0
  210. package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
  211. package/packages/sci/src/vm/kernels/arrays.js +182 -0
  212. package/packages/sci/src/vm/kernels/arrays.ts +164 -0
  213. package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
  214. package/packages/sci/src/vm/kernels/audio.js +347 -0
  215. package/packages/sci/src/vm/kernels/audio.ts +349 -0
  216. package/packages/sci/src/vm/kernels/core.d.ts +5 -0
  217. package/packages/sci/src/vm/kernels/core.js +87 -0
  218. package/packages/sci/src/vm/kernels/core.ts +91 -0
  219. package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
  220. package/packages/sci/src/vm/kernels/graphics.js +467 -0
  221. package/packages/sci/src/vm/kernels/graphics.ts +502 -0
  222. package/packages/sci/src/vm/kernels/index.d.ts +8 -0
  223. package/packages/sci/src/vm/kernels/index.js +29 -0
  224. package/packages/sci/src/vm/kernels/index.ts +32 -0
  225. package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
  226. package/packages/sci/src/vm/kernels/lists.js +127 -0
  227. package/packages/sci/src/vm/kernels/lists.ts +111 -0
  228. package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
  229. package/packages/sci/src/vm/kernels/motion.js +204 -0
  230. package/packages/sci/src/vm/kernels/motion.ts +194 -0
  231. package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
  232. package/packages/sci/src/vm/kernels/palette.js +292 -0
  233. package/packages/sci/src/vm/kernels/palette.ts +303 -0
  234. package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
  235. package/packages/sci/src/vm/kernels/saves.js +123 -0
  236. package/packages/sci/src/vm/kernels/saves.ts +149 -0
  237. package/packages/sci/src/vm/kernels/system.d.ts +62 -0
  238. package/packages/sci/src/vm/kernels/system.js +110 -0
  239. package/packages/sci/src/vm/kernels/system.ts +128 -0
  240. package/packages/sci/src/vm/kernels/text.d.ts +13 -0
  241. package/packages/sci/src/vm/kernels/text.js +338 -0
  242. package/packages/sci/src/vm/kernels/text.ts +328 -0
  243. package/packages/sci/src/vm/memory.d.ts +138 -0
  244. package/packages/sci/src/vm/memory.js +168 -0
  245. package/packages/sci/src/vm/memory.ts +266 -0
  246. package/packages/sci/src/vm/replace.d.ts +11 -0
  247. package/packages/sci/src/vm/replace.js +14 -0
  248. package/packages/sci/src/vm/replace.ts +16 -0
  249. package/packages/sci/src/vm/savegame.d.ts +89 -0
  250. package/packages/sci/src/vm/savegame.js +161 -0
  251. package/packages/sci/src/vm/savegame.ts +188 -0
  252. package/packages/sci/src/vm/value.d.ts +17 -0
  253. package/packages/sci/src/vm/value.js +12 -0
  254. package/packages/sci/src/vm/value.ts +25 -0
  255. package/packages/sci/src/vm/vm.d.ts +155 -0
  256. package/packages/sci/src/vm/vm.js +1053 -0
  257. package/packages/sci/src/vm/vm.ts +914 -0
  258. package/tools/adlib-render.d.ts +1 -0
  259. package/tools/adlib-render.js +49 -0
  260. package/tools/adlib-render.ts +42 -0
  261. package/tools/analyze.d.ts +1 -0
  262. package/tools/analyze.js +25 -0
  263. package/tools/analyze.ts +25 -0
  264. package/tools/art/build.d.ts +34 -0
  265. package/tools/art/build.js +175 -0
  266. package/tools/art/build.ts +153 -0
  267. package/tools/art.d.ts +1 -0
  268. package/tools/art.js +31 -0
  269. package/tools/art.ts +29 -0
  270. package/tools/asm.d.ts +1 -0
  271. package/tools/asm.js +61 -0
  272. package/tools/asm.ts +55 -0
  273. package/tools/cli.d.ts +1 -0
  274. package/tools/cli.js +99 -0
  275. package/tools/cli.ts +100 -0
  276. package/tools/editor-server.d.ts +29 -0
  277. package/tools/editor-server.js +162 -0
  278. package/tools/editor-server.ts +176 -0
  279. package/tools/explore.d.ts +1 -0
  280. package/tools/explore.js +305 -0
  281. package/tools/explore.ts +250 -0
  282. package/tools/game/audio.d.ts +19 -0
  283. package/tools/game/audio.js +143 -0
  284. package/tools/game/audio.ts +130 -0
  285. package/tools/game/build.d.ts +46 -0
  286. package/tools/game/build.js +259 -0
  287. package/tools/game/build.ts +283 -0
  288. package/tools/game/defaults.d.ts +26 -0
  289. package/tools/game/defaults.js +82 -0
  290. package/tools/game/defaults.ts +83 -0
  291. package/tools/game/font.d.ts +3 -0
  292. package/tools/game/font.js +124 -0
  293. package/tools/game/font.ts +126 -0
  294. package/tools/game/kit.d.ts +7 -0
  295. package/tools/game/kit.js +7 -0
  296. package/tools/game/kit.ts +10 -0
  297. package/tools/game/new.d.ts +8 -0
  298. package/tools/game/new.js +39 -0
  299. package/tools/game/new.ts +38 -0
  300. package/tools/game/target.d.ts +7 -0
  301. package/tools/game/target.js +41 -0
  302. package/tools/game/target.ts +42 -0
  303. package/tools/game/template/resources.ts.tmpl +43 -0
  304. package/tools/game/template/rooms/1.room.yaml +9 -0
  305. package/tools/game/template/rooms/1.yarn +17 -0
  306. package/tools/game/template/scripts/0.sc +24 -0
  307. package/tools/game.d.ts +1 -0
  308. package/tools/game.js +77 -0
  309. package/tools/game.ts +70 -0
  310. package/tools/mod-build.d.ts +28 -0
  311. package/tools/mod-build.js +173 -0
  312. package/tools/mod-build.ts +179 -0
  313. package/tools/mod-target.d.ts +16 -0
  314. package/tools/mod-target.js +26 -0
  315. package/tools/mod-target.ts +32 -0
  316. package/tools/mod.d.ts +1 -0
  317. package/tools/mod.js +47 -0
  318. package/tools/mod.ts +45 -0
  319. package/tools/msg.d.ts +1 -0
  320. package/tools/msg.js +40 -0
  321. package/tools/msg.ts +36 -0
  322. package/tools/node-files.d.ts +3 -0
  323. package/tools/node-files.js +34 -0
  324. package/tools/node-files.ts +33 -0
  325. package/tools/patches.d.ts +3 -0
  326. package/tools/patches.js +13 -0
  327. package/tools/patches.ts +13 -0
  328. package/tools/pic-build.d.ts +9 -0
  329. package/tools/pic-build.js +26 -0
  330. package/tools/pic-build.ts +27 -0
  331. package/tools/pic.d.ts +1 -0
  332. package/tools/pic.js +64 -0
  333. package/tools/pic.ts +61 -0
  334. package/tools/play.d.ts +1 -0
  335. package/tools/play.js +54 -0
  336. package/tools/play.ts +54 -0
  337. package/tools/png.d.ts +16 -0
  338. package/tools/png.js +124 -0
  339. package/tools/png.ts +123 -0
  340. package/tools/res.d.ts +1 -0
  341. package/tools/res.js +48 -0
  342. package/tools/res.ts +50 -0
  343. package/tools/scenario.d.ts +1 -0
  344. package/tools/scenario.js +74 -0
  345. package/tools/scenario.ts +80 -0
  346. package/tools/site/build.d.ts +8 -0
  347. package/tools/site/build.js +110 -0
  348. package/tools/site/build.ts +116 -0
  349. package/tools/site/markdown.d.ts +11 -0
  350. package/tools/site/markdown.js +88 -0
  351. package/tools/site/markdown.ts +84 -0
  352. package/tools/view-build.d.ts +34 -0
  353. package/tools/view-build.js +47 -0
  354. package/tools/view-build.ts +71 -0
  355. package/tools/view.d.ts +1 -0
  356. package/tools/view.js +54 -0
  357. package/tools/view.ts +52 -0
@@ -0,0 +1,164 @@
1
+ import type { ArrayType, SciArray } from "../memory.ts";
2
+ import type { KernelFn, Vm } from "../vm.ts";
3
+ import { NULL, fromInt, isNumber, segmentOf, toSigned, type Value } from "../value.ts";
4
+
5
+ const arrayTypes: ArrayType[] = ["int16", "id", "byte", "string"];
6
+
7
+ /** Resolves an array argument: an array ref, or a String/Array object holding one in `data`. */
8
+ function resolve(vm: Vm, v: Value): SciArray | undefined {
9
+ const direct = vm.memory.array(v);
10
+ if (direct) return direct;
11
+ const obj = vm.memory.object(v);
12
+ if (obj) {
13
+ const data = vm.getProp(obj, "data");
14
+ if (data !== undefined) return vm.memory.array(data);
15
+ }
16
+ return undefined;
17
+ }
18
+
19
+ const store = (a: SciArray, value: Value) => (a.type === "byte" || a.type === "string" ? value & 0xff : value);
20
+
21
+ function setElements(vm: Vm, [ref = 0, index = 0, ...values]: Value[]): Value {
22
+ const a = resolve(vm, ref);
23
+ if (!a) return ref;
24
+ const start = toSigned(index);
25
+ values.forEach((v, i) => {
26
+ a.data[start + i] = store(a, v);
27
+ });
28
+ for (let i = 0; i < a.data.length; i++) a.data[i] ??= 0;
29
+ return ref;
30
+ }
31
+
32
+ /** Byte values of a string argument: literal in a script heap or an array. */
33
+ function chars(vm: Vm, v: Value): number[] {
34
+ const a = resolve(vm, v);
35
+ if (a) {
36
+ const out: number[] = [];
37
+ for (const c of a.data) {
38
+ if ((c & 0xff) === 0) break;
39
+ out.push(c & 0xff);
40
+ }
41
+ return out;
42
+ }
43
+ if (!v) return [];
44
+ return [...vm.memory.string(v)].map((c) => c.charCodeAt(0));
45
+ }
46
+
47
+ const str = (vm: Vm, v: Value) => String.fromCharCode(...chars(vm, v));
48
+
49
+ function newString(vm: Vm, s: string): Value {
50
+ const ref = vm.memory.newArray("string", s.length + 1);
51
+ const a = vm.memory.array(ref)!;
52
+ for (let i = 0; i < s.length; i++) a.data[i] = s.charCodeAt(i) & 0xff;
53
+ return ref;
54
+ }
55
+
56
+ function writeString(vm: Vm, ref: Value, s: string): Value {
57
+ const a = resolve(vm, ref);
58
+ if (!a) return ref;
59
+ a.data.length = 0;
60
+ for (let i = 0; i < s.length; i++) a.data.push(s.charCodeAt(i) & 0xff);
61
+ a.data.push(0);
62
+ return ref;
63
+ }
64
+
65
+ function copy(vm: Vm, [dst = 0, dstIndex = 0, src = 0, srcIndex = 0, count = 0xffff]: Value[]): Value {
66
+ const d = resolve(vm, dst);
67
+ if (!d) return dst;
68
+ const s = resolve(vm, src);
69
+ const from = s ? s.data : chars(vm, src).concat(0);
70
+ const start = toSigned(srcIndex);
71
+ let n = toSigned(count);
72
+ if (n < 0) n = from.length - start;
73
+ for (let i = 0; i < n && start + i < from.length; i++) d.data[toSigned(dstIndex) + i] = store(d, from[start + i]!);
74
+ for (let i = 0; i < d.data.length; i++) d.data[i] ??= 0;
75
+ return dst;
76
+ }
77
+
78
+ /** printf-style formatting, as SCI's StringFormat understands it. */
79
+ export function sciFormat(vm: Vm, format: string, args: Value[]): string {
80
+ let argi = 0;
81
+ return format.replace(/%(-?)(0?)(\d*)([dsuxXc%])/g, (_m, left: string, zero: string, width: string, conv: string) => {
82
+ if (conv === "%") return "%";
83
+ const arg = args[argi++] ?? 0;
84
+ let out: string;
85
+ switch (conv) {
86
+ case "d": out = String(toSigned(arg)); break;
87
+ case "u": out = String(arg & 0xffff); break;
88
+ case "x": out = (arg & 0xffff).toString(16); break;
89
+ case "X": out = (arg & 0xffff).toString(16).toUpperCase(); break;
90
+ case "c": out = String.fromCharCode(arg & 0xff); break;
91
+ default: out = isNumber(arg) && arg !== 0 ? String(toSigned(arg)) : str(vm, arg);
92
+ }
93
+ const w = Number(width || 0);
94
+ return left ? out.padEnd(w) : out.padStart(w, zero ? "0" : " ");
95
+ });
96
+ }
97
+
98
+ const arraySubops: Record<number, KernelFn> = {
99
+ 0: (vm, [size = 0, type = 0]) => vm.memory.newArray(arrayTypes[type] ?? "int16", size),
100
+ 1: (vm, [ref = 0]) => resolve(vm, ref)?.data.length ?? 0,
101
+ 2: (vm, [ref = 0, index = 0]) => resolve(vm, ref)?.data[toSigned(index)] ?? 0,
102
+ 3: setElements,
103
+ 4: (vm, [ref = 0]) => {
104
+ if (vm.memory.array(ref)) vm.memory.free(segmentOf(ref));
105
+ return 0;
106
+ },
107
+ 5: (vm, [ref = 0, index = 0, count = 0, value = 0]) => {
108
+ const a = resolve(vm, ref);
109
+ if (a) for (let i = 0; i < count; i++) a.data[toSigned(index) + i] = store(a, value);
110
+ if (a) for (let i = 0; i < a.data.length; i++) a.data[i] ??= 0;
111
+ return ref;
112
+ },
113
+ 6: copy,
114
+ 8: (vm, [ref = 0]) => {
115
+ const a = resolve(vm, ref);
116
+ if (!a) return NULL;
117
+ const dup = vm.memory.newArray(a.type, 0);
118
+ vm.memory.array(dup)!.data = a.data.slice();
119
+ return dup;
120
+ },
121
+ 9: (vm, [ref = 0]) => {
122
+ const obj = vm.memory.object(ref);
123
+ return obj ? (vm.getProp(obj, "data") ?? ref) : ref;
124
+ },
125
+ };
126
+
127
+ const stringSubops: Record<number, KernelFn> = {
128
+ ...arraySubops,
129
+ 0: (vm, [size = 0]) => vm.memory.newArray("string", size),
130
+ // Duplicate: also takes a literal string from a script's heap (e.g. `Prints "text"`).
131
+ 8: (vm, args) => (resolve(vm, args[0] ?? 0) ? arraySubops[8]!(vm, args) : newString(vm, str(vm, args[0] ?? 0))),
132
+ 7: (vm, [a = 0, b = 0, n]) => {
133
+ let x = str(vm, a), y = str(vm, b);
134
+ if (n !== undefined) (x = x.slice(0, n), (y = y.slice(0, n)));
135
+ return fromInt(x < y ? -1 : x > y ? 1 : 0);
136
+ },
137
+ 10: (vm, [s = 0]) => chars(vm, s).length,
138
+ 11: (vm, [format = 0, ...args]) => newString(vm, sciFormat(vm, str(vm, format), args)),
139
+ 12: (vm, [dst = 0, format = 0, ...args]) => writeString(vm, dst, sciFormat(vm, str(vm, format), args)),
140
+ 13: (vm, [s = 0]) => fromInt(parseInt(str(vm, s), 10) || 0),
141
+ 14: (vm, [s = 0, flags = 0]) => {
142
+ let t = str(vm, s);
143
+ if (flags & 1) t = t.trimStart();
144
+ if (flags & 2) t = t.trimEnd();
145
+ if (flags & 4) t = t.replace(/ /g, "");
146
+ return writeString(vm, s, t);
147
+ },
148
+ 15: (vm, [s = 0]) => writeString(vm, s, str(vm, s).toUpperCase()),
149
+ 16: (vm, [s = 0]) => writeString(vm, s, str(vm, s).toLowerCase()),
150
+ };
151
+
152
+ const dispatch = (name: string, table: Record<number, KernelFn>): KernelFn => (vm, [op = 0, ...args]) => {
153
+ const fn = table[op];
154
+ if (!fn) throw new Error(`${name} subop ${op} not implemented`);
155
+ return fn(vm, args);
156
+ };
157
+
158
+ /** SCI32 dynamic arrays and strings. */
159
+ export const arrayKernels: Record<string, KernelFn> = {
160
+ Array: dispatch("Array", arraySubops),
161
+ String: dispatch("String", stringSubops),
162
+ };
163
+
164
+ export const stringHelpers = { str, newString, writeString, resolve };
@@ -0,0 +1,70 @@
1
+ import { AudioIndex, type AudioClip, type AudioOutput } from "../../audio/index.ts";
2
+ import { SongPlayer, type MidiOutput, type Song } from "../../audio/midi.ts";
3
+ import type { KernelFn, Vm } from "../vm.ts";
4
+ import { type Value } from "../value.ts";
5
+ interface Channel {
6
+ clip: AudioClip;
7
+ /** VM clock (ms) when playback started; undefined while waiting to start. */
8
+ start: number | undefined;
9
+ pausedAt: number | undefined;
10
+ ticks: number;
11
+ loop: boolean;
12
+ volume: number;
13
+ }
14
+ /**
15
+ * Digital audio state. Durations are known synchronously from the index, so the scripts'
16
+ * position polling works off the VM clock; the host's AudioOutput does the actual playback
17
+ * (fetching and decoding clips asynchronously).
18
+ */
19
+ export declare class AudioState {
20
+ private readonly vm;
21
+ readonly index: AudioIndex;
22
+ readonly channels: Map<string, Channel>;
23
+ output: AudioOutput | undefined;
24
+ /** MIDI music goes here (a SoundFont synth or the AdLib driver). */
25
+ midi: MidiOutput | undefined;
26
+ /** Which track of each sound resource to play: General MIDI or AdLib. */
27
+ device: number;
28
+ readonly songs: Map<number, SongPlayer>;
29
+ private readonly songNumbers;
30
+ private readonly songCache;
31
+ masterVolume: number;
32
+ /**
33
+ * Ticks to report when a speech clip doesn't exist (new dialogue in a mod). The game times
34
+ * a spoken line by its clip, so a missing clip would flash the text for a second; hosts
35
+ * that load mods set this (see `readingTime`). Unset, missing clips report 0 like SSCI.
36
+ */
37
+ missingSpeechTicks?: (module: number, noun: number, verb: number, cond: number, seq: number) => number;
38
+ constructor(vm: Vm);
39
+ ticks(): number;
40
+ song(number: number, device?: number): Song | undefined;
41
+ startSong(obj: Value, number: number, loop: boolean, volume: number, position?: number): void;
42
+ playingSongs(): {
43
+ object: Value;
44
+ number: number;
45
+ player: SongPlayer;
46
+ }[];
47
+ /** Silences all music, effects and speech (restart / restore). */
48
+ stopEverything(): void;
49
+ stopSong(obj: Value): void;
50
+ /**
51
+ * Switches music to another device (track) and output, carrying every playing song over
52
+ * at its current position.
53
+ */
54
+ setMusicDevice(device: number, output: MidiOutput | undefined): void;
55
+ play(key: string, clip: AudioClip, loop: boolean, volume: number, autoStart?: boolean): number;
56
+ start(key: string): void;
57
+ stop(key: string): void;
58
+ stopAll(prefix?: string): void;
59
+ /** Elapsed ticks, or undefined once the clip has finished (non-looping). */
60
+ position(key: string): number | undefined;
61
+ pause(key: string, paused: boolean): void;
62
+ }
63
+ export declare const audio: (vm: Vm) => AudioState;
64
+ export declare const audioKernels: Record<string, KernelFn>;
65
+ /**
66
+ * A `missingSpeechTicks` that gives a line without a recording as long as it takes to read:
67
+ * ~15 characters a second, at least two seconds. Reads the text from the message resources.
68
+ */
69
+ export declare function readingTime(vm: Vm): (module: number, noun: number, verb: number, cond: number, seq: number) => number;
70
+ export {};
@@ -0,0 +1,347 @@
1
+ import { AudioIndex, speechKey } from "../../audio/index.js";
2
+ import { SongPlayer, SoundDevice, parseSound } from "../../audio/midi.js";
3
+ import { ResourceType } from "../../resource/types.js";
4
+ import { findMessage, parseMessages } from "../../text/message.js";
5
+ import { toSigned } from "../value.js";
6
+ const NOT_PLAYING = 0xffff;
7
+ const MAX_VOLUME = 127;
8
+ /**
9
+ * Digital audio state. Durations are known synchronously from the index, so the scripts'
10
+ * position polling works off the VM clock; the host's AudioOutput does the actual playback
11
+ * (fetching and decoding clips asynchronously).
12
+ */
13
+ export class AudioState {
14
+ vm;
15
+ index;
16
+ channels = new Map();
17
+ output;
18
+ /** MIDI music goes here (a SoundFont synth or the AdLib driver). */
19
+ midi;
20
+ /** Which track of each sound resource to play: General MIDI or AdLib. */
21
+ device = SoundDevice.GeneralMidi;
22
+ songs = new Map();
23
+ songNumbers = new Map();
24
+ songCache = new Map();
25
+ masterVolume = 15;
26
+ /**
27
+ * Ticks to report when a speech clip doesn't exist (new dialogue in a mod). The game times
28
+ * a spoken line by its clip, so a missing clip would flash the text for a second; hosts
29
+ * that load mods set this (see `readingTime`). Unset, missing clips report 0 like SSCI.
30
+ */
31
+ missingSpeechTicks;
32
+ constructor(vm) {
33
+ this.vm = vm;
34
+ this.index = AudioIndex.build(vm.resources);
35
+ vm.frameHooks.push(() => {
36
+ const now = this.ticks();
37
+ for (const player of this.songs.values())
38
+ player.update(now);
39
+ });
40
+ }
41
+ ticks() {
42
+ return Math.floor((this.vm.clock() * 60) / 1000);
43
+ }
44
+ song(number, device = this.device) {
45
+ const key = `${number}:${device}`;
46
+ if (!this.songCache.has(key)) {
47
+ const id = { type: ResourceType.Sound, number };
48
+ this.songCache.set(key, this.vm.resources.has(id) ? (parseSound(this.vm.resources.loadSync(id).data, device) ?? null) : null);
49
+ }
50
+ return this.songCache.get(key) ?? undefined;
51
+ }
52
+ startSong(obj, number, loop, volume, position = 0) {
53
+ const song = this.song(number);
54
+ if (!song)
55
+ return;
56
+ const player = new SongPlayer(song, this.midi, loop, volume, this.ticks());
57
+ if (position)
58
+ player.seek(position % Math.max(song.length, 1), this.ticks());
59
+ this.songs.set(obj, player);
60
+ this.songNumbers.set(obj, number);
61
+ }
62
+ playingSongs() {
63
+ return [...this.songs].map(([object, player]) => ({ object, number: this.songNumbers.get(object), player }));
64
+ }
65
+ /** Silences all music, effects and speech (restart / restore). */
66
+ stopEverything() {
67
+ for (const obj of [...this.songs.keys()])
68
+ this.stopSong(obj);
69
+ for (const key of [...this.channels.keys()])
70
+ this.stop(key);
71
+ }
72
+ stopSong(obj) {
73
+ this.songs.get(obj)?.stop();
74
+ this.songs.delete(obj);
75
+ this.songNumbers.delete(obj);
76
+ }
77
+ /**
78
+ * Switches music to another device (track) and output, carrying every playing song over
79
+ * at its current position.
80
+ */
81
+ setMusicDevice(device, output) {
82
+ const now = this.ticks();
83
+ const playing = [...this.songs].map(([obj, player]) => ({ obj, player, pos: player.position(now) }));
84
+ for (const { player } of playing)
85
+ player.stop();
86
+ this.device = device;
87
+ this.midi = output;
88
+ output?.setMasterVolume?.(this.masterVolume);
89
+ for (const { obj, player, pos } of playing) {
90
+ const song = this.song(this.songNumbers.get(obj));
91
+ if (!song) {
92
+ this.songs.delete(obj);
93
+ continue;
94
+ }
95
+ const next = new SongPlayer(song, output, player.loop, player.volume, now);
96
+ next.seek(song.length ? pos % Math.max(song.length, 1) : 0, now);
97
+ this.songs.set(obj, next);
98
+ }
99
+ }
100
+ play(key, clip, loop, volume, autoStart = true) {
101
+ this.stop(key);
102
+ const ticks = AudioIndex.ticks(clip);
103
+ this.channels.set(key, { clip, start: undefined, pausedAt: undefined, ticks, loop, volume });
104
+ if (autoStart)
105
+ this.start(key);
106
+ return ticks;
107
+ }
108
+ start(key) {
109
+ const ch = this.channels.get(key);
110
+ if (!ch || ch.start !== undefined)
111
+ return;
112
+ ch.start = this.vm.clock();
113
+ this.output?.play(key, ch.clip, { loop: ch.loop, volume: ch.volume / MAX_VOLUME });
114
+ }
115
+ stop(key) {
116
+ if (!this.channels.delete(key))
117
+ return;
118
+ this.output?.stop(key);
119
+ }
120
+ stopAll(prefix = "") {
121
+ for (const key of [...this.channels.keys()])
122
+ if (key.startsWith(prefix))
123
+ this.stop(key);
124
+ }
125
+ /** Elapsed ticks, or undefined once the clip has finished (non-looping). */
126
+ position(key) {
127
+ const ch = this.channels.get(key);
128
+ if (!ch)
129
+ return undefined;
130
+ if (ch.start === undefined)
131
+ return 0;
132
+ const now = ch.pausedAt ?? this.vm.clock();
133
+ const elapsed = Math.floor(((now - ch.start) * 60) / 1000);
134
+ if (!ch.loop && elapsed >= ch.ticks) {
135
+ this.stop(key);
136
+ return undefined;
137
+ }
138
+ return ch.loop ? elapsed % ch.ticks : elapsed;
139
+ }
140
+ pause(key, paused) {
141
+ const ch = this.channels.get(key);
142
+ if (!ch || ch.start === undefined)
143
+ return;
144
+ if (paused && ch.pausedAt === undefined) {
145
+ ch.pausedAt = this.vm.clock();
146
+ this.output?.pause(key);
147
+ }
148
+ else if (!paused && ch.pausedAt !== undefined) {
149
+ ch.start += this.vm.clock() - ch.pausedAt;
150
+ ch.pausedAt = undefined;
151
+ this.output?.resume(key);
152
+ }
153
+ }
154
+ }
155
+ const stateOf = new WeakMap();
156
+ export const audio = (vm) => {
157
+ let a = stateOf.get(vm);
158
+ if (!a) {
159
+ const created = (a = new AudioState(vm));
160
+ stateOf.set(vm, created);
161
+ vm.hostRoots.push(() => created.songs.keys());
162
+ }
163
+ return a;
164
+ };
165
+ /**
166
+ * DoAudio arguments name a clip either as an audio36 (speech) tuple
167
+ * `module, noun, verb, cond, seq, [loop], [volume]` or as a plain audio number
168
+ * `number, [loop], [volume]`.
169
+ */
170
+ function resolveArgs(a, args) {
171
+ if (args.length >= 5) {
172
+ const [module = 0, noun = 0, verb = 0, cond = 0, seq = 0, loop, volume] = args;
173
+ return {
174
+ key: `aud:${speechKey(module, noun, verb, cond, seq)}`,
175
+ clip: a.index.speech.get(speechKey(module, noun, verb, cond, seq)),
176
+ loop: loop !== undefined && toSigned(loop) === -1,
177
+ volume: volume !== undefined && toSigned(volume) >= 0 ? Math.min(toSigned(volume), MAX_VOLUME) : MAX_VOLUME,
178
+ };
179
+ }
180
+ if (args.length >= 1) {
181
+ const [number = 0, loop, volume] = args;
182
+ return {
183
+ key: `sfx:${number}`,
184
+ clip: a.index.effects.get(number),
185
+ loop: loop !== undefined && toSigned(loop) === -1,
186
+ volume: volume !== undefined && toSigned(volume) >= 0 ? Math.min(toSigned(volume), MAX_VOLUME) : MAX_VOLUME,
187
+ };
188
+ }
189
+ return undefined;
190
+ }
191
+ const soundKey = (obj) => `snd:${obj.toString(16)}`;
192
+ const prop = (vm, obj, name) => toSigned(vm.getProp(obj, name) ?? 0);
193
+ export const audioKernels = {
194
+ DoAudio: (vm, [subop = 0, ...args]) => {
195
+ const a = audio(vm);
196
+ const target = resolveArgs(a, args);
197
+ switch (subop) {
198
+ case 0: return 0; // init
199
+ case 1: // wait-for-play: prepare without starting, return length
200
+ case 2: { // play
201
+ if (!target)
202
+ return a.channels.size;
203
+ if (!target.clip) {
204
+ const [module = 0, noun = 0, verb = 0, cond = 0, seq = 0] = args;
205
+ return args.length >= 5 && a.missingSpeechTicks ? a.missingSpeechTicks(module, noun, verb, cond, seq) & 0xffff : 0;
206
+ }
207
+ return a.play(target.key, target.clip, target.loop, target.volume, subop === 2);
208
+ }
209
+ case 3: // stop
210
+ if (target)
211
+ a.stop(target.key);
212
+ else
213
+ a.stopAll("aud:"), a.stopAll("sfx:");
214
+ return 0;
215
+ case 4:
216
+ case 5: // pause / resume
217
+ for (const key of target ? [target.key] : [...a.channels.keys()])
218
+ a.pause(key, subop === 4);
219
+ return 0;
220
+ case 6: { // position in ticks, -1 when not playing
221
+ const key = target?.key ?? [...a.channels.keys()].find((k) => k.startsWith("aud:"));
222
+ const pos = key === undefined ? undefined : a.position(key);
223
+ return pos === undefined ? NOT_PLAYING : pos & 0xffff;
224
+ }
225
+ case 8: return 0; // volume
226
+ case 9: return 1; // capability: audio present
227
+ case 10: return 16; // bit depth
228
+ case 12: return 1; // mixing
229
+ case 13: return 2; // channels
230
+ default: return 0;
231
+ }
232
+ },
233
+ // Sound objects. Numbers with a digital effect in RESOURCE.SFX play as samples;
234
+ // everything else is MIDI, sequenced here and sent to `midi` (a SoundFont synth).
235
+ DoSound: (vm, [subop = 0, obj = 0, arg = 0, , , stopAfter = 0]) => {
236
+ const a = audio(vm);
237
+ switch (subop) {
238
+ case 0: // master volume (get, or set to arg)
239
+ if (obj !== 0 || arg) {
240
+ a.masterVolume = obj & 0xf;
241
+ a.midi?.setMasterVolume?.(a.masterVolume);
242
+ }
243
+ return a.masterVolume;
244
+ case 3: return 32; // polyphony
245
+ case 4: return 1; // digital audio available
246
+ case 6: // init
247
+ vm.setProp(obj, "nodePtr", obj);
248
+ return 0;
249
+ case 7: // dispose
250
+ a.stop(soundKey(obj));
251
+ a.stopSong(obj);
252
+ vm.setProp(obj, "nodePtr", 0);
253
+ return 0;
254
+ case 8: { // play: a digital effect if RESOURCE.SFX has this number, else MIDI
255
+ const number = prop(vm, obj, "number");
256
+ const loop = prop(vm, obj, "loop") === -1;
257
+ const volume = vm.getProp(obj, "vol") === undefined ? MAX_VOLUME : prop(vm, obj, "vol");
258
+ a.stop(soundKey(obj));
259
+ a.stopSong(obj);
260
+ vm.setProp(obj, "signal", 0);
261
+ // SCI2 Sounds ask for updates (Sound::check -> updateCues, which reports the end and
262
+ // cues the client) only while `handle` is set; playing sets it, stopping clears it.
263
+ vm.setProp(obj, "handle", obj);
264
+ const clip = a.index.effects.get(number);
265
+ if (clip)
266
+ a.play(soundKey(obj), clip, loop, volume);
267
+ else
268
+ a.startSong(obj, number, loop, volume);
269
+ return 0;
270
+ }
271
+ case 9: // stop
272
+ a.stop(soundKey(obj));
273
+ a.stopSong(obj);
274
+ vm.setProp(obj, "signal", NOT_PLAYING);
275
+ vm.setProp(obj, "handle", 0);
276
+ return 0;
277
+ case 10: { // pause (obj or 0 = all, arg = pause?)
278
+ for (const key of obj ? [soundKey(obj)] : [...a.channels.keys()].filter((k) => k.startsWith("snd:")))
279
+ a.pause(key, arg !== 0);
280
+ for (const player of obj ? [a.songs.get(obj)] : [...a.songs.values()]) {
281
+ if (arg !== 0)
282
+ player?.pause(a.ticks());
283
+ else
284
+ player?.resume(a.ticks());
285
+ }
286
+ return 0;
287
+ }
288
+ case 11: { // fade(obj, targetVolume, ticks, step, stopAfter): fading out → stop
289
+ if (arg === 0 || stopAfter) {
290
+ a.stop(soundKey(obj));
291
+ a.stopSong(obj);
292
+ vm.setProp(obj, "signal", NOT_PLAYING);
293
+ }
294
+ else
295
+ a.songs.get(obj)?.setVolume(arg);
296
+ return 0;
297
+ }
298
+ case 14: // set volume
299
+ a.output?.setVolume(soundKey(obj), arg / MAX_VOLUME);
300
+ a.songs.get(obj)?.setVolume(arg);
301
+ vm.setProp(obj, "vol", arg);
302
+ return 0;
303
+ case 16: { // set loop
304
+ vm.setProp(obj, "loop", arg);
305
+ const player = a.songs.get(obj);
306
+ if (player)
307
+ player.loop = toSigned(arg) === -1;
308
+ return 0;
309
+ }
310
+ case 17: { // update cues: report song signals/cues and finished sounds to the script
311
+ if (a.channels.has(soundKey(obj)) && a.position(soundKey(obj)) === undefined)
312
+ vm.setProp(obj, "signal", NOT_PLAYING);
313
+ const player = a.songs.get(obj);
314
+ if (player) {
315
+ player.update(a.ticks());
316
+ if (player.signal) {
317
+ vm.setProp(obj, "signal", player.signal);
318
+ vm.setProp(obj, "dataInc", player.dataInc);
319
+ player.signal = 0;
320
+ }
321
+ if (player.finished)
322
+ a.stopSong(obj);
323
+ }
324
+ return 0;
325
+ }
326
+ default:
327
+ return 0;
328
+ }
329
+ },
330
+ DoSync: () => 0,
331
+ };
332
+ /**
333
+ * A `missingSpeechTicks` that gives a line without a recording as long as it takes to read:
334
+ * ~15 characters a second, at least two seconds. Reads the text from the message resources.
335
+ */
336
+ export function readingTime(vm) {
337
+ const files = new Map();
338
+ return (module, noun, verb, cond, seq) => {
339
+ if (!files.has(module)) {
340
+ const id = { type: ResourceType.Message, number: module };
341
+ files.set(module, vm.resources.has(id) ? parseMessages(vm.resources.loadSync(id).data) : undefined);
342
+ }
343
+ const file = files.get(module);
344
+ const text = file ? findMessage(file, { noun, verb, cond, seq })?.text ?? "" : "";
345
+ return Math.max(120, text.length * 4);
346
+ };
347
+ }