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,233 @@
1
+ import type { Cel } from "./cel.ts";
2
+ import { decodeCel } from "./cel.ts";
3
+ import { parseHunkPaletteFile, writeHunkPalette, type HunkPaletteFile } from "./pic-file.ts";
4
+
5
+ /**
6
+ * A view resource with everything needed to write it back byte for byte. Measured on all
7
+ * 500 views of an SCI2 game (every one has this layout; all 9,628 cels are RLE-compressed):
8
+ *
9
+ * header (18): +0 u16 16 +2 u8 loop count +3 u8 0 +4 u8 `flags` (1) +5 u8 0
10
+ * +6 u8 total cels +7 u8 0 +8 u32 palette offset +12 u8 16 +13 u8 36
11
+ * loop headers (16): +0 i8 linked loop (-1 = own cels) +1 u8 mirror +2 u8 cel count
12
+ * +3 ff ff ff ff 03 00 00 00 00 +12 u32 first cel header
13
+ * (a linked loop's points at where the next cel headers begin)
14
+ * cel headers (36), loop by loop: +0 u16 w +2 u16 h +4 i16 dx +6 i16 dy +8 u8 skip
15
+ * +9 u8 0x8a +12 u32 control+literal bytes +16 u32 control bytes
16
+ * +24 control offset +28 literal offset +32 row table offset
17
+ * 00 03 u32 size | hunk palette
18
+ * 00 04 u32 size | every cel's control stream, then every cel's literal stream
19
+ * 00 05 u32 size | every cel's row table (u32 control offsets, then u32 literal offsets)
20
+ */
21
+ export interface ViewFile {
22
+ flags: number;
23
+ loops: ViewLoop[];
24
+ palette: HunkPaletteFile | undefined;
25
+ /** The two chunk-size words (498 views: control+literal bytes, 0; 2 views: control, literal). */
26
+ chunkSizes?: [number, number];
27
+ }
28
+
29
+ export interface ViewLoop {
30
+ /** -1: this loop has its own cels; otherwise it reuses loop `link`'s (mirrored if `mirror`). */
31
+ link: number;
32
+ mirror: boolean;
33
+ cels: ViewCel[];
34
+ }
35
+
36
+ export interface ViewCel extends Cel {
37
+ /**
38
+ * The cel's compressed streams as read, reused while `pixels` still equals `original`.
39
+ * Sierra's encoder follows heuristics we don't reproduce exactly,
40
+ * so keeping them is what makes untouched views round-trip byte for byte.
41
+ */
42
+ encoded?: RleCel & { original: Uint8Array };
43
+ }
44
+
45
+ export interface RleCel {
46
+ control: number[] | Uint8Array;
47
+ literal: number[] | Uint8Array;
48
+ rowControl: number[];
49
+ rowLiteral: number[];
50
+ }
51
+
52
+ const HEADER = 18, LOOP = 16, CEL = 36;
53
+ const LOOP_FILLER = [0xff, 0xff, 0xff, 0xff, 0x03, 0, 0, 0, 0];
54
+
55
+ export function parseViewFile(data: Uint8Array): ViewFile {
56
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
57
+ const loopCount = data[2]!;
58
+ const palette = v.getUint32(8, true);
59
+ const loops: ViewLoop[] = [];
60
+ for (let i = 0; i < loopCount; i++) {
61
+ const lh = HEADER + LOOP * i;
62
+ const link = v.getInt8(lh);
63
+ const first = v.getUint32(lh + 12, true);
64
+ loops.push({
65
+ link,
66
+ mirror: data[lh + 1] === 1,
67
+ cels: link === -1 ? Array.from({ length: data[lh + 2]! }, (_, c) => readCel(data, first + CEL * c)) : [],
68
+ });
69
+ }
70
+ let chunkSizes: [number, number] | undefined;
71
+ const firstCel = loops.findIndex((l) => l.link === -1 && l.cels.length);
72
+ if (firstCel >= 0) {
73
+ const h = v.getUint32(HEADER + LOOP * firstCel + 12, true);
74
+ const ctrl = v.getUint32(h + 24, true), rows = v.getUint32(h + 32, true);
75
+ const litEnd = rows - 6;
76
+ chunkSizes = [v.getUint32(ctrl - 4, true), v.getUint32(litEnd + 2, true)];
77
+ }
78
+ return { flags: data[4]!, loops, palette: palette ? parseHunkPaletteFile(data, palette) : undefined, chunkSizes };
79
+ }
80
+
81
+ function readCel(data: Uint8Array, h: number): ViewCel {
82
+ const cel: ViewCel = decodeCel(data, h);
83
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
84
+ const ctrlSize = v.getUint32(h + 16, true), total = v.getUint32(h + 12, true);
85
+ const ctrl = v.getUint32(h + 24, true), lit = v.getUint32(h + 28, true), rows = v.getUint32(h + 32, true);
86
+ cel.encoded = {
87
+ control: data.slice(ctrl, ctrl + ctrlSize),
88
+ literal: data.slice(lit, lit + total - ctrlSize),
89
+ rowControl: Array.from({ length: cel.height }, (_, y) => v.getUint32(rows + y * 4, true)),
90
+ rowLiteral: Array.from({ length: cel.height }, (_, y) => v.getUint32(rows + (cel.height + y) * 4, true)),
91
+ original: cel.pixels.slice(),
92
+ };
93
+ return cel;
94
+ }
95
+
96
+ /**
97
+ * SCI32 cel RLE, one row at a time: transparent pixels as transparent runs (11nnnnnn), equal
98
+ * pixels as colour runs (10nnnnnn + the colour in the literal stream), the rest as literals
99
+ * (0nnnnnnn + that many bytes); runs hold up to 63 pixels, literal groups up to 127.
100
+ *
101
+ * Each row is split to use the fewest bytes: a transparent run costs 1, a colour run 2, a
102
+ * literal group 1 + its length (and may include a lone transparent pixel when that's
103
+ * cheaper, as Sierra's encoder also does). The result is never larger than Sierra's.
104
+ */
105
+ export function encodeCelRle(cel: Cel): RleCel & { control: number[]; literal: number[] } {
106
+ const control: number[] = [], literal: number[] = [], rowControl: number[] = [], rowLiteral: number[] = [];
107
+ const { width: w, height: h, pixels, skipColor } = cel;
108
+ for (let y = 0; y < h; y++) {
109
+ rowControl.push(control.length);
110
+ rowLiteral.push(literal.length);
111
+ const row = pixels.subarray(y * w, (y + 1) * w);
112
+ let x = 0;
113
+ for (const [kind, n] of cheapestSplit(row, skipColor)) {
114
+ if (kind === "skip") control.push(0xc0 | n);
115
+ else if (kind === "run") {
116
+ control.push(0x80 | n);
117
+ literal.push(row[x]!);
118
+ } else {
119
+ control.push(n);
120
+ for (let k = 0; k < n; k++) literal.push(row[x + k]!);
121
+ }
122
+ x += n;
123
+ }
124
+ }
125
+ return { control, literal, rowControl, rowLiteral };
126
+ }
127
+
128
+ type Token = ["skip" | "run" | "lit", number];
129
+
130
+ /** Fewest bytes for a row: dynamic programming from the end. */
131
+ function cheapestSplit(row: Uint8Array, skipColor: number): Token[] {
132
+ const n = row.length;
133
+ const cost = new Array<number>(n + 1).fill(Infinity);
134
+ const pick: Token[] = new Array(n);
135
+ cost[n] = 0;
136
+ for (let i = n - 1; i >= 0; i--) {
137
+ let same = 1;
138
+ while (i + same < n && row[i + same] === row[i] && same < 63) same++;
139
+ const transparent = row[i] === skipColor;
140
+ for (let k = same; k >= (transparent ? 1 : 2); k--) {
141
+ const c = (transparent ? 1 : 2) + cost[i + k]!;
142
+ if (c < cost[i]!) (cost[i] = c), (pick[i] = [transparent ? "skip" : "run", k]);
143
+ }
144
+ for (let k = 1; k <= Math.min(127, n - i); k++) {
145
+ const c = 1 + k + cost[i + k]!;
146
+ if (c < cost[i]!) (cost[i] = c), (pick[i] = ["lit", k]);
147
+ }
148
+ }
149
+ const out: Token[] = [];
150
+ for (let i = 0; i < n; i += pick[i]![1]) out.push(pick[i]!);
151
+ return out;
152
+ }
153
+
154
+ /** The cel's streams: as read if its pixels are untouched, else freshly encoded. */
155
+ function streamsOf(cel: ViewCel): RleCel {
156
+ const e = cel.encoded;
157
+ if (e && e.original.length === cel.pixels.length && e.original.every((p, i) => p === cel.pixels[i])) return e;
158
+ return encodeCelRle(cel);
159
+ }
160
+
161
+ export function writeView(view: ViewFile): Uint8Array {
162
+ const own = view.loops.filter((l) => l.link === -1);
163
+ const cels = own.flatMap((l) => l.cels);
164
+ const encoded = cels.map(streamsOf);
165
+ const palette = view.palette ? writeHunkPalette(view.palette) : undefined;
166
+ const headersEnd = HEADER + LOOP * view.loops.length + CEL * cels.length;
167
+ const paletteAt = palette ? headersEnd + 6 : 0;
168
+ const ctrlChunk = (palette ? paletteAt + palette.length : headersEnd) + 6;
169
+ const ctrlSize = encoded.reduce((n, e) => n + e.control.length, 0);
170
+ const litSize = encoded.reduce((n, e) => n + e.literal.length, 0);
171
+ const litAt = ctrlChunk + ctrlSize;
172
+ const rowsAt = litAt + litSize + 6;
173
+ const rowsSize = cels.reduce((n, c) => n + c.height * 8, 0);
174
+ const out = new Uint8Array(rowsAt + rowsSize);
175
+ const v = new DataView(out.buffer);
176
+
177
+ v.setUint16(0, HEADER - 2, true);
178
+ out[2] = view.loops.length;
179
+ out[4] = view.flags;
180
+ out[6] = cels.length;
181
+ v.setUint32(8, paletteAt, true);
182
+ out[12] = LOOP;
183
+ out[13] = CEL;
184
+
185
+ // Loop headers; cel headers follow in loop order.
186
+ let celHeader = HEADER + LOOP * view.loops.length;
187
+ let celIndex = 0;
188
+ let ctrl = ctrlChunk, lit = litAt, rows = rowsAt;
189
+ view.loops.forEach((loop, i) => {
190
+ const lh = HEADER + LOOP * i;
191
+ v.setInt8(lh, loop.link);
192
+ out[lh + 1] = loop.mirror ? 1 : 0;
193
+ out[lh + 2] = loop.link === -1 ? loop.cels.length : 0;
194
+ out.set(LOOP_FILLER, lh + 3);
195
+ v.setUint32(lh + 12, celHeader, true);
196
+ if (loop.link !== -1) return;
197
+ for (const cel of loop.cels) {
198
+ const e = encoded[celIndex++]!;
199
+ const h = celHeader;
200
+ v.setUint16(h, cel.width, true);
201
+ v.setUint16(h + 2, cel.height, true);
202
+ v.setInt16(h + 4, cel.displaceX, true);
203
+ v.setInt16(h + 6, cel.displaceY, true);
204
+ out[h + 8] = cel.skipColor;
205
+ out[h + 9] = 0x8a;
206
+ v.setUint32(h + 12, e.control.length + e.literal.length, true);
207
+ v.setUint32(h + 16, e.control.length, true);
208
+ v.setUint32(h + 24, ctrl, true);
209
+ v.setUint32(h + 28, lit, true);
210
+ v.setUint32(h + 32, rows, true);
211
+ out.set(e.control, ctrl);
212
+ out.set(e.literal, lit);
213
+ e.rowControl.forEach((o, y) => v.setUint32(rows + y * 4, o, true));
214
+ e.rowLiteral.forEach((o, y) => v.setUint32(rows + (cel.height + y) * 4, o, true));
215
+ ctrl += e.control.length;
216
+ lit += e.literal.length;
217
+ rows += cel.height * 8;
218
+ celHeader += CEL;
219
+ }
220
+ });
221
+
222
+ const [size4, size5] = view.chunkSizes ?? [ctrlSize + litSize, 0];
223
+ if (palette) {
224
+ out.set([0, 3], headersEnd);
225
+ v.setUint32(headersEnd + 2, palette.length, true);
226
+ out.set(palette, paletteAt);
227
+ }
228
+ out.set([0, 4], ctrlChunk - 6);
229
+ v.setUint32(ctrlChunk - 4, size4, true);
230
+ out.set([0, 5], rowsAt - 6);
231
+ v.setUint32(rowsAt - 4, size5, true);
232
+ return out;
233
+ }
@@ -0,0 +1,28 @@
1
+ import { type Cel } from "./cel.ts";
2
+ import { type Palette } from "./palette.ts";
3
+ export interface Loop {
4
+ /** Loop drawn as a horizontal mirror of another (e.g. walking left = walking right, flipped). */
5
+ mirror: boolean;
6
+ cels: Cel[];
7
+ }
8
+ /** A view: sprite animations grouped into loops (usually one per facing direction). */
9
+ export interface View {
10
+ loops: Loop[];
11
+ palette: Palette | undefined;
12
+ }
13
+ /**
14
+ * SCI32 view layout:
15
+ *
16
+ * +0 u16 header size (loop headers start at 2 + this)
17
+ * +2 u8 loop count
18
+ * +8 u32 embedded palette offset (0 = none)
19
+ * +12 u8 loop header size
20
+ * +13 u8 cel header size
21
+ *
22
+ * Loop header:
23
+ * +0 i8 linked loop (-1 = own cels; otherwise reuse that loop's cels)
24
+ * +1 u8 1 = mirror the linked loop
25
+ * +2 u8 cel count
26
+ * +12 u32 offset of the first cel header
27
+ */
28
+ export declare function parseView(data: Uint8Array): View;
@@ -0,0 +1,45 @@
1
+ import { decodeCel } from "./cel.js";
2
+ import { parseHunkPalette } from "./palette.js";
3
+ /**
4
+ * SCI32 view layout:
5
+ *
6
+ * +0 u16 header size (loop headers start at 2 + this)
7
+ * +2 u8 loop count
8
+ * +8 u32 embedded palette offset (0 = none)
9
+ * +12 u8 loop header size
10
+ * +13 u8 cel header size
11
+ *
12
+ * Loop header:
13
+ * +0 i8 linked loop (-1 = own cels; otherwise reuse that loop's cels)
14
+ * +1 u8 1 = mirror the linked loop
15
+ * +2 u8 cel count
16
+ * +12 u32 offset of the first cel header
17
+ */
18
+ export function parseView(data) {
19
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
20
+ const headerSize = v.getUint16(0, true);
21
+ const loopCount = data[2];
22
+ const paletteOffset = v.getUint32(8, true);
23
+ const loopHeaderSize = data[12];
24
+ const celHeaderSize = data[13];
25
+ const loopHeaderAt = (i) => 2 + headerSize + loopHeaderSize * i;
26
+ const decoded = new Map();
27
+ const celsOf = (loopNo) => {
28
+ let cels = decoded.get(loopNo);
29
+ if (!cels) {
30
+ const lh = loopHeaderAt(loopNo);
31
+ const celCount = data[lh + 2];
32
+ const first = v.getUint32(lh + 12, true);
33
+ cels = Array.from({ length: celCount }, (_, c) => decodeCel(data, first + celHeaderSize * c));
34
+ decoded.set(loopNo, cels);
35
+ }
36
+ return cels;
37
+ };
38
+ const loops = [];
39
+ for (let i = 0; i < loopCount; i++) {
40
+ const lh = loopHeaderAt(i);
41
+ const link = v.getInt8(lh);
42
+ loops.push(link === -1 ? { mirror: false, cels: celsOf(i) } : { mirror: data[lh + 1] === 1, cels: celsOf(link) });
43
+ }
44
+ return { loops, palette: paletteOffset ? parseHunkPalette(data, paletteOffset) : undefined };
45
+ }
@@ -0,0 +1,62 @@
1
+ import { decodeCel, type Cel } from "./cel.ts";
2
+ import { parseHunkPalette, type Palette } from "./palette.ts";
3
+
4
+ export interface Loop {
5
+ /** Loop drawn as a horizontal mirror of another (e.g. walking left = walking right, flipped). */
6
+ mirror: boolean;
7
+ cels: Cel[];
8
+ }
9
+
10
+ /** A view: sprite animations grouped into loops (usually one per facing direction). */
11
+ export interface View {
12
+ loops: Loop[];
13
+ palette: Palette | undefined;
14
+ }
15
+
16
+ /**
17
+ * SCI32 view layout:
18
+ *
19
+ * +0 u16 header size (loop headers start at 2 + this)
20
+ * +2 u8 loop count
21
+ * +8 u32 embedded palette offset (0 = none)
22
+ * +12 u8 loop header size
23
+ * +13 u8 cel header size
24
+ *
25
+ * Loop header:
26
+ * +0 i8 linked loop (-1 = own cels; otherwise reuse that loop's cels)
27
+ * +1 u8 1 = mirror the linked loop
28
+ * +2 u8 cel count
29
+ * +12 u32 offset of the first cel header
30
+ */
31
+ export function parseView(data: Uint8Array): View {
32
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
33
+ const headerSize = v.getUint16(0, true);
34
+ const loopCount = data[2]!;
35
+ const paletteOffset = v.getUint32(8, true);
36
+ const loopHeaderSize = data[12]!;
37
+ const celHeaderSize = data[13]!;
38
+
39
+ const loopHeaderAt = (i: number) => 2 + headerSize + loopHeaderSize * i;
40
+ const decoded = new Map<number, Cel[]>();
41
+
42
+ const celsOf = (loopNo: number): Cel[] => {
43
+ let cels = decoded.get(loopNo);
44
+ if (!cels) {
45
+ const lh = loopHeaderAt(loopNo);
46
+ const celCount = data[lh + 2]!;
47
+ const first = v.getUint32(lh + 12, true);
48
+ cels = Array.from({ length: celCount }, (_, c) => decodeCel(data, first + celHeaderSize * c));
49
+ decoded.set(loopNo, cels);
50
+ }
51
+ return cels;
52
+ };
53
+
54
+ const loops: Loop[] = [];
55
+ for (let i = 0; i < loopCount; i++) {
56
+ const lh = loopHeaderAt(i);
57
+ const link = v.getInt8(lh);
58
+ loops.push(link === -1 ? { mirror: false, cels: celsOf(i) } : { mirror: data[lh + 1] === 1, cels: celsOf(link) });
59
+ }
60
+
61
+ return { loops, palette: paletteOffset ? parseHunkPalette(data, paletteOffset) : undefined };
62
+ }
@@ -0,0 +1,38 @@
1
+ export * from "./resource/types.ts";
2
+ export * from "./resource/map.ts";
3
+ export * from "./resource/lzs.ts";
4
+ export * from "./resource/manager.ts";
5
+ export * from "./resource/archive.ts";
6
+ export * from "./gfx/palette.ts";
7
+ export * from "./gfx/cel.ts";
8
+ export * from "./gfx/view.ts";
9
+ export * from "./gfx/pic.ts";
10
+ export * from "./gfx/pic-file.ts";
11
+ export * from "./gfx/quantize.ts";
12
+ export * from "./gfx/view-file.ts";
13
+ export * from "./text/font.ts";
14
+ export * from "./text/message.ts";
15
+ export * from "./script/kernel-names.ts";
16
+ export * from "./script/vocab.ts";
17
+ export * from "./script/script.ts";
18
+ export * from "./script/opcodes.ts";
19
+ export * from "./script/disasm.ts";
20
+ export * from "./script/world.ts";
21
+ export * from "./vm/value.ts";
22
+ export * from "./vm/memory.ts";
23
+ export * from "./vm/vm.ts";
24
+ export * from "./vm/kernels/index.ts";
25
+ export * from "./audio/sol.ts";
26
+ export * from "./audio/index.ts";
27
+ export * from "./audio/midi.ts";
28
+ export * from "./audio/opl2.ts";
29
+ export * from "./audio/adlib.ts";
30
+ export * from "./audio/write.ts";
31
+ export * from "./motion/pathfinding.ts";
32
+ export * from "./vm/savegame.ts";
33
+ export * from "./analysis/game.ts";
34
+ export * from "./analysis/runtime.ts";
35
+ export * from "./script/assembly.ts";
36
+ export * from "./script/sexpr.ts";
37
+ export * from "./script/compiler.ts";
38
+ export * from "./vm/replace.ts";
@@ -0,0 +1,38 @@
1
+ export * from "./resource/types.js";
2
+ export * from "./resource/map.js";
3
+ export * from "./resource/lzs.js";
4
+ export * from "./resource/manager.js";
5
+ export * from "./resource/archive.js";
6
+ export * from "./gfx/palette.js";
7
+ export * from "./gfx/cel.js";
8
+ export * from "./gfx/view.js";
9
+ export * from "./gfx/pic.js";
10
+ export * from "./gfx/pic-file.js";
11
+ export * from "./gfx/quantize.js";
12
+ export * from "./gfx/view-file.js";
13
+ export * from "./text/font.js";
14
+ export * from "./text/message.js";
15
+ export * from "./script/kernel-names.js";
16
+ export * from "./script/vocab.js";
17
+ export * from "./script/script.js";
18
+ export * from "./script/opcodes.js";
19
+ export * from "./script/disasm.js";
20
+ export * from "./script/world.js";
21
+ export * from "./vm/value.js";
22
+ export * from "./vm/memory.js";
23
+ export * from "./vm/vm.js";
24
+ export * from "./vm/kernels/index.js";
25
+ export * from "./audio/sol.js";
26
+ export * from "./audio/index.js";
27
+ export * from "./audio/midi.js";
28
+ export * from "./audio/opl2.js";
29
+ export * from "./audio/adlib.js";
30
+ export * from "./audio/write.js";
31
+ export * from "./motion/pathfinding.js";
32
+ export * from "./vm/savegame.js";
33
+ export * from "./analysis/game.js";
34
+ export * from "./analysis/runtime.js";
35
+ export * from "./script/assembly.js";
36
+ export * from "./script/sexpr.js";
37
+ export * from "./script/compiler.js";
38
+ export * from "./vm/replace.js";
@@ -0,0 +1,38 @@
1
+ export * from "./resource/types.ts";
2
+ export * from "./resource/map.ts";
3
+ export * from "./resource/lzs.ts";
4
+ export * from "./resource/manager.ts";
5
+ export * from "./resource/archive.ts";
6
+ export * from "./gfx/palette.ts";
7
+ export * from "./gfx/cel.ts";
8
+ export * from "./gfx/view.ts";
9
+ export * from "./gfx/pic.ts";
10
+ export * from "./gfx/pic-file.ts";
11
+ export * from "./gfx/quantize.ts";
12
+ export * from "./gfx/view-file.ts";
13
+ export * from "./text/font.ts";
14
+ export * from "./text/message.ts";
15
+ export * from "./script/kernel-names.ts";
16
+ export * from "./script/vocab.ts";
17
+ export * from "./script/script.ts";
18
+ export * from "./script/opcodes.ts";
19
+ export * from "./script/disasm.ts";
20
+ export * from "./script/world.ts";
21
+ export * from "./vm/value.ts";
22
+ export * from "./vm/memory.ts";
23
+ export * from "./vm/vm.ts";
24
+ export * from "./vm/kernels/index.ts";
25
+ export * from "./audio/sol.ts";
26
+ export * from "./audio/index.ts";
27
+ export * from "./audio/midi.ts";
28
+ export * from "./audio/opl2.ts";
29
+ export * from "./audio/adlib.ts";
30
+ export * from "./audio/write.ts";
31
+ export * from "./motion/pathfinding.ts";
32
+ export * from "./vm/savegame.ts";
33
+ export * from "./analysis/game.ts";
34
+ export * from "./analysis/runtime.ts";
35
+ export * from "./script/assembly.ts";
36
+ export * from "./script/sexpr.ts";
37
+ export * from "./script/compiler.ts";
38
+ export * from "./vm/replace.ts";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pathfinding over SCI polygons.
3
+ *
4
+ * Rooms describe walkable space with polygons of four access types:
5
+ * Total (0): can't enter; if you start inside you may walk out.
6
+ * Nearest (1): can't enter; clicking inside walks to its nearest edge.
7
+ * Barred (2): can't enter at all.
8
+ * Contained (3): the walkable area itself: you can't leave it.
9
+ *
10
+ * `findPath` snaps unreachable start/end points to the nearest reachable point, builds a
11
+ * visibility graph over the polygon vertices (two points are connected if the straight
12
+ * segment between them stays walkable) and runs Dijkstra. Boundaries count as walkable,
13
+ * so paths can hug walls and corners, like the original.
14
+ */
15
+ export declare const PolygonType: {
16
+ readonly Total: 0;
17
+ readonly Nearest: 1;
18
+ readonly Barred: 2;
19
+ readonly Contained: 3;
20
+ };
21
+ export interface Point {
22
+ x: number;
23
+ y: number;
24
+ }
25
+ export interface Polygon {
26
+ type: number;
27
+ points: Point[];
28
+ }
29
+ export type Containment = "inside" | "outside" | "edge";
30
+ export declare function contains(poly: Polygon, p: Point): Containment;
31
+ /** Walkable = inside (or on) every contained polygon and not strictly inside any obstacle. */
32
+ export declare function walkable(polys: Polygon[], p: Point): boolean;
33
+ /** Nearest walkable point to `p`, found on polygon edges (rounded to whole pixels). */
34
+ export declare function nearestWalkable(polys: Polygon[], p: Point): Point;
35
+ /**
36
+ * Shortest walkable path from `start` to `end`, including both endpoints. If the end is
37
+ * unreachable it's moved to the nearest reachable point; if the start is inside an
38
+ * obstacle (e.g. pushed into one), the path first steps out of it.
39
+ */
40
+ export declare function findPath(start: Point, end: Point, polys: Polygon[]): Point[];
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Pathfinding over SCI polygons.
3
+ *
4
+ * Rooms describe walkable space with polygons of four access types:
5
+ * Total (0): can't enter; if you start inside you may walk out.
6
+ * Nearest (1): can't enter; clicking inside walks to its nearest edge.
7
+ * Barred (2): can't enter at all.
8
+ * Contained (3): the walkable area itself: you can't leave it.
9
+ *
10
+ * `findPath` snaps unreachable start/end points to the nearest reachable point, builds a
11
+ * visibility graph over the polygon vertices (two points are connected if the straight
12
+ * segment between them stays walkable) and runs Dijkstra. Boundaries count as walkable,
13
+ * so paths can hug walls and corners, like the original.
14
+ */
15
+ export const PolygonType = { Total: 0, Nearest: 1, Barred: 2, Contained: 3 };
16
+ const EPS = 1e-9;
17
+ const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
18
+ function onSegment(p, a, b) {
19
+ if (Math.abs(cross(a, b, p)) > EPS)
20
+ return false;
21
+ return p.x >= Math.min(a.x, b.x) - EPS && p.x <= Math.max(a.x, b.x) + EPS && p.y >= Math.min(a.y, b.y) - EPS && p.y <= Math.max(a.y, b.y) + EPS;
22
+ }
23
+ function edges(poly) {
24
+ return poly.points.map((p, i) => [p, poly.points[(i + 1) % poly.points.length]]);
25
+ }
26
+ export function contains(poly, p) {
27
+ let inside = false;
28
+ for (const [a, b] of edges(poly)) {
29
+ if (onSegment(p, a, b))
30
+ return "edge";
31
+ if (a.y > p.y !== b.y > p.y && p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x)
32
+ inside = !inside;
33
+ }
34
+ return inside ? "inside" : "outside";
35
+ }
36
+ /** Walkable = inside (or on) every contained polygon and not strictly inside any obstacle. */
37
+ export function walkable(polys, p) {
38
+ for (const poly of polys) {
39
+ const c = contains(poly, p);
40
+ if (poly.type === PolygonType.Contained ? c === "outside" : c === "inside")
41
+ return false;
42
+ }
43
+ return true;
44
+ }
45
+ /** True if segments ab and cd cross at a point interior to both. */
46
+ function properlyIntersect(a, b, c, d) {
47
+ const d1 = cross(c, d, a), d2 = cross(c, d, b), d3 = cross(a, b, c), d4 = cross(a, b, d);
48
+ return ((d1 > EPS && d2 < -EPS) || (d1 < -EPS && d2 > EPS)) && ((d3 > EPS && d4 < -EPS) || (d3 < -EPS && d4 > EPS));
49
+ }
50
+ /** A segment is clear if it crosses no polygon edge and its interior stays walkable. */
51
+ function clear(polys, a, b) {
52
+ for (const poly of polys)
53
+ for (const [c, d] of edges(poly))
54
+ if (properlyIntersect(a, b, c, d))
55
+ return false;
56
+ // Crossing no edges can still mean running through a polygon's interior (e.g. a diagonal
57
+ // between two of its vertices), so sample along the segment.
58
+ const steps = Math.max(2, Math.ceil(Math.hypot(b.x - a.x, b.y - a.y) / 4));
59
+ for (let i = 1; i < steps; i++) {
60
+ const t = i / steps;
61
+ if (!walkable(polys, { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }))
62
+ return false;
63
+ }
64
+ return true;
65
+ }
66
+ function nearestOnSegment(p, a, b) {
67
+ const dx = b.x - a.x, dy = b.y - a.y;
68
+ const len2 = dx * dx + dy * dy;
69
+ const t = len2 ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2)) : 0;
70
+ return { x: a.x + t * dx, y: a.y + t * dy };
71
+ }
72
+ /** Nearest walkable point to `p`, found on polygon edges (rounded to whole pixels). */
73
+ export function nearestWalkable(polys, p) {
74
+ if (walkable(polys, p))
75
+ return p;
76
+ let best;
77
+ let bestDist = Infinity;
78
+ for (const poly of polys) {
79
+ for (const [a, b] of edges(poly)) {
80
+ const q = nearestOnSegment(p, a, b);
81
+ // Try the point and its integer neighbours; SCI coordinates are whole pixels.
82
+ for (const cand of [{ x: Math.round(q.x), y: Math.round(q.y) }, { x: Math.floor(q.x), y: Math.floor(q.y) }, { x: Math.ceil(q.x), y: Math.ceil(q.y) }, q]) {
83
+ const d = Math.hypot(cand.x - p.x, cand.y - p.y);
84
+ if (d < bestDist && walkable(polys, cand)) {
85
+ best = cand;
86
+ bestDist = d;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return best ?? p;
92
+ }
93
+ /**
94
+ * Shortest walkable path from `start` to `end`, including both endpoints. If the end is
95
+ * unreachable it's moved to the nearest reachable point; if the start is inside an
96
+ * obstacle (e.g. pushed into one), the path first steps out of it.
97
+ */
98
+ export function findPath(start, end, polys) {
99
+ // Obstacles that contain the start are ignored, so an actor stuck in one can walk out.
100
+ const active = polys.filter((p) => p.type === PolygonType.Contained || contains(p, start) !== "inside");
101
+ const from = nearestWalkable(active, start);
102
+ const to = nearestWalkable(active, end);
103
+ const prefix = from.x !== start.x || from.y !== start.y ? [start] : [];
104
+ if (clear(active, from, to))
105
+ return [...prefix, from, to];
106
+ const nodes = [from, to];
107
+ for (const poly of active)
108
+ for (const p of poly.points)
109
+ if (walkable(active, p))
110
+ nodes.push(p);
111
+ // Dijkstra over the visibility graph (edges evaluated lazily; rooms have few vertices).
112
+ const dist = new Array(nodes.length).fill(Infinity);
113
+ const prev = new Array(nodes.length).fill(-1);
114
+ const done = new Array(nodes.length).fill(false);
115
+ dist[0] = 0;
116
+ for (;;) {
117
+ let u = -1;
118
+ for (let i = 0; i < nodes.length; i++)
119
+ if (!done[i] && dist[i] < Infinity && (u === -1 || dist[i] < dist[u]))
120
+ u = i;
121
+ if (u === -1 || u === 1)
122
+ break;
123
+ done[u] = true;
124
+ for (let v = 0; v < nodes.length; v++) {
125
+ if (done[v])
126
+ continue;
127
+ const w = Math.hypot(nodes[v].x - nodes[u].x, nodes[v].y - nodes[u].y);
128
+ if (dist[u] + w < dist[v] && clear(active, nodes[u], nodes[v])) {
129
+ dist[v] = dist[u] + w;
130
+ prev[v] = u;
131
+ }
132
+ }
133
+ }
134
+ if (prev[1] === -1)
135
+ return [...prefix, from]; // unreachable: stay put
136
+ const path = [];
137
+ for (let i = 1; i !== -1; i = prev[i])
138
+ path.unshift(nodes[i]);
139
+ return [...prefix, ...path];
140
+ }