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,45 @@
1
+ import { type VmSnapshot } from "../savegame.ts";
2
+ import type { KernelFn, Vm } from "../vm.ts";
3
+ export interface SaveInfo {
4
+ id: number;
5
+ description: string;
6
+ version: string;
7
+ date: number;
8
+ }
9
+ /** Where saves live. Synchronous because kernels are; hosts preload and write through. */
10
+ export interface SaveStore {
11
+ list(): SaveInfo[];
12
+ get(id: number): {
13
+ info: SaveInfo;
14
+ snapshot: VmSnapshot;
15
+ } | undefined;
16
+ put(info: SaveInfo, snapshot: VmSnapshot): void;
17
+ }
18
+ export declare class MemorySaveStore implements SaveStore {
19
+ readonly saves: Map<number, {
20
+ info: SaveInfo;
21
+ snapshot: VmSnapshot;
22
+ }>;
23
+ /** Called after every write, e.g. to persist asynchronously. */
24
+ onWrite?: (info: SaveInfo, snapshot: VmSnapshot) => void;
25
+ list(): SaveInfo[];
26
+ get(id: number): {
27
+ info: SaveInfo;
28
+ snapshot: VmSnapshot;
29
+ } | undefined;
30
+ put(info: SaveInfo, snap: VmSnapshot): void;
31
+ }
32
+ declare class SaveState {
33
+ store: SaveStore;
34
+ restarting: number;
35
+ }
36
+ export declare const saves: (vm: Vm) => SaveState;
37
+ /**
38
+ * Loads a saved game the way SSCI's RestoreGame does: host state (planes, screen items,
39
+ * palette, sound) is cleared, memory replaced, and the game restarts with `replay` knowing
40
+ * it's a restore. Hosts that load a save themselves must use this too: stale planes and
41
+ * items left from before would alias unrelated objects in the restored memory.
42
+ */
43
+ export declare function restoreGame(vm: Vm, snap: VmSnapshot): void;
44
+ export declare const saveKernels: Record<string, KernelFn>;
45
+ export {};
@@ -0,0 +1,123 @@
1
+ import { restore, snapshot } from "../savegame.js";
2
+ import { toSigned } from "../value.js";
3
+ import { stringHelpers } from "./arrays.js";
4
+ import { audio } from "./audio.js";
5
+ import { graphics } from "./graphics.js";
6
+ import { paletteEffects } from "./palette.js";
7
+ import { input } from "./system.js";
8
+ export class MemorySaveStore {
9
+ saves = new Map();
10
+ /** Called after every write, e.g. to persist asynchronously. */
11
+ onWrite;
12
+ list() {
13
+ return [...this.saves.values()].map((s) => s.info).sort((a, b) => b.date - a.date);
14
+ }
15
+ get(id) {
16
+ return this.saves.get(id);
17
+ }
18
+ put(info, snap) {
19
+ this.saves.set(info.id, { info, snapshot: snap });
20
+ this.onWrite?.(info, snap);
21
+ }
22
+ }
23
+ const Restarting = { None: 0, Restart: 1, Restore: 2 };
24
+ /** SSCI's fixed description slot size in GetSaveFiles' output. */
25
+ const DESCRIPTION_SIZE = 36;
26
+ class SaveState {
27
+ store = new MemorySaveStore();
28
+ restarting = Restarting.None;
29
+ }
30
+ const stateOf = new WeakMap();
31
+ export const saves = (vm) => {
32
+ let s = stateOf.get(vm);
33
+ if (!s)
34
+ stateOf.set(vm, (s = new SaveState()));
35
+ return s;
36
+ };
37
+ /**
38
+ * Loads a saved game the way SSCI's RestoreGame does: host state (planes, screen items,
39
+ * palette, sound) is cleared, memory replaced, and the game restarts with `replay` knowing
40
+ * it's a restore. Hosts that load a save themselves must use this too: stale planes and
41
+ * items left from before would alias unrelated objects in the restored memory.
42
+ */
43
+ export function restoreGame(vm, snap) {
44
+ const game = vm.exportAddress(0, 0);
45
+ resetHostState(vm);
46
+ restore(vm, snap);
47
+ saves(vm).restarting = Restarting.Restore;
48
+ vm.resetExecution();
49
+ vm.start(game, "replay");
50
+ }
51
+ /** Clears everything that isn't part of the VM's memory before a restart or restore. */
52
+ function resetHostState(vm) {
53
+ audio(vm).stopEverything();
54
+ const g = graphics(vm);
55
+ g.planes.clear();
56
+ g.items.clear();
57
+ g.resetPalette();
58
+ paletteEffects(vm).reset();
59
+ input(vm).queue.length = 0;
60
+ }
61
+ export const saveKernels = {
62
+ // SaveGame(gameName, id, description, version)
63
+ SaveGame: (vm, [, id = 0, description = 0, version = 0]) => {
64
+ const info = {
65
+ id,
66
+ description: stringHelpers.str(vm, description),
67
+ version: stringHelpers.str(vm, version),
68
+ date: Date.now(),
69
+ };
70
+ saves(vm).store.put(info, snapshot(vm));
71
+ return 1;
72
+ },
73
+ // RestoreGame(gameName, id, version): on success nothing returns; the game restarts in
74
+ // the saved state via `theGame replay`, as in the original interpreter.
75
+ RestoreGame: (vm, [, id = 0]) => {
76
+ const save = saves(vm).store.get(toSigned(id));
77
+ if (!save)
78
+ return 0;
79
+ restoreGame(vm, save.snapshot);
80
+ return 1;
81
+ },
82
+ RestartGame: (vm) => {
83
+ resetHostState(vm);
84
+ vm.resetMemory();
85
+ saves(vm).restarting = Restarting.Restart;
86
+ vm.start(vm.exportAddress(0, 0), "play");
87
+ return 0;
88
+ },
89
+ // Returns the flag; any argument clears it.
90
+ GameIsRestarting: (vm, args) => {
91
+ const s = saves(vm);
92
+ const flag = s.restarting;
93
+ if (args.length)
94
+ s.restarting = Restarting.None;
95
+ return flag;
96
+ },
97
+ // GetSaveFiles(gameName, descriptions, ids): fixed 36-byte description slots and a
98
+ // 0-terminated id list, newest first.
99
+ GetSaveFiles: (vm, [, descriptions = 0, ids = 0]) => {
100
+ const list = saves(vm).store.list();
101
+ const desc = stringHelpers.resolve(vm, descriptions);
102
+ const idArray = stringHelpers.resolve(vm, ids);
103
+ if (desc) {
104
+ desc.data = new Array(DESCRIPTION_SIZE * list.length + 1).fill(0);
105
+ list.forEach((s, i) => {
106
+ const text = s.description.slice(0, DESCRIPTION_SIZE - 1);
107
+ for (let c = 0; c < text.length; c++)
108
+ desc.data[i * DESCRIPTION_SIZE + c] = text.charCodeAt(c) & 0xff;
109
+ });
110
+ }
111
+ if (idArray)
112
+ idArray.data = [...list.map((s) => s.id & 0xffff), 0];
113
+ return list.length;
114
+ },
115
+ // CheckSaveGame(gameName, id, version): exists and was made by this game version.
116
+ CheckSaveGame: (vm, [, id = 0, version = 0]) => {
117
+ const save = saves(vm).store.get(toSigned(id));
118
+ return save && save.info.version === stringHelpers.str(vm, version) ? 1 : 0;
119
+ },
120
+ GetSaveDir: (vm) => stringHelpers.newString(vm, "SAVES"),
121
+ MakeSaveCatName: (vm, [dst = 0]) => stringHelpers.writeString(vm, dst, "QG4CDSG.CAT"),
122
+ MakeSaveFileName: (vm, [dst = 0, , id = 0]) => stringHelpers.writeString(vm, dst, `QG4CDSG.${String(toSigned(id)).padStart(3, "0")}`),
123
+ };
@@ -0,0 +1,149 @@
1
+ import { restore, snapshot, type VmSnapshot } from "../savegame.ts";
2
+ import type { KernelFn, Vm } from "../vm.ts";
3
+ import { toSigned } from "../value.ts";
4
+ import { stringHelpers } from "./arrays.ts";
5
+ import { audio } from "./audio.ts";
6
+ import { graphics } from "./graphics.ts";
7
+ import { paletteEffects } from "./palette.ts";
8
+ import { input } from "./system.ts";
9
+
10
+ export interface SaveInfo {
11
+ id: number;
12
+ description: string;
13
+ version: string;
14
+ date: number;
15
+ }
16
+
17
+ /** Where saves live. Synchronous because kernels are; hosts preload and write through. */
18
+ export interface SaveStore {
19
+ list(): SaveInfo[];
20
+ get(id: number): { info: SaveInfo; snapshot: VmSnapshot } | undefined;
21
+ put(info: SaveInfo, snapshot: VmSnapshot): void;
22
+ }
23
+
24
+ export class MemorySaveStore implements SaveStore {
25
+ readonly saves = new Map<number, { info: SaveInfo; snapshot: VmSnapshot }>();
26
+ /** Called after every write, e.g. to persist asynchronously. */
27
+ onWrite?: (info: SaveInfo, snapshot: VmSnapshot) => void;
28
+
29
+ list(): SaveInfo[] {
30
+ return [...this.saves.values()].map((s) => s.info).sort((a, b) => b.date - a.date);
31
+ }
32
+
33
+ get(id: number) {
34
+ return this.saves.get(id);
35
+ }
36
+
37
+ put(info: SaveInfo, snap: VmSnapshot): void {
38
+ this.saves.set(info.id, { info, snapshot: snap });
39
+ this.onWrite?.(info, snap);
40
+ }
41
+ }
42
+
43
+ const Restarting = { None: 0, Restart: 1, Restore: 2 } as const;
44
+ /** SSCI's fixed description slot size in GetSaveFiles' output. */
45
+ const DESCRIPTION_SIZE = 36;
46
+
47
+ class SaveState {
48
+ store: SaveStore = new MemorySaveStore();
49
+ restarting: number = Restarting.None;
50
+ }
51
+
52
+ const stateOf = new WeakMap<Vm, SaveState>();
53
+ export const saves = (vm: Vm): SaveState => {
54
+ let s = stateOf.get(vm);
55
+ if (!s) stateOf.set(vm, (s = new SaveState()));
56
+ return s;
57
+ };
58
+
59
+ /**
60
+ * Loads a saved game the way SSCI's RestoreGame does: host state (planes, screen items,
61
+ * palette, sound) is cleared, memory replaced, and the game restarts with `replay` knowing
62
+ * it's a restore. Hosts that load a save themselves must use this too: stale planes and
63
+ * items left from before would alias unrelated objects in the restored memory.
64
+ */
65
+ export function restoreGame(vm: Vm, snap: VmSnapshot): void {
66
+ const game = vm.exportAddress(0, 0);
67
+ resetHostState(vm);
68
+ restore(vm, snap);
69
+ saves(vm).restarting = Restarting.Restore;
70
+ vm.resetExecution();
71
+ vm.start(game, "replay");
72
+ }
73
+
74
+ /** Clears everything that isn't part of the VM's memory before a restart or restore. */
75
+ function resetHostState(vm: Vm) {
76
+ audio(vm).stopEverything();
77
+ const g = graphics(vm);
78
+ g.planes.clear();
79
+ g.items.clear();
80
+ g.resetPalette();
81
+ paletteEffects(vm).reset();
82
+ input(vm).queue.length = 0;
83
+ }
84
+
85
+ export const saveKernels: Record<string, KernelFn> = {
86
+ // SaveGame(gameName, id, description, version)
87
+ SaveGame: (vm, [, id = 0, description = 0, version = 0]) => {
88
+ const info: SaveInfo = {
89
+ id,
90
+ description: stringHelpers.str(vm, description),
91
+ version: stringHelpers.str(vm, version),
92
+ date: Date.now(),
93
+ };
94
+ saves(vm).store.put(info, snapshot(vm));
95
+ return 1;
96
+ },
97
+
98
+ // RestoreGame(gameName, id, version): on success nothing returns; the game restarts in
99
+ // the saved state via `theGame replay`, as in the original interpreter.
100
+ RestoreGame: (vm, [, id = 0]) => {
101
+ const save = saves(vm).store.get(toSigned(id));
102
+ if (!save) return 0;
103
+ restoreGame(vm, save.snapshot);
104
+ return 1;
105
+ },
106
+
107
+ RestartGame: (vm) => {
108
+ resetHostState(vm);
109
+ vm.resetMemory();
110
+ saves(vm).restarting = Restarting.Restart;
111
+ vm.start(vm.exportAddress(0, 0), "play");
112
+ return 0;
113
+ },
114
+
115
+ // Returns the flag; any argument clears it.
116
+ GameIsRestarting: (vm, args) => {
117
+ const s = saves(vm);
118
+ const flag = s.restarting;
119
+ if (args.length) s.restarting = Restarting.None;
120
+ return flag;
121
+ },
122
+
123
+ // GetSaveFiles(gameName, descriptions, ids): fixed 36-byte description slots and a
124
+ // 0-terminated id list, newest first.
125
+ GetSaveFiles: (vm, [, descriptions = 0, ids = 0]) => {
126
+ const list = saves(vm).store.list();
127
+ const desc = stringHelpers.resolve(vm, descriptions);
128
+ const idArray = stringHelpers.resolve(vm, ids);
129
+ if (desc) {
130
+ desc.data = new Array(DESCRIPTION_SIZE * list.length + 1).fill(0);
131
+ list.forEach((s, i) => {
132
+ const text = s.description.slice(0, DESCRIPTION_SIZE - 1);
133
+ for (let c = 0; c < text.length; c++) desc.data[i * DESCRIPTION_SIZE + c] = text.charCodeAt(c) & 0xff;
134
+ });
135
+ }
136
+ if (idArray) idArray.data = [...list.map((s) => s.id & 0xffff), 0];
137
+ return list.length;
138
+ },
139
+
140
+ // CheckSaveGame(gameName, id, version): exists and was made by this game version.
141
+ CheckSaveGame: (vm, [, id = 0, version = 0]) => {
142
+ const save = saves(vm).store.get(toSigned(id));
143
+ return save && save.info.version === stringHelpers.str(vm, version) ? 1 : 0;
144
+ },
145
+
146
+ GetSaveDir: (vm) => stringHelpers.newString(vm, "SAVES"),
147
+ MakeSaveCatName: (vm, [dst = 0]) => stringHelpers.writeString(vm, dst, "QG4CDSG.CAT"),
148
+ MakeSaveFileName: (vm, [dst = 0, , id = 0]) => stringHelpers.writeString(vm, dst, `QG4CDSG.${String(toSigned(id)).padStart(3, "0")}`),
149
+ };
@@ -0,0 +1,62 @@
1
+ import type { KernelFn, Vm } from "../vm.ts";
2
+ import { type Value } from "../value.ts";
3
+ /** SCI key codes for non-character keys: PC BIOS scan codes in the high byte. */
4
+ export declare const SciKey: {
5
+ readonly Home: 18176;
6
+ readonly Up: 18432;
7
+ readonly PageUp: 18688;
8
+ readonly Left: 19200;
9
+ readonly Center: 19456;
10
+ readonly Right: 19712;
11
+ readonly End: 20224;
12
+ readonly Down: 20480;
13
+ readonly PageDown: 20736;
14
+ readonly Insert: 20992;
15
+ readonly Delete: 21248;
16
+ readonly ShiftTab: 3840;
17
+ readonly F1: 15104;
18
+ readonly F2: 15360;
19
+ readonly F3: 15616;
20
+ readonly F4: 15872;
21
+ readonly F5: 16128;
22
+ readonly F6: 16384;
23
+ readonly F7: 16640;
24
+ readonly F8: 16896;
25
+ readonly F9: 17152;
26
+ readonly F10: 17408;
27
+ };
28
+ /** SCI event types (SCI32). */
29
+ export declare const EventType: {
30
+ readonly Null: 0;
31
+ readonly MouseDown: 1;
32
+ readonly MouseUp: 2;
33
+ readonly KeyDown: 4;
34
+ readonly KeyUp: 8;
35
+ readonly Direction: 16;
36
+ readonly Quit: 1024;
37
+ };
38
+ export interface SciEvent {
39
+ type: number;
40
+ message: number;
41
+ modifiers: number;
42
+ x: number;
43
+ y: number;
44
+ }
45
+ /** Input from the host (browser or test harness). */
46
+ export declare class Input {
47
+ readonly queue: SciEvent[];
48
+ x: number;
49
+ y: number;
50
+ modifiers: number;
51
+ push(e: Omit<SciEvent, "x" | "y" | "modifiers"> & Partial<SciEvent>): void;
52
+ }
53
+ export declare const input: (vm: Vm) => Input;
54
+ /** Events, sound (stubbed for now), files and host information. */
55
+ export declare const systemKernels: Record<string, KernelFn>;
56
+ export type { Value };
57
+ /**
58
+ * Files the game may find with FileIO "exists" (lower-case names). A game may load Sierra's
59
+ * debug room (script 18: Alt-T teleport, Alt-G flags, Alt-I items...) when "18.scr" exists, as it
60
+ * did on the developers' machines.
61
+ */
62
+ export declare const hostFiles: (vm: Vm) => Set<string>;
@@ -0,0 +1,110 @@
1
+ import { stringHelpers } from "./arrays.js";
2
+ import { fromInt, toSigned } from "../value.js";
3
+ /** SCI key codes for non-character keys: PC BIOS scan codes in the high byte. */
4
+ export const SciKey = {
5
+ Home: 0x4700, Up: 0x4800, PageUp: 0x4900, Left: 0x4b00, Center: 0x4c00, Right: 0x4d00,
6
+ End: 0x4f00, Down: 0x5000, PageDown: 0x5100, Insert: 0x5200, Delete: 0x5300, ShiftTab: 0x0f00,
7
+ F1: 0x3b00, F2: 0x3c00, F3: 0x3d00, F4: 0x3e00, F5: 0x3f00, F6: 0x4000, F7: 0x4100, F8: 0x4200, F9: 0x4300, F10: 0x4400,
8
+ };
9
+ /** Keypad/arrow keys → SCI directions (1 = up, clockwise to 8 = up-left; 0 = stop). */
10
+ const KEY_TO_DIRECTION = new Map([
11
+ [SciKey.Up, 1], [SciKey.PageUp, 2], [SciKey.Right, 3], [SciKey.PageDown, 4],
12
+ [SciKey.Down, 5], [SciKey.End, 6], [SciKey.Left, 7], [SciKey.Home, 8], [SciKey.Center, 0],
13
+ ]);
14
+ /** SCI event types (SCI32). */
15
+ // Direction = 0x10 in SCI2 (scripts test `type & $10`); SCI0 used 0x40.
16
+ export const EventType = { Null: 0, MouseDown: 1, MouseUp: 2, KeyDown: 4, KeyUp: 8, Direction: 0x10, Quit: 0x400 };
17
+ /** Input from the host (browser or test harness). */
18
+ export class Input {
19
+ queue = [];
20
+ x = 160;
21
+ y = 100;
22
+ modifiers = 0;
23
+ push(e) {
24
+ this.queue.push({ x: this.x, y: this.y, modifiers: this.modifiers, ...e });
25
+ }
26
+ }
27
+ const inputOf = new WeakMap();
28
+ export const input = (vm) => {
29
+ let i = inputOf.get(vm);
30
+ if (!i) {
31
+ const created = new Input();
32
+ inputOf.set(vm, (i = created));
33
+ vm.moveMouse ??= (x, y) => {
34
+ created.x = x;
35
+ created.y = y;
36
+ };
37
+ }
38
+ return i;
39
+ };
40
+ function shiftEvent(vm, event, plane, sign) {
41
+ if (!vm.memory.object(event) || !vm.memory.object(plane))
42
+ return 0;
43
+ const get = (o, n) => toSigned(vm.getProp(o, n) ?? 0);
44
+ vm.setProp(event, "x", fromInt(get(event, "x") + sign * get(plane, "inLeft")));
45
+ vm.setProp(event, "y", fromInt(get(event, "y") + sign * get(plane, "inTop")));
46
+ return 0;
47
+ }
48
+ /** Events, sound (stubbed for now), files and host information. */
49
+ export const systemKernels = {
50
+ GetEvent: (vm, [mask = 0, event = 0]) => {
51
+ const inp = input(vm);
52
+ const index = inp.queue.findIndex((e) => (e.type & mask) !== 0);
53
+ const e = index >= 0 ? inp.queue.splice(index, 1)[0] : { type: EventType.Null, message: 0, modifiers: inp.modifiers, x: inp.x, y: inp.y };
54
+ vm.setProp(event, "type", e.type);
55
+ vm.setProp(event, "message", e.message);
56
+ vm.setProp(event, "modifiers", e.modifiers);
57
+ vm.setProp(event, "x", fromInt(e.x));
58
+ vm.setProp(event, "y", fromInt(e.y));
59
+ return e.type === EventType.Null ? 0 : 1;
60
+ },
61
+ // Convert an event's x/y between screen and plane-local coordinates.
62
+ GlobalToLocal: (vm, [event = 0, plane = 0]) => shiftEvent(vm, event, plane, -1),
63
+ LocalToGlobal: (vm, [event = 0, plane = 0]) => shiftEvent(vm, event, plane, 1),
64
+ // Turns an arrow/keypad key event into a direction event; 0 if it isn't one.
65
+ MapKeyToDir: (vm, [event = 0]) => {
66
+ if (vm.getProp(event, "type") !== EventType.KeyDown)
67
+ return undefined; // leave acc
68
+ const dir = KEY_TO_DIRECTION.get(vm.getProp(event, "message") ?? 0);
69
+ if (dir === undefined)
70
+ return 0;
71
+ vm.setProp(event, "type", EventType.Direction);
72
+ vm.setProp(event, "message", dir);
73
+ return 1;
74
+ },
75
+ VibrateMouse: () => 0,
76
+ SetQuitStr: () => 0,
77
+ GetCWD: (vm) => stringHelpers.newString(vm, "C:\\GAME"),
78
+ ValidPath: () => 1,
79
+ CheckFreeSpace: () => 1,
80
+ CheckCDisc: () => 1,
81
+ GetSaveCDisc: () => 1,
82
+ DeviceInfo: () => 0,
83
+ MemoryInfo: () => 0x7fff,
84
+ ResourceTrack: () => 0,
85
+ PreloadResource: () => 0,
86
+ Dummy: () => 0,
87
+ // No filesystem yet: opens fail (-1), which the scripts handle as "no config/saves".
88
+ FileIO: (vm, [op = 0, name = 0]) => {
89
+ switch (op) {
90
+ case 0: return 0xffff; // open
91
+ case 10: { // exists: only what the host says is there
92
+ const file = stringHelpers.str(vm, name).split(/[\\/]/).pop().toLowerCase();
93
+ return hostFiles(vm).has(file) ? 1 : 0;
94
+ }
95
+ default: return 0;
96
+ }
97
+ },
98
+ };
99
+ const hostFileSets = new WeakMap();
100
+ /**
101
+ * Files the game may find with FileIO "exists" (lower-case names). A game may load Sierra's
102
+ * debug room (script 18: Alt-T teleport, Alt-G flags, Alt-I items...) when "18.scr" exists, as it
103
+ * did on the developers' machines.
104
+ */
105
+ export const hostFiles = (vm) => {
106
+ let set = hostFileSets.get(vm);
107
+ if (!set)
108
+ hostFileSets.set(vm, (set = new Set()));
109
+ return set;
110
+ };
@@ -0,0 +1,128 @@
1
+ import type { KernelFn, Vm } from "../vm.ts";
2
+ import { stringHelpers } from "./arrays.ts";
3
+ import { fromInt, toSigned, type Value } from "../value.ts";
4
+
5
+ /** SCI key codes for non-character keys: PC BIOS scan codes in the high byte. */
6
+ export const SciKey = {
7
+ Home: 0x4700, Up: 0x4800, PageUp: 0x4900, Left: 0x4b00, Center: 0x4c00, Right: 0x4d00,
8
+ End: 0x4f00, Down: 0x5000, PageDown: 0x5100, Insert: 0x5200, Delete: 0x5300, ShiftTab: 0x0f00,
9
+ F1: 0x3b00, F2: 0x3c00, F3: 0x3d00, F4: 0x3e00, F5: 0x3f00, F6: 0x4000, F7: 0x4100, F8: 0x4200, F9: 0x4300, F10: 0x4400,
10
+ } as const;
11
+
12
+ /** Keypad/arrow keys → SCI directions (1 = up, clockwise to 8 = up-left; 0 = stop). */
13
+ const KEY_TO_DIRECTION = new Map<number, number>([
14
+ [SciKey.Up, 1], [SciKey.PageUp, 2], [SciKey.Right, 3], [SciKey.PageDown, 4],
15
+ [SciKey.Down, 5], [SciKey.End, 6], [SciKey.Left, 7], [SciKey.Home, 8], [SciKey.Center, 0],
16
+ ]);
17
+
18
+ /** SCI event types (SCI32). */
19
+ // Direction = 0x10 in SCI2 (scripts test `type & $10`); SCI0 used 0x40.
20
+ export const EventType = { Null: 0, MouseDown: 1, MouseUp: 2, KeyDown: 4, KeyUp: 8, Direction: 0x10, Quit: 0x400 } as const;
21
+
22
+ export interface SciEvent {
23
+ type: number;
24
+ message: number;
25
+ modifiers: number;
26
+ x: number;
27
+ y: number;
28
+ }
29
+
30
+ /** Input from the host (browser or test harness). */
31
+ export class Input {
32
+ readonly queue: SciEvent[] = [];
33
+ x = 160;
34
+ y = 100;
35
+ modifiers = 0;
36
+
37
+ push(e: Omit<SciEvent, "x" | "y" | "modifiers"> & Partial<SciEvent>): void {
38
+ this.queue.push({ x: this.x, y: this.y, modifiers: this.modifiers, ...e });
39
+ }
40
+ }
41
+
42
+ const inputOf = new WeakMap<Vm, Input>();
43
+ export const input = (vm: Vm): Input => {
44
+ let i = inputOf.get(vm);
45
+ if (!i) {
46
+ const created = new Input();
47
+ inputOf.set(vm, (i = created));
48
+ vm.moveMouse ??= (x, y) => {
49
+ created.x = x;
50
+ created.y = y;
51
+ };
52
+ }
53
+ return i;
54
+ };
55
+
56
+ function shiftEvent(vm: Vm, event: Value, plane: Value, sign: number): Value {
57
+ if (!vm.memory.object(event) || !vm.memory.object(plane)) return 0;
58
+ const get = (o: Value, n: string) => toSigned(vm.getProp(o, n) ?? 0);
59
+ vm.setProp(event, "x", fromInt(get(event, "x") + sign * get(plane, "inLeft")));
60
+ vm.setProp(event, "y", fromInt(get(event, "y") + sign * get(plane, "inTop")));
61
+ return 0;
62
+ }
63
+
64
+ /** Events, sound (stubbed for now), files and host information. */
65
+ export const systemKernels: Record<string, KernelFn> = {
66
+ GetEvent: (vm, [mask = 0, event = 0]) => {
67
+ const inp = input(vm);
68
+ const index = inp.queue.findIndex((e) => (e.type & mask) !== 0);
69
+ const e = index >= 0 ? inp.queue.splice(index, 1)[0]! : { type: EventType.Null, message: 0, modifiers: inp.modifiers, x: inp.x, y: inp.y };
70
+ vm.setProp(event, "type", e.type);
71
+ vm.setProp(event, "message", e.message);
72
+ vm.setProp(event, "modifiers", e.modifiers);
73
+ vm.setProp(event, "x", fromInt(e.x));
74
+ vm.setProp(event, "y", fromInt(e.y));
75
+ return e.type === EventType.Null ? 0 : 1;
76
+ },
77
+ // Convert an event's x/y between screen and plane-local coordinates.
78
+ GlobalToLocal: (vm, [event = 0, plane = 0]) => shiftEvent(vm, event, plane, -1),
79
+ LocalToGlobal: (vm, [event = 0, plane = 0]) => shiftEvent(vm, event, plane, 1),
80
+ // Turns an arrow/keypad key event into a direction event; 0 if it isn't one.
81
+ MapKeyToDir: (vm, [event = 0]) => {
82
+ if (vm.getProp(event, "type") !== EventType.KeyDown) return undefined; // leave acc
83
+ const dir = KEY_TO_DIRECTION.get(vm.getProp(event, "message") ?? 0);
84
+ if (dir === undefined) return 0;
85
+ vm.setProp(event, "type", EventType.Direction);
86
+ vm.setProp(event, "message", dir);
87
+ return 1;
88
+ },
89
+ VibrateMouse: () => 0,
90
+
91
+ SetQuitStr: () => 0,
92
+ GetCWD: (vm) => stringHelpers.newString(vm, "C:\\GAME"),
93
+ ValidPath: () => 1,
94
+ CheckFreeSpace: () => 1,
95
+ CheckCDisc: () => 1,
96
+ GetSaveCDisc: () => 1,
97
+ DeviceInfo: () => 0,
98
+ MemoryInfo: () => 0x7fff,
99
+ ResourceTrack: () => 0,
100
+ PreloadResource: () => 0,
101
+ Dummy: () => 0,
102
+
103
+ // No filesystem yet: opens fail (-1), which the scripts handle as "no config/saves".
104
+ FileIO: (vm, [op = 0, name = 0]) => {
105
+ switch (op) {
106
+ case 0: return 0xffff; // open
107
+ case 10: { // exists: only what the host says is there
108
+ const file = stringHelpers.str(vm, name).split(/[\\/]/).pop()!.toLowerCase();
109
+ return hostFiles(vm).has(file) ? 1 : 0;
110
+ }
111
+ default: return 0;
112
+ }
113
+ },
114
+ };
115
+
116
+ export type { Value };
117
+
118
+ const hostFileSets = new WeakMap<Vm, Set<string>>();
119
+ /**
120
+ * Files the game may find with FileIO "exists" (lower-case names). A game may load Sierra's
121
+ * debug room (script 18: Alt-T teleport, Alt-G flags, Alt-I items...) when "18.scr" exists, as it
122
+ * did on the developers' machines.
123
+ */
124
+ export const hostFiles = (vm: Vm): Set<string> => {
125
+ let set = hostFileSets.get(vm);
126
+ if (!set) hostFileSets.set(vm, (set = new Set()));
127
+ return set;
128
+ };
@@ -0,0 +1,13 @@
1
+ import type { KernelFn, Vm } from "../vm.ts";
2
+ /** For replaceResources: forget parsed message files. */
3
+ export declare const forgetMessages: (vm: Vm) => void;
4
+ /**
5
+ * Sierra text escapes: `\xx` (two hex digits) is a character code, e.g. `\1d`/`\1e` are the
6
+ * arrow glyphs on dialog scroll buttons.
7
+ */
8
+ export declare const unescapeText: (s: string) => string;
9
+ /**
10
+ * SCI32 text: scripts measure text (TextSize), render it into an off-screen bitmap
11
+ * (CreateTextBitmap) and show that bitmap as a screen item via the object's `bitmap`.
12
+ */
13
+ export declare const textKernels: Record<string, KernelFn>;