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,283 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import {
5
+ AsmError, CompileError, GLOBAL_NAMES_VOCAB, OBJECT_HEADER, ResourceType, assemble, compileScripts, kernelNames, messagesFromText, parseScript,
6
+ resourceKey, writeClassTable, writeEffects, writeMessages, writeSelectorNames, writeSol, writeSound, type AsmContext,
7
+ type ResourceData, type ScriptObject,
8
+ } from "../../packages/sci/src/index.ts";
9
+ import { ContentError, compileRoom } from "../../packages/content/src/index.ts";
10
+ import { modFlags } from "../mod-build.ts";
11
+ import { AudioFileError, readMidiFile, readWav } from "./audio.ts";
12
+ import { defaultResources } from "./defaults.ts";
13
+ import { libraryTarget } from "./target.ts";
14
+
15
+ /**
16
+ * Builds a game from its sources, needing nothing from any other game:
17
+ *
18
+ * games/<name>/scripts/<n>.sc scripts (see packages/sci/src/script/compiler.ts), compiled
19
+ * together; they may include files from the game folder
20
+ * games/<name>/scripts/<n>.sca scripts in assembly (packages/sci/src/script/assembly.ts)
21
+ * games/<name>/messages/<n>.msg message files, as text (`pnpm msg` writes the same form)
22
+ * games/<name>/rooms/<n>.room.yaml (+ <n>.yarn) rooms as data (packages/content), compiled
23
+ * for the class library; their Yarn variables are flags
24
+ * numbered in flags.yaml
25
+ * games/<name>/music/<n>.mid music: sound n, from a Standard MIDI File (General MIDI)
26
+ * games/<name>/sounds/<n>.wav digital effects: sound n plays this instead of music
27
+ * games/<name>/resources.ts optional: `export default () => ResourceData[]` for art,
28
+ * music and anything else made by code
29
+ * games/<name>/game.json optional: { "library": false } to build without lib/
30
+ *
31
+ * plus the class library (lib/: its scripts and resources) and font 0, palette 999 and cursor
32
+ * view 999 (tools/game/defaults.ts), unless the game makes its own. Selectors are numbered as the scripts use them, starting with the nine object
33
+ * header slots; vocab 997 lists them and vocab 996 says which script defines each class.
34
+ */
35
+ export interface BuiltGame {
36
+ resources: ResourceData[];
37
+ selectors: string[];
38
+ classes: Map<number, { name: string; script: number }>;
39
+ /** The assembly compiled from each .sc file, by script number. */
40
+ generated: Map<number, string>;
41
+ /** The globals' names, by number (when script 0 is compiled). */
42
+ globals: string[];
43
+ /** The message text compiled from each YAML room's Yarn, by room. */
44
+ roomMessages: Map<number, string>;
45
+ /** Files that go next to RESOURCE.MAP and RESOURCE.000 (RESOURCE.SFX, the effects). */
46
+ files: Record<string, Uint8Array>;
47
+ /** Likely mistakes that still built, as file:line: message. */
48
+ warnings: string[];
49
+ }
50
+
51
+ const HEADER = OBJECT_HEADER;
52
+
53
+ export class GameBuildError extends Error {}
54
+
55
+ /** sci-ts's class library: scripts every game gets, and the resources they use. */
56
+ export const LIBRARY_DIR = resolve(import.meta.dirname, "../../lib");
57
+
58
+ export async function buildGame(dir: string, options: { library?: boolean } = {}): Promise<BuiltGame> {
59
+ const scriptsDir = join(dir, "scripts");
60
+ if (!existsSync(scriptsDir)) throw new GameBuildError(`${dir}: no scripts/ folder`);
61
+ const config = existsSync(join(dir, "game.json")) ? (JSON.parse(readFileSync(join(dir, "game.json"), "utf8")) as { library?: boolean }) : {};
62
+ const library = options.library ?? config.library ?? true;
63
+ const load = (from: string, re: RegExp) =>
64
+ readdirSync(from).sort().filter((f) => re.test(f)).map((f) => ({ file: join(from, f), text: readFileSync(join(from, f), "utf8") }));
65
+ const sources = load(scriptsDir, /^\d+\.sca$/);
66
+ const compiled = load(scriptsDir, /^\d+\.sc$/);
67
+ const numberOf = (file: string) => Number(basename(file).split(".")[0]);
68
+ const numbers = [...sources, ...compiled].map((s) => numberOf(s.file));
69
+ if (!numbers.includes(0)) throw new GameBuildError(`${dir}: no scripts/0.sc or 0.sca (script 0 exports the game object)`);
70
+ const repeated = numbers.find((n, i) => numbers.indexOf(n) !== i);
71
+ if (repeated !== undefined) throw new GameBuildError(`${dir}: script ${repeated} is there twice`);
72
+ if (library) {
73
+ const lib = load(LIBRARY_DIR, /^\d+\.sc$/);
74
+ const clash = lib.find((l) => numbers.includes(numberOf(l.file)));
75
+ if (clash) throw new GameBuildError(`${dir}: script ${numberOf(clash.file)} is the library's (${relative(process.cwd(), clash.file)})`);
76
+ compiled.push(...lib);
77
+ }
78
+
79
+ const selectors = [...HEADER];
80
+ const classes = new Map<number, ScriptObject & { script: number }>();
81
+ const byName = new Map<string, number>();
82
+ const ctx: AsmContext = {
83
+ selectorNames: selectors,
84
+ kernelNames,
85
+ className: (species) => classes.get(species)?.name,
86
+ propSelectors: (obj) => (obj.isClass ? obj.propSelectors : classes.get(obj.superclass)?.propSelectors),
87
+ classSpecies: (name) => byName.get(name),
88
+ classPropSelectors: (species) => classes.get(species)?.propSelectors,
89
+ classDefaults: (species) => classes.get(species)?.properties,
90
+ newSelector: (name) => selectors.push(name) - 1,
91
+ };
92
+
93
+ // Compiled scripts may subclass classes from assembled ones (known once those are built),
94
+ // so the compiler runs inside the same loop.
95
+ const generated = new Map<number, string>();
96
+ let globals: string[] = [];
97
+ const takenSpecies = new Set(sources.flatMap((s) => [...s.text.matchAll(/^\s*class\s+\S+\s+of\s+\S+\s+species\s+(\d+)/gm)].map((m) => Number(m[1]))));
98
+ let compileError: CompileError | undefined;
99
+ const warnings: string[] = [];
100
+ const tryCompile = (): { file: string; text: string }[] => {
101
+ if (!compiled.length) return [];
102
+ try {
103
+ const out = compileScripts(
104
+ compiled.map((s) => ({ file: relative(process.cwd(), s.file), text: s.text })),
105
+ {
106
+ kernelNames,
107
+ takenSpecies,
108
+ // Only when every class is compiled: hand-written scripts' methods can't be seen.
109
+ onWarning: sources.length ? undefined : (w) => warnings.push(`${w.file}:${w.line}: ${w.message}`),
110
+ externalClass: (name) => {
111
+ const species = byName.get(name);
112
+ const c = species === undefined ? undefined : classes.get(species);
113
+ if (!c) return undefined;
114
+ const props = c.propSelectors!.slice(HEADER.length);
115
+ return { species: c.species, properties: props.map((sel, i) => ({ name: selectors[sel]!, value: c.properties[HEADER.length + i]! })) };
116
+ },
117
+ // Next to the including file, then in the game folder, then in the library.
118
+ include: (name, from) => {
119
+ const path = [join(dirname(from), name), join(dir, name), join(LIBRARY_DIR, name)].find((p) => existsSync(p));
120
+ return path ? readFileSync(path, "utf8") : undefined;
121
+ },
122
+ },
123
+ );
124
+ compiled.length = 0;
125
+ compileError = undefined;
126
+ for (const c of out) generated.set(c.number, c.assembly);
127
+ globals = out.find((c) => c.number === 0)?.variables ?? [];
128
+ const rooms = library ? compileRooms() : [];
129
+ return [...out.map((c) => ({ file: c.file, text: c.assembly })), ...rooms];
130
+ } catch (e) {
131
+ if (!(e instanceof CompileError)) throw e;
132
+ compileError = e;
133
+ return [];
134
+ }
135
+ };
136
+
137
+ // Rooms as YAML and Yarn: compiled once script 0 is (the target needs its globals).
138
+ const roomMessages: ResourceData[] = [];
139
+ const roomMessageText = new Map<number, string>();
140
+ const compileRooms = (): { file: string; text: string }[] => {
141
+ const roomsDir = join(dir, "rooms");
142
+ if (!existsSync(roomsDir)) return [];
143
+ const target = libraryTarget(globals);
144
+ const flags = modFlags(dir, { content: target });
145
+ const out: { file: string; text: string }[] = [];
146
+ for (const { file, text } of load(roomsDir, /^\d+\.room\.yaml$/)) {
147
+ const n = numberOf(file);
148
+ if (numbers.includes(n)) throw new GameBuildError(`${relative(process.cwd(), file)}: there's also a scripts/${n} (a room is one or the other)`);
149
+ const yarnFile = join(roomsDir, `${n}.yarn`);
150
+ let room: ReturnType<typeof compileRoom>;
151
+ try {
152
+ room = compileRoom(text, existsSync(yarnFile) ? readFileSync(yarnFile, "utf8") : undefined, target,
153
+ { yaml: relative(process.cwd(), file), yarn: relative(process.cwd(), yarnFile) }, { flag: flags.number });
154
+ } catch (e) {
155
+ if (e instanceof ContentError) throw new GameBuildError(e.message);
156
+ throw e;
157
+ }
158
+ generated.set(n, room.sca);
159
+ roomMessageText.set(n, room.msg);
160
+ roomMessages.push({ type: ResourceType.Message, number: n, data: writeMessages(messagesFromText(room.msg).file) });
161
+ out.push({ file: relative(process.cwd(), file), text: room.sca });
162
+ }
163
+ flags.save();
164
+ return out;
165
+ };
166
+
167
+ // A script can only be assembled once the classes it uses are known, so keep going round
168
+ // until everything is built or nothing more can be.
169
+ const resources: ResourceData[] = [];
170
+ let pending = [...sources, ...tryCompile()];
171
+ while (pending.length || compiled.length) {
172
+ const failed: { file: string; text: string; error: AsmError }[] = [];
173
+ for (const src of pending) {
174
+ try {
175
+ const built = assemble(src.text, ctx);
176
+ if (Number(basename(src.file).split(".")[0]) !== built.number) throw new GameBuildError(`${src.file} says it is script ${built.number}`);
177
+ resources.push({ type: ResourceType.Script, number: built.number, data: built.code }, { type: ResourceType.Heap, number: built.number, data: built.heap });
178
+ for (const o of parseScript(built.number, built.code, built.heap).objects) {
179
+ if (!o.isClass) continue;
180
+ const other = classes.get(o.species);
181
+ if (other) throw new GameBuildError(`${src.file}: class ${o.name} has species ${o.species}, already taken by ${other.name} (script ${other.script})`);
182
+ if (byName.has(o.name)) throw new GameBuildError(`${src.file}: class ${o.name} is defined twice`);
183
+ classes.set(o.species, { ...o, script: built.number });
184
+ byName.set(o.name, o.species);
185
+ }
186
+ } catch (e) {
187
+ if (!(e instanceof AsmError)) throw e;
188
+ failed.push({ ...src, error: e });
189
+ }
190
+ }
191
+ const more = tryCompile();
192
+ if (failed.length === pending.length && !more.length) {
193
+ if (compileError) throw new GameBuildError(compileError.message);
194
+ throw new GameBuildError(failed.map((f) => `${f.file}: ${f.error.message}`).join("\n"));
195
+ }
196
+ pending = [...failed, ...more];
197
+ }
198
+
199
+ const classTable = Array.from({ length: Math.max(-1, ...classes.keys()) + 1 }, (_, species) => classes.get(species)?.script ?? 0);
200
+ resources.push(
201
+ { type: ResourceType.Vocab, number: 997, data: writeSelectorNames(selectors) },
202
+ { type: ResourceType.Vocab, number: 996, data: writeClassTable(classTable) },
203
+ );
204
+ // The globals' names, for tools reading the running game (the player's room links, the
205
+ // editor, the explorer): sci-ts's own vocab, in the selector list format.
206
+ if (globals.length) resources.push({ type: ResourceType.Vocab, number: GLOBAL_NAMES_VOCAB, data: writeSelectorNames(globals) });
207
+
208
+ // Messages: the rooms', then those written as text.
209
+ resources.push(...roomMessages);
210
+ const messagesDir = join(dir, "messages");
211
+ if (existsSync(messagesDir)) {
212
+ for (const { file, text } of load(messagesDir, /^\d+\.msg$/)) {
213
+ let parsed: ReturnType<typeof messagesFromText>;
214
+ try {
215
+ parsed = messagesFromText(text);
216
+ } catch (e) {
217
+ throw new GameBuildError(`${relative(process.cwd(), file)}: ${(e as Error).message}`);
218
+ }
219
+ if (parsed.number !== numberOf(file)) throw new GameBuildError(`${file} says it is message file ${parsed.number}`);
220
+ resources.push({ type: ResourceType.Message, number: parsed.number, data: writeMessages(parsed.file) });
221
+ }
222
+ }
223
+
224
+ // Music (MIDI files) and digital effects (WAV files).
225
+ const audioFile = <T>(file: string, read: () => T): T => {
226
+ try {
227
+ return read();
228
+ } catch (e) {
229
+ if (e instanceof AudioFileError) throw new GameBuildError(`${relative(process.cwd(), file)}: ${e.message}`);
230
+ throw e;
231
+ }
232
+ };
233
+ const musicDir = join(dir, "music");
234
+ if (existsSync(musicDir)) {
235
+ for (const f of readdirSync(musicDir).filter((x) => /^\d+\.midi?$/i.test(x)).sort()) {
236
+ const file = join(musicDir, f);
237
+ const spec = audioFile(file, () => readMidiFile(new Uint8Array(readFileSync(file))));
238
+ resources.push({ type: ResourceType.Sound, number: numberOf(file), data: writeSound(spec) });
239
+ }
240
+ }
241
+ const files: Record<string, Uint8Array> = {};
242
+ const soundsDir = join(dir, "sounds");
243
+ if (existsSync(soundsDir)) {
244
+ const clips = readdirSync(soundsDir).filter((x) => /^\d+\.wav$/i.test(x)).sort().map((f) => {
245
+ const file = join(soundsDir, f);
246
+ const { samples, rate } = audioFile(file, () => readWav(new Uint8Array(readFileSync(file))));
247
+ return { number: numberOf(file), sol: writeSol(samples, rate) };
248
+ });
249
+ if (clips.length) {
250
+ const { sfx, map } = writeEffects(clips);
251
+ files["RESOURCE.SFX"] = sfx;
252
+ resources.push({ type: ResourceType.Map, number: 65535, data: map });
253
+ }
254
+ }
255
+
256
+ // Resources made in code: the game's, then the library's and the defaults for what's left.
257
+ const made = async (path: string) => {
258
+ if (!existsSync(path)) return [];
259
+ const mod = (await import(pathToFileURL(resolve(path)).href)) as { default: () => ResourceData[] | Promise<ResourceData[]> };
260
+ return mod.default();
261
+ };
262
+ resources.push(...(await made(join(dir, "resources.ts"))));
263
+ const have = new Set(resources.map(resourceKey));
264
+ // The library's resources are compiled in the npm package (resources.js), source here.
265
+ const libraryResources = ["resources.js", "resources.ts"].map((f) => join(LIBRARY_DIR, f)).find((f) => existsSync(f))!;
266
+ const fallback = [...(library ? await made(libraryResources) : []), ...defaultResources()];
267
+ for (const r of fallback) if (!have.has(resourceKey(r))) (resources.push(r), have.add(resourceKey(r)));
268
+
269
+ const keys = resources.map(resourceKey);
270
+ const twice = keys.find((k, i) => keys.indexOf(k) !== i);
271
+ if (twice) throw new GameBuildError(`${dir}: resource ${twice} is made twice`);
272
+
273
+ return {
274
+ resources,
275
+ selectors,
276
+ classes: new Map([...classes].map(([species, c]) => [species, { name: c.name, script: c.script }])),
277
+ generated,
278
+ globals,
279
+ roomMessages: roomMessageText,
280
+ files,
281
+ warnings: [...new Set(warnings)],
282
+ };
283
+ }
@@ -0,0 +1,26 @@
1
+ import { type HunkPaletteFile, type ResourceData } from "../../packages/sci/src/index.ts";
2
+ /**
3
+ * What every game built by sci-ts gets unless it brings its own: font 0, palette 999 (the
4
+ * base palette) and view 999 (the cursor).
5
+ */
6
+ /** Colour indices in the base palette. */
7
+ export declare const Colour: {
8
+ readonly Black: 0;
9
+ readonly White: 255;
10
+ readonly Transparent: 254;
11
+ };
12
+ /**
13
+ * The base palette:
14
+ * 0-15 black, then a dark and a light version of seven hues
15
+ * 16-231 a 6×6×6 colour cube (index 16 + 36r + 6g + b, each 0-5)
16
+ * 232-253 greys, dark to light
17
+ * 254 the transparent colour our own art uses (never drawn)
18
+ * 255 white
19
+ */
20
+ export declare function basePalette(): HunkPaletteFile;
21
+ /** The nearest colour-cube index for an RGB colour (0-255 per channel). */
22
+ export declare const cube: (r: number, g: number, b: number) => number;
23
+ export declare const DEFAULT_FONT = 0;
24
+ export declare const BASE_PALETTE = 999;
25
+ export declare const CURSOR_VIEW = 999;
26
+ export declare function defaultResources(): ResourceData[];
@@ -0,0 +1,82 @@
1
+ import { writeFont, writeHunkPalette, writeView, ResourceType } from "../../packages/sci/src/index.js";
2
+ import { pixelFont } from "./font.js";
3
+ /**
4
+ * What every game built by sci-ts gets unless it brings its own: font 0, palette 999 (the
5
+ * base palette) and view 999 (the cursor).
6
+ */
7
+ /** Colour indices in the base palette. */
8
+ export const Colour = { Black: 0, White: 255, Transparent: 254 };
9
+ /**
10
+ * The base palette:
11
+ * 0-15 black, then a dark and a light version of seven hues
12
+ * 16-231 a 6×6×6 colour cube (index 16 + 36r + 6g + b, each 0-5)
13
+ * 232-253 greys, dark to light
14
+ * 254 the transparent colour our own art uses (never drawn)
15
+ * 255 white
16
+ */
17
+ export function basePalette() {
18
+ const rgb = [[0, 0, 0]];
19
+ const hues = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1]];
20
+ for (const level of [128, 240])
21
+ for (const [r, g, b] of hues)
22
+ if (rgb.length < 16)
23
+ rgb.push([r * level, g * level, b * level]);
24
+ while (rgb.length < 16)
25
+ rgb.push([255, 255, 255]);
26
+ const step = [0, 51, 102, 153, 204, 255];
27
+ for (let r = 0; r < 6; r++)
28
+ for (let g = 0; g < 6; g++)
29
+ for (let b = 0; b < 6; b++)
30
+ rgb.push([step[r], step[g], step[b]]);
31
+ for (let i = 0; i < 22; i++) {
32
+ const v = Math.round(8 + (i * (247 - 8)) / 21);
33
+ rgb.push([v, v, v]);
34
+ }
35
+ rgb.push([255, 0, 255], [255, 255, 255]);
36
+ return {
37
+ header: Uint8Array.from([14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]),
38
+ entryPrefix: new Uint8Array(10),
39
+ first: 0,
40
+ used: rgb.map(() => 1),
41
+ rgb,
42
+ shared: false,
43
+ defaultUsed: 1,
44
+ };
45
+ }
46
+ /** The nearest colour-cube index for an RGB colour (0-255 per channel). */
47
+ export const cube = (r, g, b) => 16 + 36 * Math.round(r / 51) + 6 * Math.round(g / 51) + Math.round(b / 51);
48
+ /** An arrow, white with a black outline; its hotspot is the tip (the top-left pixel). */
49
+ function cursorView() {
50
+ const art = [
51
+ "X.........",
52
+ "XX........",
53
+ "XWX.......",
54
+ "XWWX......",
55
+ "XWWWX.....",
56
+ "XWWWWX....",
57
+ "XWWWWWX...",
58
+ "XWWWWWWX..",
59
+ "XWWWWWWWX.",
60
+ "XWWWWXXXXX",
61
+ "XWWXWWX...",
62
+ "XWX.XWWX..",
63
+ "XX..XWWX..",
64
+ "X....XWWX.",
65
+ ".....XWWX.",
66
+ "......XX..",
67
+ ];
68
+ const width = art[0].length, height = art.length;
69
+ const pixels = Uint8Array.from(art.join(""), (c) => (c === "X" ? Colour.Black : c === "W" ? Colour.White : Colour.Transparent));
70
+ const cel = { width, height, displaceX: width >> 1, displaceY: height - 1, skipColor: Colour.Transparent, pixels };
71
+ return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
72
+ }
73
+ export const DEFAULT_FONT = 0;
74
+ export const BASE_PALETTE = 999;
75
+ export const CURSOR_VIEW = 999;
76
+ export function defaultResources() {
77
+ return [
78
+ { type: ResourceType.Font, number: DEFAULT_FONT, data: writeFont(pixelFont()) },
79
+ { type: ResourceType.Palette, number: BASE_PALETTE, data: writeHunkPalette(basePalette()) },
80
+ { type: ResourceType.View, number: CURSOR_VIEW, data: cursorView() },
81
+ ];
82
+ }
@@ -0,0 +1,83 @@
1
+ import { writeFont, writeHunkPalette, writeView, type HunkPaletteFile, type ResourceData, ResourceType } from "../../packages/sci/src/index.ts";
2
+ import { pixelFont } from "./font.ts";
3
+
4
+ /**
5
+ * What every game built by sci-ts gets unless it brings its own: font 0, palette 999 (the
6
+ * base palette) and view 999 (the cursor).
7
+ */
8
+
9
+ /** Colour indices in the base palette. */
10
+ export const Colour = { Black: 0, White: 255, Transparent: 254 } as const;
11
+
12
+ /**
13
+ * The base palette:
14
+ * 0-15 black, then a dark and a light version of seven hues
15
+ * 16-231 a 6×6×6 colour cube (index 16 + 36r + 6g + b, each 0-5)
16
+ * 232-253 greys, dark to light
17
+ * 254 the transparent colour our own art uses (never drawn)
18
+ * 255 white
19
+ */
20
+ export function basePalette(): HunkPaletteFile {
21
+ const rgb: [number, number, number][] = [[0, 0, 0]];
22
+ const hues: [number, number, number][] = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1]];
23
+ for (const level of [128, 240]) for (const [r, g, b] of hues) if (rgb.length < 16) rgb.push([r * level, g * level, b * level]);
24
+ while (rgb.length < 16) rgb.push([255, 255, 255]);
25
+ const step = [0, 51, 102, 153, 204, 255];
26
+ for (let r = 0; r < 6; r++) for (let g = 0; g < 6; g++) for (let b = 0; b < 6; b++) rgb.push([step[r]!, step[g]!, step[b]!]);
27
+ for (let i = 0; i < 22; i++) {
28
+ const v = Math.round(8 + (i * (247 - 8)) / 21);
29
+ rgb.push([v, v, v]);
30
+ }
31
+ rgb.push([255, 0, 255], [255, 255, 255]);
32
+ return {
33
+ header: Uint8Array.from([14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]),
34
+ entryPrefix: new Uint8Array(10),
35
+ first: 0,
36
+ used: rgb.map(() => 1),
37
+ rgb,
38
+ shared: false,
39
+ defaultUsed: 1,
40
+ };
41
+ }
42
+
43
+ /** The nearest colour-cube index for an RGB colour (0-255 per channel). */
44
+ export const cube = (r: number, g: number, b: number): number =>
45
+ 16 + 36 * Math.round(r / 51) + 6 * Math.round(g / 51) + Math.round(b / 51);
46
+
47
+ /** An arrow, white with a black outline; its hotspot is the tip (the top-left pixel). */
48
+ function cursorView() {
49
+ const art = [
50
+ "X.........",
51
+ "XX........",
52
+ "XWX.......",
53
+ "XWWX......",
54
+ "XWWWX.....",
55
+ "XWWWWX....",
56
+ "XWWWWWX...",
57
+ "XWWWWWWX..",
58
+ "XWWWWWWWX.",
59
+ "XWWWWXXXXX",
60
+ "XWWXWWX...",
61
+ "XWX.XWWX..",
62
+ "XX..XWWX..",
63
+ "X....XWWX.",
64
+ ".....XWWX.",
65
+ "......XX..",
66
+ ];
67
+ const width = art[0]!.length, height = art.length;
68
+ const pixels = Uint8Array.from(art.join(""), (c) => (c === "X" ? Colour.Black : c === "W" ? Colour.White : Colour.Transparent));
69
+ const cel = { width, height, displaceX: width >> 1, displaceY: height - 1, skipColor: Colour.Transparent, pixels };
70
+ return writeView({ flags: 1, loops: [{ link: -1, mirror: false, cels: [cel] }], palette: undefined });
71
+ }
72
+
73
+ export const DEFAULT_FONT = 0;
74
+ export const BASE_PALETTE = 999;
75
+ export const CURSOR_VIEW = 999;
76
+
77
+ export function defaultResources(): ResourceData[] {
78
+ return [
79
+ { type: ResourceType.Font, number: DEFAULT_FONT, data: writeFont(pixelFont()) },
80
+ { type: ResourceType.Palette, number: BASE_PALETTE, data: writeHunkPalette(basePalette()) },
81
+ { type: ResourceType.View, number: CURSOR_VIEW, data: cursorView() },
82
+ ];
83
+ }
@@ -0,0 +1,3 @@
1
+ import type { Font } from "../../packages/sci/src/index.ts";
2
+ /** The font, as a Font (write it with writeFont). */
3
+ export declare function pixelFont(): Font;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * sci-ts's own pixel font: 7 pixels from the top of a capital to the baseline, 2 below for
3
+ * descenders, a line height of 10. Drawn here as rows of `#` (ink) and `.`; a glyph is 7 rows
4
+ * (sitting on the baseline) or 9 (with a descender). Each glyph gets one column of space after
5
+ * it. Characters 0-31 and 127 are empty.
6
+ */
7
+ const ROWS = 9;
8
+ const LINE_HEIGHT = 10;
9
+ const GLYPHS = {
10
+ " ": "... ... ... ... ... ... ...",
11
+ "!": "# # # # # . #",
12
+ '"': "#.# #.# ... ... ... ... ...",
13
+ "#": "..... .#.#. ##### .#.#. ##### .#.#. .....",
14
+ "$": "..#.. .#### #.#.. .###. ..#.# ####. ..#..",
15
+ "%": "##..# ##.#. ..#.. .#... #..## #..## .....",
16
+ "&": ".#.. #.#. .#.. #.#. #..# #.#. .#.#",
17
+ "'": "# # . . . . .",
18
+ "(": ".# #. #. #. #. #. .#",
19
+ ")": "#. .# .# .# .# .# #.",
20
+ "*": "... #.# .#. #.# ... ... ...",
21
+ "+": "... ... .#. ### .#. ... ...",
22
+ ",": ".. .. .. .. .. .# .# #. ..",
23
+ "-": "... ... ... ### ... ... ...",
24
+ ".": ". . . . . . #",
25
+ "/": "...# ...# ..#. ..#. .#.. #... #...",
26
+ "0": ".##. #..# #.## ##.# #..# #..# .##.",
27
+ "1": ".#. ##. .#. .#. .#. .#. ###",
28
+ "2": ".##. #..# ...# ..#. .#.. #... ####",
29
+ "3": "###. ...# ...# .##. ...# ...# ###.",
30
+ "4": "#..# #..# #..# #### ...# ...# ...#",
31
+ "5": "#### #... ###. ...# ...# #..# .##.",
32
+ "6": ".##. #... #... ###. #..# #..# .##.",
33
+ "7": "#### ...# ..#. ..#. .#.. .#.. .#..",
34
+ "8": ".##. #..# #..# .##. #..# #..# .##.",
35
+ "9": ".##. #..# #..# .### ...# ...# .##.",
36
+ ":": ". . # . . # .",
37
+ ";": ".. .. .# .. .. .# .# #. ..",
38
+ "<": "... ..# .#. #.. .#. ..# ...",
39
+ "=": ".... .... #### .... #### .... ....",
40
+ ">": "... #.. .#. ..# .#. #.. ...",
41
+ "?": ".##. #..# ...# ..#. .#.. .... .#..",
42
+ "@": ".###. #...# #.### #.#.# #.### #.... .###.",
43
+ "A": ".##. #..# #..# #### #..# #..# #..#",
44
+ "B": "###. #..# #..# ###. #..# #..# ###.",
45
+ "C": ".##. #..# #... #... #... #..# .##.",
46
+ "D": "###. #..# #..# #..# #..# #..# ###.",
47
+ "E": "#### #... #... ###. #... #... ####",
48
+ "F": "#### #... #... ###. #... #... #...",
49
+ "G": ".##. #..# #... #.## #..# #..# .###",
50
+ "H": "#..# #..# #..# #### #..# #..# #..#",
51
+ "I": "### .#. .#. .#. .#. .#. ###",
52
+ "J": "..## ...# ...# ...# ...# #..# .##.",
53
+ "K": "#..# #.#. ##.. ##.. #.#. #..# #..#",
54
+ "L": "#... #... #... #... #... #... ####",
55
+ "M": "#...# ##.## #.#.# #.#.# #...# #...# #...#",
56
+ "N": "#..# ##.# ##.# #.## #.## #..# #..#",
57
+ "O": ".##. #..# #..# #..# #..# #..# .##.",
58
+ "P": "###. #..# #..# ###. #... #... #...",
59
+ "Q": ".##. #..# #..# #..# #..# #.#. .#.#",
60
+ "R": "###. #..# #..# ###. #.#. #..# #..#",
61
+ "S": ".##. #..# #... .##. ...# #..# .##.",
62
+ "T": "##### ..#.. ..#.. ..#.. ..#.. ..#.. ..#..",
63
+ "U": "#..# #..# #..# #..# #..# #..# .##.",
64
+ "V": "#...# #...# #...# .#.#. .#.#. ..#.. ..#..",
65
+ "W": "#...# #...# #...# #.#.# #.#.# ##.## #...#",
66
+ "X": "#..# #..# .##. .##. .##. #..# #..#",
67
+ "Y": "#...# #...# .#.#. ..#.. ..#.. ..#.. ..#..",
68
+ "Z": "#### ...# ..#. .#.. #... #... ####",
69
+ "[": "## #. #. #. #. #. ##",
70
+ "\\": "#... #... .#.. .#.. ..#. ...# ...#",
71
+ "]": "## .# .# .# .# .# ##",
72
+ "^": ".#. #.# ... ... ... ... ...",
73
+ _: ".... .... .... .... .... .... .... .... ####",
74
+ "`": "#. .# .. .. .. .. ..",
75
+ a: ".... .... .##. ...# .### #..# .###",
76
+ b: "#... #... ###. #..# #..# #..# ###.",
77
+ c: "... ... .## #.. #.. #.. .##",
78
+ d: "...# ...# .### #..# #..# #..# .###",
79
+ e: ".... .... .##. #..# #### #... .##.",
80
+ f: ".## #.. ### #.. #.. #.. #..",
81
+ g: ".... .... .### #..# #..# #..# .### ...# .##.",
82
+ h: "#... #... ###. #..# #..# #..# #..#",
83
+ i: "# . # # # # #",
84
+ j: ".# .. .# .# .# .# .# .# #.",
85
+ k: "#... #... #..# #.#. ##.. #.#. #..#",
86
+ l: "#. #. #. #. #. #. .#",
87
+ m: "..... ..... ####. #.#.# #.#.# #.#.# #.#.#",
88
+ n: ".... .... ###. #..# #..# #..# #..#",
89
+ o: ".... .... .##. #..# #..# #..# .##.",
90
+ p: ".... .... ###. #..# #..# #..# ###. #... #...",
91
+ q: ".... .... .### #..# #..# #..# .### ...# ...#",
92
+ r: "... ... #.# ##. #.. #.. #..",
93
+ s: ".... .... .### #... .##. ...# ###.",
94
+ t: ".#. .#. ### .#. .#. .#. ..#",
95
+ u: ".... .... #..# #..# #..# #..# .###",
96
+ v: "..... ..... #...# #...# .#.#. .#.#. ..#..",
97
+ w: "..... ..... #...# #...# #.#.# #.#.# .#.#.",
98
+ x: "..... ..... #...# .#.#. ..#.. .#.#. #...#",
99
+ y: ".... .... #..# #..# #..# #..# .### ...# .##.",
100
+ z: ".... .... #### ...# ..#. .#.. ####",
101
+ "{": ".## .#. .#. #.. .#. .#. .##",
102
+ "|": "# # # # # # # # #",
103
+ "}": "##. .#. .#. ..# .#. .#. ##.",
104
+ "~": ".... .... .#.# #.#. .... .... ....",
105
+ };
106
+ function glyph(char, art) {
107
+ const rows = art.split(" ");
108
+ if (rows.length !== 7 && rows.length !== 9)
109
+ throw new Error(`glyph ${JSON.stringify(char)}: ${rows.length} rows`);
110
+ const inked = rows[0].length;
111
+ if (rows.some((r) => r.length !== inked))
112
+ throw new Error(`glyph ${JSON.stringify(char)}: rows of different widths`);
113
+ const width = inked + 1;
114
+ const pixels = new Uint8Array(width * ROWS);
115
+ rows.forEach((row, y) => [...row].forEach((c, x) => (pixels[y * width + x] = c === "#" ? 1 : 0)));
116
+ return { width, height: ROWS, pixels };
117
+ }
118
+ /** The font, as a Font (write it with writeFont). */
119
+ export function pixelFont() {
120
+ const glyphs = Array.from({ length: 128 }, () => ({ width: 0, height: 0, pixels: new Uint8Array(0) }));
121
+ for (const [char, art] of Object.entries(GLYPHS))
122
+ glyphs[char.charCodeAt(0)] = glyph(char, art);
123
+ return { height: LINE_HEIGHT, glyphs };
124
+ }