@rpgm-tools/neo-angband-core 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1183) hide show
  1. package/LICENSE.md +43 -0
  2. package/README.md +80 -0
  3. package/dist/agent/act.d.ts +22 -0
  4. package/dist/agent/act.d.ts.map +1 -0
  5. package/dist/agent/act.js +88 -0
  6. package/dist/agent/act.js.map +1 -0
  7. package/dist/agent/controller.d.ts +34 -0
  8. package/dist/agent/controller.d.ts.map +1 -0
  9. package/dist/agent/controller.js +90 -0
  10. package/dist/agent/controller.js.map +1 -0
  11. package/dist/agent/events.d.ts +27 -0
  12. package/dist/agent/events.d.ts.map +1 -0
  13. package/dist/agent/events.js +31 -0
  14. package/dist/agent/events.js.map +1 -0
  15. package/dist/agent/index.d.ts +17 -0
  16. package/dist/agent/index.d.ts.map +1 -0
  17. package/dist/agent/index.js +15 -0
  18. package/dist/agent/index.js.map +1 -0
  19. package/dist/agent/perceive.d.ts +21 -0
  20. package/dist/agent/perceive.d.ts.map +1 -0
  21. package/dist/agent/perceive.js +386 -0
  22. package/dist/agent/perceive.js.map +1 -0
  23. package/dist/agent/types.d.ts +436 -0
  24. package/dist/agent/types.d.ts.map +1 -0
  25. package/dist/agent/types.js +63 -0
  26. package/dist/agent/types.js.map +1 -0
  27. package/dist/bitflag.d.ts +199 -0
  28. package/dist/bitflag.d.ts.map +1 -0
  29. package/dist/bitflag.js +478 -0
  30. package/dist/bitflag.js.map +1 -0
  31. package/dist/cmd.d.ts +164 -0
  32. package/dist/cmd.d.ts.map +1 -0
  33. package/dist/cmd.js +350 -0
  34. package/dist/cmd.js.map +1 -0
  35. package/dist/color.d.ts +104 -0
  36. package/dist/color.d.ts.map +1 -0
  37. package/dist/color.js +240 -0
  38. package/dist/color.js.map +1 -0
  39. package/dist/combat/brand-slay.d.ts +119 -0
  40. package/dist/combat/brand-slay.d.ts.map +1 -0
  41. package/dist/combat/brand-slay.js +264 -0
  42. package/dist/combat/brand-slay.js.map +1 -0
  43. package/dist/combat/hit.d.ts +198 -0
  44. package/dist/combat/hit.d.ts.map +1 -0
  45. package/dist/combat/hit.js +310 -0
  46. package/dist/combat/hit.js.map +1 -0
  47. package/dist/combat/index.d.ts +15 -0
  48. package/dist/combat/index.d.ts.map +1 -0
  49. package/dist/combat/index.js +15 -0
  50. package/dist/combat/index.js.map +1 -0
  51. package/dist/combat/melee.d.ts +261 -0
  52. package/dist/combat/melee.d.ts.map +1 -0
  53. package/dist/combat/melee.js +402 -0
  54. package/dist/combat/melee.js.map +1 -0
  55. package/dist/combat/mon-melee.d.ts +233 -0
  56. package/dist/combat/mon-melee.d.ts.map +1 -0
  57. package/dist/combat/mon-melee.js +883 -0
  58. package/dist/combat/mon-melee.js.map +1 -0
  59. package/dist/combat/ranged.d.ts +89 -0
  60. package/dist/combat/ranged.d.ts.map +1 -0
  61. package/dist/combat/ranged.js +253 -0
  62. package/dist/combat/ranged.js.map +1 -0
  63. package/dist/constants.d.ts +134 -0
  64. package/dist/constants.d.ts.map +1 -0
  65. package/dist/constants.js +171 -0
  66. package/dist/constants.js.map +1 -0
  67. package/dist/dice.d.ts +91 -0
  68. package/dist/dice.d.ts.map +1 -0
  69. package/dist/dice.js +424 -0
  70. package/dist/dice.js.map +1 -0
  71. package/dist/effects/effect-choice.d.ts +15 -0
  72. package/dist/effects/effect-choice.d.ts.map +1 -0
  73. package/dist/effects/effect-choice.js +18 -0
  74. package/dist/effects/effect-choice.js.map +1 -0
  75. package/dist/effects/effect-info.d.ts +124 -0
  76. package/dist/effects/effect-info.d.ts.map +1 -0
  77. package/dist/effects/effect-info.js +706 -0
  78. package/dist/effects/effect-info.js.map +1 -0
  79. package/dist/effects/effect.d.ts +165 -0
  80. package/dist/effects/effect.d.ts.map +1 -0
  81. package/dist/effects/effect.js +452 -0
  82. package/dist/effects/effect.js.map +1 -0
  83. package/dist/effects/handlers.d.ts +70 -0
  84. package/dist/effects/handlers.d.ts.map +1 -0
  85. package/dist/effects/handlers.js +394 -0
  86. package/dist/effects/handlers.js.map +1 -0
  87. package/dist/effects/interpreter.d.ts +291 -0
  88. package/dist/effects/interpreter.d.ts.map +1 -0
  89. package/dist/effects/interpreter.js +325 -0
  90. package/dist/effects/interpreter.js.map +1 -0
  91. package/dist/events.d.ts +188 -0
  92. package/dist/events.d.ts.map +1 -0
  93. package/dist/events.js +138 -0
  94. package/dist/events.js.map +1 -0
  95. package/dist/expression.d.ts +83 -0
  96. package/dist/expression.d.ts.map +1 -0
  97. package/dist/expression.js +333 -0
  98. package/dist/expression.js.map +1 -0
  99. package/dist/game/cave-cmd.d.ts +195 -0
  100. package/dist/game/cave-cmd.d.ts.map +1 -0
  101. package/dist/game/cave-cmd.js +968 -0
  102. package/dist/game/cave-cmd.js.map +1 -0
  103. package/dist/game/char-sheet.d.ts +189 -0
  104. package/dist/game/char-sheet.d.ts.map +1 -0
  105. package/dist/game/char-sheet.js +381 -0
  106. package/dist/game/char-sheet.js.map +1 -0
  107. package/dist/game/chest.d.ts +94 -0
  108. package/dist/game/chest.d.ts.map +1 -0
  109. package/dist/game/chest.js +267 -0
  110. package/dist/game/chest.js.map +1 -0
  111. package/dist/game/context.d.ts +970 -0
  112. package/dist/game/context.d.ts.map +1 -0
  113. package/dist/game/context.js +353 -0
  114. package/dist/game/context.js.map +1 -0
  115. package/dist/game/curse-tick.d.ts +35 -0
  116. package/dist/game/curse-tick.d.ts.map +1 -0
  117. package/dist/game/curse-tick.js +100 -0
  118. package/dist/game/curse-tick.js.map +1 -0
  119. package/dist/game/describe.d.ts +20 -0
  120. package/dist/game/describe.d.ts.map +1 -0
  121. package/dist/game/describe.js +45 -0
  122. package/dist/game/describe.js.map +1 -0
  123. package/dist/game/display.d.ts +196 -0
  124. package/dist/game/display.d.ts.map +1 -0
  125. package/dist/game/display.js +684 -0
  126. package/dist/game/display.js.map +1 -0
  127. package/dist/game/dump-level.d.ts +37 -0
  128. package/dist/game/dump-level.d.ts.map +1 -0
  129. package/dist/game/dump-level.js +110 -0
  130. package/dist/game/dump-level.js.map +1 -0
  131. package/dist/game/effect-attack.d.ts +36 -0
  132. package/dist/game/effect-attack.d.ts.map +1 -0
  133. package/dist/game/effect-attack.js +694 -0
  134. package/dist/game/effect-attack.js.map +1 -0
  135. package/dist/game/effect-detect.d.ts +34 -0
  136. package/dist/game/effect-detect.d.ts.map +1 -0
  137. package/dist/game/effect-detect.js +456 -0
  138. package/dist/game/effect-detect.js.map +1 -0
  139. package/dist/game/effect-env.d.ts +77 -0
  140. package/dist/game/effect-env.d.ts.map +1 -0
  141. package/dist/game/effect-env.js +166 -0
  142. package/dist/game/effect-env.js.map +1 -0
  143. package/dist/game/effect-game-env.d.ts +76 -0
  144. package/dist/game/effect-game-env.d.ts.map +1 -0
  145. package/dist/game/effect-game-env.js +23 -0
  146. package/dist/game/effect-game-env.js.map +1 -0
  147. package/dist/game/effect-general.d.ts +98 -0
  148. package/dist/game/effect-general.d.ts.map +1 -0
  149. package/dist/game/effect-general.js +914 -0
  150. package/dist/game/effect-general.js.map +1 -0
  151. package/dist/game/effect-item.d.ts +124 -0
  152. package/dist/game/effect-item.d.ts.map +1 -0
  153. package/dist/game/effect-item.js +789 -0
  154. package/dist/game/effect-item.js.map +1 -0
  155. package/dist/game/effect-melee.d.ts +43 -0
  156. package/dist/game/effect-melee.d.ts.map +1 -0
  157. package/dist/game/effect-melee.js +477 -0
  158. package/dist/game/effect-melee.js.map +1 -0
  159. package/dist/game/effect-mon-origin.d.ts +67 -0
  160. package/dist/game/effect-mon-origin.d.ts.map +1 -0
  161. package/dist/game/effect-mon-origin.js +164 -0
  162. package/dist/game/effect-mon-origin.js.map +1 -0
  163. package/dist/game/effect-monster.d.ts +29 -0
  164. package/dist/game/effect-monster.d.ts.map +1 -0
  165. package/dist/game/effect-monster.js +281 -0
  166. package/dist/game/effect-monster.js.map +1 -0
  167. package/dist/game/effect-summon.d.ts +37 -0
  168. package/dist/game/effect-summon.d.ts.map +1 -0
  169. package/dist/game/effect-summon.js +113 -0
  170. package/dist/game/effect-summon.js.map +1 -0
  171. package/dist/game/effect-teleport.d.ts +130 -0
  172. package/dist/game/effect-teleport.d.ts.map +1 -0
  173. package/dist/game/effect-teleport.js +529 -0
  174. package/dist/game/effect-teleport.js.map +1 -0
  175. package/dist/game/effect-terrain.d.ts +91 -0
  176. package/dist/game/effect-terrain.d.ts.map +1 -0
  177. package/dist/game/effect-terrain.js +812 -0
  178. package/dist/game/effect-terrain.js.map +1 -0
  179. package/dist/game/energy.d.ts +45 -0
  180. package/dist/game/energy.d.ts.map +1 -0
  181. package/dist/game/energy.js +51 -0
  182. package/dist/game/energy.js.map +1 -0
  183. package/dist/game/equip-cmp.d.ts +138 -0
  184. package/dist/game/equip-cmp.d.ts.map +1 -0
  185. package/dist/game/equip-cmp.js +363 -0
  186. package/dist/game/equip-cmp.js.map +1 -0
  187. package/dist/game/floor.d.ts +182 -0
  188. package/dist/game/floor.d.ts.map +1 -0
  189. package/dist/game/floor.js +435 -0
  190. package/dist/game/floor.js.map +1 -0
  191. package/dist/game/gear.d.ts +408 -0
  192. package/dist/game/gear.d.ts.map +1 -0
  193. package/dist/game/gear.js +1037 -0
  194. package/dist/game/gear.js.map +1 -0
  195. package/dist/game/harness.d.ts +61 -0
  196. package/dist/game/harness.d.ts.map +1 -0
  197. package/dist/game/harness.js +198 -0
  198. package/dist/game/harness.js.map +1 -0
  199. package/dist/game/history.d.ts +49 -0
  200. package/dist/game/history.d.ts.map +1 -0
  201. package/dist/game/history.js +55 -0
  202. package/dist/game/history.js.map +1 -0
  203. package/dist/game/ignore-cmd.d.ts +42 -0
  204. package/dist/game/ignore-cmd.d.ts.map +1 -0
  205. package/dist/game/ignore-cmd.js +59 -0
  206. package/dist/game/ignore-cmd.js.map +1 -0
  207. package/dist/game/inscription-confirm.d.ts +80 -0
  208. package/dist/game/inscription-confirm.d.ts.map +1 -0
  209. package/dist/game/inscription-confirm.js +95 -0
  210. package/dist/game/inscription-confirm.js.map +1 -0
  211. package/dist/game/known.d.ts +297 -0
  212. package/dist/game/known.d.ts.map +1 -0
  213. package/dist/game/known.js +849 -0
  214. package/dist/game/known.js.map +1 -0
  215. package/dist/game/loop.d.ts +139 -0
  216. package/dist/game/loop.d.ts.map +1 -0
  217. package/dist/game/loop.js +631 -0
  218. package/dist/game/loop.js.map +1 -0
  219. package/dist/game/lore-color.d.ts +25 -0
  220. package/dist/game/lore-color.d.ts.map +1 -0
  221. package/dist/game/lore-color.js +95 -0
  222. package/dist/game/lore-color.js.map +1 -0
  223. package/dist/game/mon-cast.d.ts +150 -0
  224. package/dist/game/mon-cast.d.ts.map +1 -0
  225. package/dist/game/mon-cast.js +301 -0
  226. package/dist/game/mon-cast.js.map +1 -0
  227. package/dist/game/mon-cmd.d.ts +51 -0
  228. package/dist/game/mon-cmd.d.ts.map +1 -0
  229. package/dist/game/mon-cmd.js +670 -0
  230. package/dist/game/mon-cmd.js.map +1 -0
  231. package/dist/game/mon-death.d.ts +143 -0
  232. package/dist/game/mon-death.d.ts.map +1 -0
  233. package/dist/game/mon-death.js +328 -0
  234. package/dist/game/mon-death.js.map +1 -0
  235. package/dist/game/mon-group.d.ts +81 -0
  236. package/dist/game/mon-group.d.ts.map +1 -0
  237. package/dist/game/mon-group.js +323 -0
  238. package/dist/game/mon-group.js.map +1 -0
  239. package/dist/game/mon-list.d.ts +85 -0
  240. package/dist/game/mon-list.d.ts.map +1 -0
  241. package/dist/game/mon-list.js +154 -0
  242. package/dist/game/mon-list.js.map +1 -0
  243. package/dist/game/mon-message.d.ts +100 -0
  244. package/dist/game/mon-message.d.ts.map +1 -0
  245. package/dist/game/mon-message.js +336 -0
  246. package/dist/game/mon-message.js.map +1 -0
  247. package/dist/game/mon-place.d.ts +226 -0
  248. package/dist/game/mon-place.d.ts.map +1 -0
  249. package/dist/game/mon-place.js +642 -0
  250. package/dist/game/mon-place.js.map +1 -0
  251. package/dist/game/mon-ranged.d.ts +87 -0
  252. package/dist/game/mon-ranged.d.ts.map +1 -0
  253. package/dist/game/mon-ranged.js +353 -0
  254. package/dist/game/mon-ranged.js.map +1 -0
  255. package/dist/game/mon-shape.d.ts +43 -0
  256. package/dist/game/mon-shape.d.ts.map +1 -0
  257. package/dist/game/mon-shape.js +115 -0
  258. package/dist/game/mon-shape.js.map +1 -0
  259. package/dist/game/mon-side.d.ts +63 -0
  260. package/dist/game/mon-side.d.ts.map +1 -0
  261. package/dist/game/mon-side.js +403 -0
  262. package/dist/game/mon-side.js.map +1 -0
  263. package/dist/game/monster-turn.d.ts +195 -0
  264. package/dist/game/monster-turn.d.ts.map +1 -0
  265. package/dist/game/monster-turn.js +1560 -0
  266. package/dist/game/monster-turn.js.map +1 -0
  267. package/dist/game/obj-cmd.d.ts +453 -0
  268. package/dist/game/obj-cmd.d.ts.map +1 -0
  269. package/dist/game/obj-cmd.js +1530 -0
  270. package/dist/game/obj-cmd.js.map +1 -0
  271. package/dist/game/obj-list.d.ts +92 -0
  272. package/dist/game/obj-list.d.ts.map +1 -0
  273. package/dist/game/obj-list.js +251 -0
  274. package/dist/game/obj-list.js.map +1 -0
  275. package/dist/game/object-inspect.d.ts +37 -0
  276. package/dist/game/object-inspect.d.ts.map +1 -0
  277. package/dist/game/object-inspect.js +106 -0
  278. package/dist/game/object-inspect.js.map +1 -0
  279. package/dist/game/pickup.d.ts +126 -0
  280. package/dist/game/pickup.d.ts.map +1 -0
  281. package/dist/game/pickup.js +367 -0
  282. package/dist/game/pickup.js.map +1 -0
  283. package/dist/game/player-path.d.ts +133 -0
  284. package/dist/game/player-path.d.ts.map +1 -0
  285. package/dist/game/player-path.js +1268 -0
  286. package/dist/game/player-path.js.map +1 -0
  287. package/dist/game/player-side.d.ts +59 -0
  288. package/dist/game/player-side.d.ts.map +1 -0
  289. package/dist/game/player-side.js +506 -0
  290. package/dist/game/player-side.js.map +1 -0
  291. package/dist/game/player-turn.d.ts +161 -0
  292. package/dist/game/player-turn.d.ts.map +1 -0
  293. package/dist/game/player-turn.js +739 -0
  294. package/dist/game/player-turn.js.map +1 -0
  295. package/dist/game/project-cast.d.ts +268 -0
  296. package/dist/game/project-cast.d.ts.map +1 -0
  297. package/dist/game/project-cast.js +496 -0
  298. package/dist/game/project-cast.js.map +1 -0
  299. package/dist/game/project-feat.d.ts +47 -0
  300. package/dist/game/project-feat.d.ts.map +1 -0
  301. package/dist/game/project-feat.js +350 -0
  302. package/dist/game/project-feat.js.map +1 -0
  303. package/dist/game/project-monster.d.ts +106 -0
  304. package/dist/game/project-monster.d.ts.map +1 -0
  305. package/dist/game/project-monster.js +272 -0
  306. package/dist/game/project-monster.js.map +1 -0
  307. package/dist/game/project-obj.d.ts +60 -0
  308. package/dist/game/project-obj.d.ts.map +1 -0
  309. package/dist/game/project-obj.js +245 -0
  310. package/dist/game/project-obj.js.map +1 -0
  311. package/dist/game/project-player.d.ts +120 -0
  312. package/dist/game/project-player.d.ts.map +1 -0
  313. package/dist/game/project-player.js +108 -0
  314. package/dist/game/project-player.js.map +1 -0
  315. package/dist/game/quest.d.ts +101 -0
  316. package/dist/game/quest.d.ts.map +1 -0
  317. package/dist/game/quest.js +192 -0
  318. package/dist/game/quest.js.map +1 -0
  319. package/dist/game/ranged-cmd.d.ts +32 -0
  320. package/dist/game/ranged-cmd.d.ts.map +1 -0
  321. package/dist/game/ranged-cmd.js +372 -0
  322. package/dist/game/ranged-cmd.js.map +1 -0
  323. package/dist/game/scheduler.d.ts +47 -0
  324. package/dist/game/scheduler.d.ts.map +1 -0
  325. package/dist/game/scheduler.js +141 -0
  326. package/dist/game/scheduler.js.map +1 -0
  327. package/dist/game/spell-cmd.d.ts +104 -0
  328. package/dist/game/spell-cmd.d.ts.map +1 -0
  329. package/dist/game/spell-cmd.js +320 -0
  330. package/dist/game/spell-cmd.js.map +1 -0
  331. package/dist/game/spoil.d.ts +28 -0
  332. package/dist/game/spoil.d.ts.map +1 -0
  333. package/dist/game/spoil.js +435 -0
  334. package/dist/game/spoil.js.map +1 -0
  335. package/dist/game/steal.d.ts +35 -0
  336. package/dist/game/steal.d.ts.map +1 -0
  337. package/dist/game/steal.js +145 -0
  338. package/dist/game/steal.js.map +1 -0
  339. package/dist/game/take-hit-hooks.d.ts +52 -0
  340. package/dist/game/take-hit-hooks.d.ts.map +1 -0
  341. package/dist/game/take-hit-hooks.js +127 -0
  342. package/dist/game/take-hit-hooks.js.map +1 -0
  343. package/dist/game/target-loop.d.ts +119 -0
  344. package/dist/game/target-loop.d.ts.map +1 -0
  345. package/dist/game/target-loop.js +408 -0
  346. package/dist/game/target-loop.js.map +1 -0
  347. package/dist/game/target.d.ts +133 -0
  348. package/dist/game/target.d.ts.map +1 -0
  349. package/dist/game/target.js +353 -0
  350. package/dist/game/target.js.map +1 -0
  351. package/dist/game/thrust.d.ts +37 -0
  352. package/dist/game/thrust.d.ts.map +1 -0
  353. package/dist/game/thrust.js +155 -0
  354. package/dist/game/thrust.js.map +1 -0
  355. package/dist/game/trap.d.ts +218 -0
  356. package/dist/game/trap.d.ts.map +1 -0
  357. package/dist/game/trap.js +618 -0
  358. package/dist/game/trap.js.map +1 -0
  359. package/dist/game/ui-entry.d.ts +282 -0
  360. package/dist/game/ui-entry.d.ts.map +1 -0
  361. package/dist/game/ui-entry.js +1838 -0
  362. package/dist/game/ui-entry.js.map +1 -0
  363. package/dist/game/wizard.d.ts +654 -0
  364. package/dist/game/wizard.d.ts.map +1 -0
  365. package/dist/game/wizard.js +1323 -0
  366. package/dist/game/wizard.js.map +1 -0
  367. package/dist/game/world.d.ts +183 -0
  368. package/dist/game/world.d.ts.map +1 -0
  369. package/dist/game/world.js +685 -0
  370. package/dist/game/world.js.map +1 -0
  371. package/dist/gen/cave.d.ts +247 -0
  372. package/dist/gen/cave.d.ts.map +1 -0
  373. package/dist/gen/cave.js +2406 -0
  374. package/dist/gen/cave.js.map +1 -0
  375. package/dist/gen/gen-monster.d.ts +151 -0
  376. package/dist/gen/gen-monster.d.ts.map +1 -0
  377. package/dist/gen/gen-monster.js +473 -0
  378. package/dist/gen/gen-monster.js.map +1 -0
  379. package/dist/gen/generate.d.ts +240 -0
  380. package/dist/gen/generate.d.ts.map +1 -0
  381. package/dist/gen/generate.js +358 -0
  382. package/dist/gen/generate.js.map +1 -0
  383. package/dist/gen/room.d.ts +160 -0
  384. package/dist/gen/room.d.ts.map +1 -0
  385. package/dist/gen/room.js +1910 -0
  386. package/dist/gen/room.js.map +1 -0
  387. package/dist/gen/util.d.ts +466 -0
  388. package/dist/gen/util.d.ts.map +1 -0
  389. package/dist/gen/util.js +1553 -0
  390. package/dist/gen/util.js.map +1 -0
  391. package/dist/generated/dun-profiles.d.ts +44 -0
  392. package/dist/generated/dun-profiles.d.ts.map +1 -0
  393. package/dist/generated/dun-profiles.js +28 -0
  394. package/dist/generated/dun-profiles.js.map +1 -0
  395. package/dist/generated/effects.d.ts +1021 -0
  396. package/dist/generated/effects.d.ts.map +1 -0
  397. package/dist/generated/effects.js +239 -0
  398. package/dist/generated/effects.js.map +1 -0
  399. package/dist/generated/elements.d.ts +83 -0
  400. package/dist/generated/elements.d.ts.map +1 -0
  401. package/dist/generated/elements.js +60 -0
  402. package/dist/generated/elements.js.map +1 -0
  403. package/dist/generated/equip-slots.d.ts +104 -0
  404. package/dist/generated/equip-slots.d.ts.map +1 -0
  405. package/dist/generated/equip-slots.js +34 -0
  406. package/dist/generated/equip-slots.js.map +1 -0
  407. package/dist/generated/history-types.d.ts +52 -0
  408. package/dist/generated/history-types.d.ts.map +1 -0
  409. package/dist/generated/history-types.js +32 -0
  410. package/dist/generated/history-types.js.map +1 -0
  411. package/dist/generated/ignore-types.d.ts +116 -0
  412. package/dist/generated/ignore-types.d.ts.map +1 -0
  413. package/dist/generated/ignore-types.js +64 -0
  414. package/dist/generated/ignore-types.js.map +1 -0
  415. package/dist/generated/index.d.ts +32 -0
  416. package/dist/generated/index.d.ts.map +1 -0
  417. package/dist/generated/index.js +33 -0
  418. package/dist/generated/index.js.map +1 -0
  419. package/dist/generated/kind-flags.d.ts +72 -0
  420. package/dist/generated/kind-flags.d.ts.map +1 -0
  421. package/dist/generated/kind-flags.js +42 -0
  422. package/dist/generated/kind-flags.js.map +1 -0
  423. package/dist/generated/message.d.ts +624 -0
  424. package/dist/generated/message.d.ts.map +1 -0
  425. package/dist/generated/message.js +318 -0
  426. package/dist/generated/message.js.map +1 -0
  427. package/dist/generated/mon-message.d.ts +386 -0
  428. package/dist/generated/mon-message.d.ts.map +1 -0
  429. package/dist/generated/mon-message.js +136 -0
  430. package/dist/generated/mon-message.js.map +1 -0
  431. package/dist/generated/mon-race-flags.d.ts +433 -0
  432. package/dist/generated/mon-race-flags.d.ts.map +1 -0
  433. package/dist/generated/mon-race-flags.js +180 -0
  434. package/dist/generated/mon-race-flags.js.map +1 -0
  435. package/dist/generated/mon-spells.d.ts +380 -0
  436. package/dist/generated/mon-spells.d.ts.map +1 -0
  437. package/dist/generated/mon-spells.js +196 -0
  438. package/dist/generated/mon-spells.js.map +1 -0
  439. package/dist/generated/mon-temp-flags.d.ts +52 -0
  440. package/dist/generated/mon-temp-flags.d.ts.map +1 -0
  441. package/dist/generated/mon-temp-flags.js +32 -0
  442. package/dist/generated/mon-temp-flags.js.map +1 -0
  443. package/dist/generated/mon-timed.d.ts +118 -0
  444. package/dist/generated/mon-timed.d.ts.map +1 -0
  445. package/dist/generated/mon-timed.js +32 -0
  446. package/dist/generated/mon-timed.js.map +1 -0
  447. package/dist/generated/object-flags.d.ts +169 -0
  448. package/dist/generated/object-flags.d.ts.map +1 -0
  449. package/dist/generated/object-flags.js +93 -0
  450. package/dist/generated/object-flags.js.map +1 -0
  451. package/dist/generated/object-modifiers.d.ts +50 -0
  452. package/dist/generated/object-modifiers.d.ts.map +1 -0
  453. package/dist/generated/object-modifiers.js +41 -0
  454. package/dist/generated/object-modifiers.js.map +1 -0
  455. package/dist/generated/options.d.ts +284 -0
  456. package/dist/generated/options.d.ts.map +1 -0
  457. package/dist/generated/options.js +102 -0
  458. package/dist/generated/options.js.map +1 -0
  459. package/dist/generated/origins.d.ts +138 -0
  460. package/dist/generated/origins.d.ts.map +1 -0
  461. package/dist/generated/origins.js +62 -0
  462. package/dist/generated/origins.js.map +1 -0
  463. package/dist/generated/parser-errors.d.ts +264 -0
  464. package/dist/generated/parser-errors.d.ts.map +1 -0
  465. package/dist/generated/parser-errors.js +138 -0
  466. package/dist/generated/parser-errors.js.map +1 -0
  467. package/dist/generated/player-flags.d.ts +62 -0
  468. package/dist/generated/player-flags.d.ts.map +1 -0
  469. package/dist/generated/player-flags.js +46 -0
  470. package/dist/generated/player-flags.js.map +1 -0
  471. package/dist/generated/player-timed.d.ts +273 -0
  472. package/dist/generated/player-timed.d.ts.map +1 -0
  473. package/dist/generated/player-timed.js +116 -0
  474. package/dist/generated/player-timed.js.map +1 -0
  475. package/dist/generated/projections.d.ts +130 -0
  476. package/dist/generated/projections.d.ts.map +1 -0
  477. package/dist/generated/projections.js +101 -0
  478. package/dist/generated/projections.js.map +1 -0
  479. package/dist/generated/randart-properties.d.ts +388 -0
  480. package/dist/generated/randart-properties.d.ts.map +1 -0
  481. package/dist/generated/randart-properties.js +200 -0
  482. package/dist/generated/randart-properties.js.map +1 -0
  483. package/dist/generated/room-flags.d.ts +21 -0
  484. package/dist/generated/room-flags.d.ts.map +1 -0
  485. package/dist/generated/room-flags.js +19 -0
  486. package/dist/generated/room-flags.js.map +1 -0
  487. package/dist/generated/rooms.d.ts +122 -0
  488. package/dist/generated/rooms.d.ts.map +1 -0
  489. package/dist/generated/rooms.js +48 -0
  490. package/dist/generated/rooms.js.map +1 -0
  491. package/dist/generated/square-flags.d.ts +96 -0
  492. package/dist/generated/square-flags.d.ts.map +1 -0
  493. package/dist/generated/square-flags.js +54 -0
  494. package/dist/generated/square-flags.js.map +1 -0
  495. package/dist/generated/stats.d.ts +23 -0
  496. package/dist/generated/stats.d.ts.map +1 -0
  497. package/dist/generated/stats.js +20 -0
  498. package/dist/generated/stats.js.map +1 -0
  499. package/dist/generated/terrain-flags.d.ts +136 -0
  500. package/dist/generated/terrain-flags.d.ts.map +1 -0
  501. package/dist/generated/terrain-flags.js +74 -0
  502. package/dist/generated/terrain-flags.js.map +1 -0
  503. package/dist/generated/terrain.d.ts +83 -0
  504. package/dist/generated/terrain.d.ts.map +1 -0
  505. package/dist/generated/terrain.js +60 -0
  506. package/dist/generated/terrain.js.map +1 -0
  507. package/dist/generated/trap-flags.d.ts +68 -0
  508. package/dist/generated/trap-flags.d.ts.map +1 -0
  509. package/dist/generated/trap-flags.js +40 -0
  510. package/dist/generated/trap-flags.js.map +1 -0
  511. package/dist/generated/tvals.d.ts +152 -0
  512. package/dist/generated/tvals.d.ts.map +1 -0
  513. package/dist/generated/tvals.js +82 -0
  514. package/dist/generated/tvals.js.map +1 -0
  515. package/dist/generated/ui-entry-renderers.d.ts +62 -0
  516. package/dist/generated/ui-entry-renderers.d.ts.map +1 -0
  517. package/dist/generated/ui-entry-renderers.js +22 -0
  518. package/dist/generated/ui-entry-renderers.js.map +1 -0
  519. package/dist/guard.d.ts +43 -0
  520. package/dist/guard.d.ts.map +1 -0
  521. package/dist/guard.js +65 -0
  522. package/dist/guard.js.map +1 -0
  523. package/dist/host/args.d.ts +129 -0
  524. package/dist/host/args.d.ts.map +1 -0
  525. package/dist/host/args.js +251 -0
  526. package/dist/host/args.js.map +1 -0
  527. package/dist/host/bridge.d.ts +61 -0
  528. package/dist/host/bridge.d.ts.map +1 -0
  529. package/dist/host/bridge.js +176 -0
  530. package/dist/host/bridge.js.map +1 -0
  531. package/dist/host/index.d.ts +18 -0
  532. package/dist/host/index.d.ts.map +1 -0
  533. package/dist/host/index.js +18 -0
  534. package/dist/host/index.js.map +1 -0
  535. package/dist/host/io.d.ts +169 -0
  536. package/dist/host/io.d.ts.map +1 -0
  537. package/dist/host/io.js +137 -0
  538. package/dist/host/io.js.map +1 -0
  539. package/dist/host/memory.d.ts +56 -0
  540. package/dist/host/memory.d.ts.map +1 -0
  541. package/dist/host/memory.js +105 -0
  542. package/dist/host/memory.js.map +1 -0
  543. package/dist/host/raw.d.ts +114 -0
  544. package/dist/host/raw.d.ts.map +1 -0
  545. package/dist/host/raw.js +107 -0
  546. package/dist/host/raw.js.map +1 -0
  547. package/dist/index.d.ts +180 -0
  548. package/dist/index.d.ts.map +1 -0
  549. package/dist/index.js +222 -0
  550. package/dist/index.js.map +1 -0
  551. package/dist/loc.d.ts +63 -0
  552. package/dist/loc.d.ts.map +1 -0
  553. package/dist/loc.js +128 -0
  554. package/dist/loc.js.map +1 -0
  555. package/dist/mod/hooks.d.ts +204 -0
  556. package/dist/mod/hooks.d.ts.map +1 -0
  557. package/dist/mod/hooks.js +161 -0
  558. package/dist/mod/hooks.js.map +1 -0
  559. package/dist/mod/ids.d.ts +160 -0
  560. package/dist/mod/ids.d.ts.map +1 -0
  561. package/dist/mod/ids.js +343 -0
  562. package/dist/mod/ids.js.map +1 -0
  563. package/dist/mod/registry-host.d.ts +156 -0
  564. package/dist/mod/registry-host.d.ts.map +1 -0
  565. package/dist/mod/registry-host.js +142 -0
  566. package/dist/mod/registry-host.js.map +1 -0
  567. package/dist/mod/save-blocks.d.ts +201 -0
  568. package/dist/mod/save-blocks.d.ts.map +1 -0
  569. package/dist/mod/save-blocks.js +640 -0
  570. package/dist/mod/save-blocks.js.map +1 -0
  571. package/dist/mod/vocabulary.d.ts +104 -0
  572. package/dist/mod/vocabulary.d.ts.map +1 -0
  573. package/dist/mod/vocabulary.js +0 -0
  574. package/dist/mod/vocabulary.js.map +1 -0
  575. package/dist/mon/bind.d.ts +266 -0
  576. package/dist/mon/bind.d.ts.map +1 -0
  577. package/dist/mon/bind.js +610 -0
  578. package/dist/mon/bind.js.map +1 -0
  579. package/dist/mon/desc.d.ts +72 -0
  580. package/dist/mon/desc.d.ts.map +1 -0
  581. package/dist/mon/desc.js +237 -0
  582. package/dist/mon/desc.js.map +1 -0
  583. package/dist/mon/knowledge-groups.d.ts +61 -0
  584. package/dist/mon/knowledge-groups.d.ts.map +1 -0
  585. package/dist/mon/knowledge-groups.js +121 -0
  586. package/dist/mon/knowledge-groups.js.map +1 -0
  587. package/dist/mon/lore-describe.d.ts +216 -0
  588. package/dist/mon/lore-describe.d.ts.map +1 -0
  589. package/dist/mon/lore-describe.js +1105 -0
  590. package/dist/mon/lore-describe.js.map +1 -0
  591. package/dist/mon/lore.d.ts +117 -0
  592. package/dist/mon/lore.d.ts.map +1 -0
  593. package/dist/mon/lore.js +221 -0
  594. package/dist/mon/lore.js.map +1 -0
  595. package/dist/mon/make.d.ts +147 -0
  596. package/dist/mon/make.d.ts.map +1 -0
  597. package/dist/mon/make.js +353 -0
  598. package/dist/mon/make.js.map +1 -0
  599. package/dist/mon/monster.d.ts +94 -0
  600. package/dist/mon/monster.d.ts.map +1 -0
  601. package/dist/mon/monster.js +90 -0
  602. package/dist/mon/monster.js.map +1 -0
  603. package/dist/mon/predicate.d.ts +87 -0
  604. package/dist/mon/predicate.d.ts.map +1 -0
  605. package/dist/mon/predicate.js +190 -0
  606. package/dist/mon/predicate.js.map +1 -0
  607. package/dist/mon/project-mon.d.ts +129 -0
  608. package/dist/mon/project-mon.d.ts.map +1 -0
  609. package/dist/mon/project-mon.js +652 -0
  610. package/dist/mon/project-mon.js.map +1 -0
  611. package/dist/mon/spell.d.ts +155 -0
  612. package/dist/mon/spell.d.ts.map +1 -0
  613. package/dist/mon/spell.js +355 -0
  614. package/dist/mon/spell.js.map +1 -0
  615. package/dist/mon/steal.d.ts +112 -0
  616. package/dist/mon/steal.d.ts.map +1 -0
  617. package/dist/mon/steal.js +163 -0
  618. package/dist/mon/steal.js.map +1 -0
  619. package/dist/mon/summon.d.ts +62 -0
  620. package/dist/mon/summon.d.ts.map +1 -0
  621. package/dist/mon/summon.js +111 -0
  622. package/dist/mon/summon.js.map +1 -0
  623. package/dist/mon/take-hit.d.ts +75 -0
  624. package/dist/mon/take-hit.d.ts.map +1 -0
  625. package/dist/mon/take-hit.js +122 -0
  626. package/dist/mon/take-hit.js.map +1 -0
  627. package/dist/mon/timed.d.ts +74 -0
  628. package/dist/mon/timed.d.ts.map +1 -0
  629. package/dist/mon/timed.js +219 -0
  630. package/dist/mon/timed.js.map +1 -0
  631. package/dist/mon/types.d.ts +252 -0
  632. package/dist/mon/types.d.ts.map +1 -0
  633. package/dist/mon/types.js +46 -0
  634. package/dist/mon/types.js.map +1 -0
  635. package/dist/msg.d.ts +71 -0
  636. package/dist/msg.d.ts.map +1 -0
  637. package/dist/msg.js +126 -0
  638. package/dist/msg.js.map +1 -0
  639. package/dist/obj/artifact-fake.d.ts +39 -0
  640. package/dist/obj/artifact-fake.d.ts.map +1 -0
  641. package/dist/obj/artifact-fake.js +52 -0
  642. package/dist/obj/artifact-fake.js.map +1 -0
  643. package/dist/obj/artifact-known.d.ts +50 -0
  644. package/dist/obj/artifact-known.d.ts.map +1 -0
  645. package/dist/obj/artifact-known.js +55 -0
  646. package/dist/obj/artifact-known.js.map +1 -0
  647. package/dist/obj/bind.d.ts +219 -0
  648. package/dist/obj/bind.d.ts.map +1 -0
  649. package/dist/obj/bind.js +1239 -0
  650. package/dist/obj/bind.js.map +1 -0
  651. package/dist/obj/chest.d.ts +91 -0
  652. package/dist/obj/chest.d.ts.map +1 -0
  653. package/dist/obj/chest.js +220 -0
  654. package/dist/obj/chest.js.map +1 -0
  655. package/dist/obj/desc.d.ts +79 -0
  656. package/dist/obj/desc.d.ts.map +1 -0
  657. package/dist/obj/desc.js +602 -0
  658. package/dist/obj/desc.js.map +1 -0
  659. package/dist/obj/effects-info.d.ts +78 -0
  660. package/dist/obj/effects-info.d.ts.map +1 -0
  661. package/dist/obj/effects-info.js +240 -0
  662. package/dist/obj/effects-info.js.map +1 -0
  663. package/dist/obj/flavor.d.ts +68 -0
  664. package/dist/obj/flavor.d.ts.map +1 -0
  665. package/dist/obj/flavor.js +201 -0
  666. package/dist/obj/flavor.js.map +1 -0
  667. package/dist/obj/ignore.d.ts +143 -0
  668. package/dist/obj/ignore.d.ts.map +1 -0
  669. package/dist/obj/ignore.js +345 -0
  670. package/dist/obj/ignore.js.map +1 -0
  671. package/dist/obj/knowledge.d.ts +520 -0
  672. package/dist/obj/knowledge.d.ts.map +1 -0
  673. package/dist/obj/knowledge.js +1364 -0
  674. package/dist/obj/knowledge.js.map +1 -0
  675. package/dist/obj/known-object.d.ts +293 -0
  676. package/dist/obj/known-object.d.ts.map +1 -0
  677. package/dist/obj/known-object.js +532 -0
  678. package/dist/obj/known-object.js.map +1 -0
  679. package/dist/obj/make.d.ts +292 -0
  680. package/dist/obj/make.d.ts.map +1 -0
  681. package/dist/obj/make.js +1055 -0
  682. package/dist/obj/make.js.map +1 -0
  683. package/dist/obj/object-info.d.ts +188 -0
  684. package/dist/obj/object-info.d.ts.map +1 -0
  685. package/dist/obj/object-info.js +1407 -0
  686. package/dist/obj/object-info.js.map +1 -0
  687. package/dist/obj/object.d.ts +335 -0
  688. package/dist/obj/object.d.ts.map +1 -0
  689. package/dist/obj/object.js +1051 -0
  690. package/dist/obj/object.js.map +1 -0
  691. package/dist/obj/power.d.ts +65 -0
  692. package/dist/obj/power.d.ts.map +1 -0
  693. package/dist/obj/power.js +580 -0
  694. package/dist/obj/power.js.map +1 -0
  695. package/dist/obj/randart-build.d.ts +223 -0
  696. package/dist/obj/randart-build.d.ts.map +1 -0
  697. package/dist/obj/randart-build.js +1444 -0
  698. package/dist/obj/randart-build.js.map +1 -0
  699. package/dist/obj/randart-data.d.ts +179 -0
  700. package/dist/obj/randart-data.d.ts.map +1 -0
  701. package/dist/obj/randart-data.js +1153 -0
  702. package/dist/obj/randart-data.js.map +1 -0
  703. package/dist/obj/randart.d.ts +128 -0
  704. package/dist/obj/randart.d.ts.map +1 -0
  705. package/dist/obj/randart.js +458 -0
  706. package/dist/obj/randart.js.map +1 -0
  707. package/dist/obj/randname.d.ts +33 -0
  708. package/dist/obj/randname.d.ts.map +1 -0
  709. package/dist/obj/randname.js +116 -0
  710. package/dist/obj/randname.js.map +1 -0
  711. package/dist/obj/recharge.d.ts +28 -0
  712. package/dist/obj/recharge.d.ts.map +1 -0
  713. package/dist/obj/recharge.js +52 -0
  714. package/dist/obj/recharge.js.map +1 -0
  715. package/dist/obj/types.d.ts +584 -0
  716. package/dist/obj/types.d.ts.map +1 -0
  717. package/dist/obj/types.js +104 -0
  718. package/dist/obj/types.js.map +1 -0
  719. package/dist/obj/value.d.ts +64 -0
  720. package/dist/obj/value.d.ts.map +1 -0
  721. package/dist/obj/value.js +176 -0
  722. package/dist/obj/value.js.map +1 -0
  723. package/dist/player/abilities.d.ts +57 -0
  724. package/dist/player/abilities.d.ts.map +1 -0
  725. package/dist/player/abilities.js +118 -0
  726. package/dist/player/abilities.js.map +1 -0
  727. package/dist/player/best-digger.d.ts +42 -0
  728. package/dist/player/best-digger.d.ts.map +1 -0
  729. package/dist/player/best-digger.js +89 -0
  730. package/dist/player/best-digger.js.map +1 -0
  731. package/dist/player/bind.d.ts +270 -0
  732. package/dist/player/bind.d.ts.map +1 -0
  733. package/dist/player/bind.js +698 -0
  734. package/dist/player/bind.js.map +1 -0
  735. package/dist/player/birth.d.ts +258 -0
  736. package/dist/player/birth.d.ts.map +1 -0
  737. package/dist/player/birth.js +639 -0
  738. package/dist/player/birth.js.map +1 -0
  739. package/dist/player/calcs.d.ts +403 -0
  740. package/dist/player/calcs.d.ts.map +1 -0
  741. package/dist/player/calcs.js +1189 -0
  742. package/dist/player/calcs.js.map +1 -0
  743. package/dist/player/combat-regen.d.ts +14 -0
  744. package/dist/player/combat-regen.d.ts.map +1 -0
  745. package/dist/player/combat-regen.js +29 -0
  746. package/dist/player/combat-regen.js.map +1 -0
  747. package/dist/player/exp.d.ts +96 -0
  748. package/dist/player/exp.d.ts.map +1 -0
  749. package/dist/player/exp.js +229 -0
  750. package/dist/player/exp.js.map +1 -0
  751. package/dist/player/history.d.ts +91 -0
  752. package/dist/player/history.d.ts.map +1 -0
  753. package/dist/player/history.js +145 -0
  754. package/dist/player/history.js.map +1 -0
  755. package/dist/player/options.d.ts +123 -0
  756. package/dist/player/options.d.ts.map +1 -0
  757. package/dist/player/options.js +199 -0
  758. package/dist/player/options.js.map +1 -0
  759. package/dist/player/player.d.ts +242 -0
  760. package/dist/player/player.d.ts.map +1 -0
  761. package/dist/player/player.js +119 -0
  762. package/dist/player/player.js.map +1 -0
  763. package/dist/player/shape-lore.d.ts +56 -0
  764. package/dist/player/shape-lore.d.ts.map +1 -0
  765. package/dist/player/shape-lore.js +221 -0
  766. package/dist/player/shape-lore.js.map +1 -0
  767. package/dist/player/spell.d.ts +146 -0
  768. package/dist/player/spell.d.ts.map +1 -0
  769. package/dist/player/spell.js +519 -0
  770. package/dist/player/spell.js.map +1 -0
  771. package/dist/player/take-hit.d.ts +87 -0
  772. package/dist/player/take-hit.d.ts.map +1 -0
  773. package/dist/player/take-hit.js +99 -0
  774. package/dist/player/take-hit.js.map +1 -0
  775. package/dist/player/timed.d.ts +205 -0
  776. package/dist/player/timed.d.ts.map +1 -0
  777. package/dist/player/timed.js +310 -0
  778. package/dist/player/timed.js.map +1 -0
  779. package/dist/player/types.d.ts +390 -0
  780. package/dist/player/types.d.ts.map +1 -0
  781. package/dist/player/types.js +51 -0
  782. package/dist/player/types.js.map +1 -0
  783. package/dist/rational.d.ts +99 -0
  784. package/dist/rational.d.ts.map +1 -0
  785. package/dist/rational.js +345 -0
  786. package/dist/rational.js.map +1 -0
  787. package/dist/rng.d.ts +168 -0
  788. package/dist/rng.d.ts.map +1 -0
  789. package/dist/rng.js +404 -0
  790. package/dist/rng.js.map +1 -0
  791. package/dist/save/buffer.d.ts +120 -0
  792. package/dist/save/buffer.d.ts.map +1 -0
  793. package/dist/save/buffer.js +238 -0
  794. package/dist/save/buffer.js.map +1 -0
  795. package/dist/save/compress.d.ts +79 -0
  796. package/dist/save/compress.d.ts.map +1 -0
  797. package/dist/save/compress.js +97 -0
  798. package/dist/save/compress.js.map +1 -0
  799. package/dist/save/description.d.ts +61 -0
  800. package/dist/save/description.d.ts.map +1 -0
  801. package/dist/save/description.js +51 -0
  802. package/dist/save/description.js.map +1 -0
  803. package/dist/save/integrity.d.ts +59 -0
  804. package/dist/save/integrity.d.ts.map +1 -0
  805. package/dist/save/integrity.js +103 -0
  806. package/dist/save/integrity.js.map +1 -0
  807. package/dist/score/display.d.ts +40 -0
  808. package/dist/score/display.d.ts.map +1 -0
  809. package/dist/score/display.js +86 -0
  810. package/dist/score/display.js.map +1 -0
  811. package/dist/score/score.d.ts +166 -0
  812. package/dist/score/score.d.ts.map +1 -0
  813. package/dist/score/score.js +244 -0
  814. package/dist/score/score.js.map +1 -0
  815. package/dist/score/types.d.ts +98 -0
  816. package/dist/score/types.d.ts.map +1 -0
  817. package/dist/score/types.js +27 -0
  818. package/dist/score/types.js.map +1 -0
  819. package/dist/session/boot.d.ts +207 -0
  820. package/dist/session/boot.d.ts.map +1 -0
  821. package/dist/session/boot.js +151 -0
  822. package/dist/session/boot.js.map +1 -0
  823. package/dist/session/game.d.ts +257 -0
  824. package/dist/session/game.d.ts.map +1 -0
  825. package/dist/session/game.js +2880 -0
  826. package/dist/session/game.js.map +1 -0
  827. package/dist/session/save.d.ts +847 -0
  828. package/dist/session/save.d.ts.map +1 -0
  829. package/dist/session/save.js +1306 -0
  830. package/dist/session/save.js.map +1 -0
  831. package/dist/sound/engine.d.ts +146 -0
  832. package/dist/sound/engine.d.ts.map +1 -0
  833. package/dist/sound/engine.js +332 -0
  834. package/dist/sound/engine.js.map +1 -0
  835. package/dist/sound/index.d.ts +13 -0
  836. package/dist/sound/index.d.ts.map +1 -0
  837. package/dist/sound/index.js +13 -0
  838. package/dist/sound/index.js.map +1 -0
  839. package/dist/sound/sound-prefs-data.d.ts +10 -0
  840. package/dist/sound/sound-prefs-data.d.ts.map +1 -0
  841. package/dist/sound/sound-prefs-data.js +165 -0
  842. package/dist/sound/sound-prefs-data.js.map +1 -0
  843. package/dist/sound/types.d.ts +64 -0
  844. package/dist/sound/types.d.ts.map +1 -0
  845. package/dist/sound/types.js +23 -0
  846. package/dist/sound/types.js.map +1 -0
  847. package/dist/store/bind.d.ts +43 -0
  848. package/dist/store/bind.d.ts.map +1 -0
  849. package/dist/store/bind.js +130 -0
  850. package/dist/store/bind.js.map +1 -0
  851. package/dist/store/price.d.ts +27 -0
  852. package/dist/store/price.d.ts.map +1 -0
  853. package/dist/store/price.js +81 -0
  854. package/dist/store/price.js.map +1 -0
  855. package/dist/store/store-cmd.d.ts +51 -0
  856. package/dist/store/store-cmd.d.ts.map +1 -0
  857. package/dist/store/store-cmd.js +73 -0
  858. package/dist/store/store-cmd.js.map +1 -0
  859. package/dist/store/store.d.ts +156 -0
  860. package/dist/store/store.d.ts.map +1 -0
  861. package/dist/store/store.js +616 -0
  862. package/dist/store/store.js.map +1 -0
  863. package/dist/store/transact.d.ts +198 -0
  864. package/dist/store/transact.d.ts.map +1 -0
  865. package/dist/store/transact.js +383 -0
  866. package/dist/store/transact.js.map +1 -0
  867. package/dist/store/types.d.ts +101 -0
  868. package/dist/store/types.d.ts.map +1 -0
  869. package/dist/store/types.js +14 -0
  870. package/dist/store/types.js.map +1 -0
  871. package/dist/version.d.ts +25 -0
  872. package/dist/version.d.ts.map +1 -0
  873. package/dist/version.js +25 -0
  874. package/dist/version.js.map +1 -0
  875. package/dist/visuals/engine.d.ts +223 -0
  876. package/dist/visuals/engine.d.ts.map +1 -0
  877. package/dist/visuals/engine.js +357 -0
  878. package/dist/visuals/engine.js.map +1 -0
  879. package/dist/visuals/glyph-table.d.ts +112 -0
  880. package/dist/visuals/glyph-table.d.ts.map +1 -0
  881. package/dist/visuals/glyph-table.js +156 -0
  882. package/dist/visuals/glyph-table.js.map +1 -0
  883. package/dist/visuals/grafmode-data.d.ts +4 -0
  884. package/dist/visuals/grafmode-data.d.ts.map +1 -0
  885. package/dist/visuals/grafmode-data.js +88 -0
  886. package/dist/visuals/grafmode-data.js.map +1 -0
  887. package/dist/visuals/grafmode.d.ts +83 -0
  888. package/dist/visuals/grafmode.d.ts.map +1 -0
  889. package/dist/visuals/grafmode.js +82 -0
  890. package/dist/visuals/grafmode.js.map +1 -0
  891. package/dist/visuals/index.d.ts +19 -0
  892. package/dist/visuals/index.d.ts.map +1 -0
  893. package/dist/visuals/index.js +19 -0
  894. package/dist/visuals/index.js.map +1 -0
  895. package/dist/visuals/map-text.d.ts +79 -0
  896. package/dist/visuals/map-text.d.ts.map +1 -0
  897. package/dist/visuals/map-text.js +122 -0
  898. package/dist/visuals/map-text.js.map +1 -0
  899. package/dist/visuals/pref-expr.d.ts +56 -0
  900. package/dist/visuals/pref-expr.d.ts.map +1 -0
  901. package/dist/visuals/pref-expr.js +173 -0
  902. package/dist/visuals/pref-expr.js.map +1 -0
  903. package/dist/visuals/prefs.d.ts +280 -0
  904. package/dist/visuals/prefs.d.ts.map +1 -0
  905. package/dist/visuals/prefs.js +835 -0
  906. package/dist/visuals/prefs.js.map +1 -0
  907. package/dist/visuals/tile-prefs.d.ts +132 -0
  908. package/dist/visuals/tile-prefs.d.ts.map +1 -0
  909. package/dist/visuals/tile-prefs.js +105 -0
  910. package/dist/visuals/tile-prefs.js.map +1 -0
  911. package/dist/world/chunk.d.ts +195 -0
  912. package/dist/world/chunk.d.ts.map +1 -0
  913. package/dist/world/chunk.js +422 -0
  914. package/dist/world/chunk.js.map +1 -0
  915. package/dist/world/feature.d.ts +86 -0
  916. package/dist/world/feature.d.ts.map +1 -0
  917. package/dist/world/feature.js +153 -0
  918. package/dist/world/feature.js.map +1 -0
  919. package/dist/world/flow.d.ts +33 -0
  920. package/dist/world/flow.d.ts.map +1 -0
  921. package/dist/world/flow.js +115 -0
  922. package/dist/world/flow.js.map +1 -0
  923. package/dist/world/project.d.ts +176 -0
  924. package/dist/world/project.d.ts.map +1 -0
  925. package/dist/world/project.js +543 -0
  926. package/dist/world/project.js.map +1 -0
  927. package/dist/world/projection.d.ts +100 -0
  928. package/dist/world/projection.d.ts.map +1 -0
  929. package/dist/world/projection.js +152 -0
  930. package/dist/world/projection.js.map +1 -0
  931. package/dist/world/scatter.d.ts +25 -0
  932. package/dist/world/scatter.d.ts.map +1 -0
  933. package/dist/world/scatter.js +54 -0
  934. package/dist/world/scatter.js.map +1 -0
  935. package/dist/world/trap.d.ts +84 -0
  936. package/dist/world/trap.d.ts.map +1 -0
  937. package/dist/world/trap.js +106 -0
  938. package/dist/world/trap.js.map +1 -0
  939. package/dist/world/view.d.ts +60 -0
  940. package/dist/world/view.d.ts.map +1 -0
  941. package/dist/world/view.js +436 -0
  942. package/dist/world/view.js.map +1 -0
  943. package/dist/z-queue.d.ts +133 -0
  944. package/dist/z-queue.d.ts.map +1 -0
  945. package/dist/z-queue.js +283 -0
  946. package/dist/z-queue.js.map +1 -0
  947. package/package.json +75 -0
  948. package/src/agent/act.ts +114 -0
  949. package/src/agent/controller.ts +109 -0
  950. package/src/agent/events.ts +47 -0
  951. package/src/agent/index.ts +36 -0
  952. package/src/agent/perceive.ts +440 -0
  953. package/src/agent/types.ts +464 -0
  954. package/src/bitflag.ts +536 -0
  955. package/src/cmd.ts +547 -0
  956. package/src/color.ts +267 -0
  957. package/src/combat/brand-slay.ts +360 -0
  958. package/src/combat/hit.ts +471 -0
  959. package/src/combat/index.ts +15 -0
  960. package/src/combat/melee.ts +728 -0
  961. package/src/combat/mon-melee.ts +1153 -0
  962. package/src/combat/ranged.ts +397 -0
  963. package/src/constants.ts +324 -0
  964. package/src/dice.ts +467 -0
  965. package/src/effects/effect-choice.ts +32 -0
  966. package/src/effects/effect-info.ts +859 -0
  967. package/src/effects/effect.ts +560 -0
  968. package/src/effects/handlers.ts +452 -0
  969. package/src/effects/interpreter.ts +603 -0
  970. package/src/events.ts +317 -0
  971. package/src/expression.ts +360 -0
  972. package/src/game/cave-cmd.ts +1147 -0
  973. package/src/game/char-sheet.ts +557 -0
  974. package/src/game/chest.ts +348 -0
  975. package/src/game/context.ts +1218 -0
  976. package/src/game/curse-tick.ts +130 -0
  977. package/src/game/describe.ts +62 -0
  978. package/src/game/display.ts +838 -0
  979. package/src/game/dump-level.ts +114 -0
  980. package/src/game/effect-attack.ts +746 -0
  981. package/src/game/effect-detect.ts +542 -0
  982. package/src/game/effect-env.ts +252 -0
  983. package/src/game/effect-game-env.ts +86 -0
  984. package/src/game/effect-general.ts +1122 -0
  985. package/src/game/effect-item.ts +999 -0
  986. package/src/game/effect-melee.ts +557 -0
  987. package/src/game/effect-mon-origin.ts +196 -0
  988. package/src/game/effect-monster.ts +310 -0
  989. package/src/game/effect-summon.ts +161 -0
  990. package/src/game/effect-teleport.ts +645 -0
  991. package/src/game/effect-terrain.ts +881 -0
  992. package/src/game/energy.ts +61 -0
  993. package/src/game/equip-cmp.ts +476 -0
  994. package/src/game/floor.ts +524 -0
  995. package/src/game/gear.ts +1352 -0
  996. package/src/game/harness.ts +270 -0
  997. package/src/game/history.ts +78 -0
  998. package/src/game/ignore-cmd.ts +68 -0
  999. package/src/game/inscription-confirm.ts +115 -0
  1000. package/src/game/known.ts +960 -0
  1001. package/src/game/loop.ts +671 -0
  1002. package/src/game/lore-color.ts +108 -0
  1003. package/src/game/mon-cast.ts +409 -0
  1004. package/src/game/mon-cmd.ts +767 -0
  1005. package/src/game/mon-death.ts +459 -0
  1006. package/src/game/mon-group.ts +368 -0
  1007. package/src/game/mon-list.ts +213 -0
  1008. package/src/game/mon-message.ts +359 -0
  1009. package/src/game/mon-place.ts +1012 -0
  1010. package/src/game/mon-ranged.ts +405 -0
  1011. package/src/game/mon-shape.ts +161 -0
  1012. package/src/game/mon-side.ts +511 -0
  1013. package/src/game/monster-turn.ts +1750 -0
  1014. package/src/game/obj-cmd.ts +1970 -0
  1015. package/src/game/obj-list.ts +302 -0
  1016. package/src/game/object-inspect.ts +162 -0
  1017. package/src/game/pickup.ts +483 -0
  1018. package/src/game/player-path.ts +1434 -0
  1019. package/src/game/player-side.ts +555 -0
  1020. package/src/game/player-turn.ts +852 -0
  1021. package/src/game/project-cast.ts +766 -0
  1022. package/src/game/project-feat.ts +386 -0
  1023. package/src/game/project-monster.ts +407 -0
  1024. package/src/game/project-obj.ts +298 -0
  1025. package/src/game/project-player.ts +236 -0
  1026. package/src/game/quest.ts +248 -0
  1027. package/src/game/ranged-cmd.ts +417 -0
  1028. package/src/game/scheduler.ts +150 -0
  1029. package/src/game/spell-cmd.ts +404 -0
  1030. package/src/game/spoil.ts +562 -0
  1031. package/src/game/steal.ts +169 -0
  1032. package/src/game/take-hit-hooks.ts +152 -0
  1033. package/src/game/target-loop.ts +501 -0
  1034. package/src/game/target.ts +424 -0
  1035. package/src/game/thrust.ts +174 -0
  1036. package/src/game/trap.ts +791 -0
  1037. package/src/game/ui-entry.ts +2075 -0
  1038. package/src/game/wizard.ts +1760 -0
  1039. package/src/game/world.ts +786 -0
  1040. package/src/gen/cave.ts +2858 -0
  1041. package/src/gen/gen-monster.ts +592 -0
  1042. package/src/gen/generate.ts +523 -0
  1043. package/src/gen/room.ts +2213 -0
  1044. package/src/gen/util.ts +1958 -0
  1045. package/src/generated/dun-profiles.ts +30 -0
  1046. package/src/generated/effects.ts +241 -0
  1047. package/src/generated/elements.ts +62 -0
  1048. package/src/generated/equip-slots.ts +36 -0
  1049. package/src/generated/history-types.ts +34 -0
  1050. package/src/generated/ignore-types.ts +66 -0
  1051. package/src/generated/index.ts +33 -0
  1052. package/src/generated/kind-flags.ts +44 -0
  1053. package/src/generated/message.ts +320 -0
  1054. package/src/generated/mon-message.ts +138 -0
  1055. package/src/generated/mon-race-flags.ts +182 -0
  1056. package/src/generated/mon-spells.ts +198 -0
  1057. package/src/generated/mon-temp-flags.ts +34 -0
  1058. package/src/generated/mon-timed.ts +34 -0
  1059. package/src/generated/object-flags.ts +95 -0
  1060. package/src/generated/object-modifiers.ts +43 -0
  1061. package/src/generated/options.ts +104 -0
  1062. package/src/generated/origins.ts +64 -0
  1063. package/src/generated/parser-errors.ts +140 -0
  1064. package/src/generated/player-flags.ts +48 -0
  1065. package/src/generated/player-timed.ts +118 -0
  1066. package/src/generated/projections.ts +103 -0
  1067. package/src/generated/randart-properties.ts +202 -0
  1068. package/src/generated/room-flags.ts +21 -0
  1069. package/src/generated/rooms.ts +50 -0
  1070. package/src/generated/square-flags.ts +56 -0
  1071. package/src/generated/stats.ts +22 -0
  1072. package/src/generated/terrain-flags.ts +76 -0
  1073. package/src/generated/terrain.ts +62 -0
  1074. package/src/generated/trap-flags.ts +42 -0
  1075. package/src/generated/tvals.ts +84 -0
  1076. package/src/generated/ui-entry-renderers.ts +24 -0
  1077. package/src/guard.ts +70 -0
  1078. package/src/host/args.ts +334 -0
  1079. package/src/host/bridge.ts +193 -0
  1080. package/src/host/index.ts +18 -0
  1081. package/src/host/io.ts +239 -0
  1082. package/src/host/memory.ts +134 -0
  1083. package/src/host/raw.ts +190 -0
  1084. package/src/index.ts +250 -0
  1085. package/src/loc.ts +157 -0
  1086. package/src/mod/hooks.ts +278 -0
  1087. package/src/mod/ids.ts +390 -0
  1088. package/src/mod/registry-host.ts +258 -0
  1089. package/src/mod/save-blocks.ts +787 -0
  1090. package/src/mod/vocabulary.ts +0 -0
  1091. package/src/mon/bind.ts +899 -0
  1092. package/src/mon/desc.ts +212 -0
  1093. package/src/mon/knowledge-groups.ts +164 -0
  1094. package/src/mon/lore-describe.ts +1414 -0
  1095. package/src/mon/lore.ts +306 -0
  1096. package/src/mon/make.ts +422 -0
  1097. package/src/mon/monster.ts +161 -0
  1098. package/src/mon/predicate.ts +220 -0
  1099. package/src/mon/project-mon.ts +855 -0
  1100. package/src/mon/spell.ts +418 -0
  1101. package/src/mon/steal.ts +246 -0
  1102. package/src/mon/summon.ts +145 -0
  1103. package/src/mon/take-hit.ts +195 -0
  1104. package/src/mon/timed.ts +300 -0
  1105. package/src/mon/types.ts +286 -0
  1106. package/src/msg.ts +146 -0
  1107. package/src/obj/artifact-fake.ts +63 -0
  1108. package/src/obj/artifact-known.ts +69 -0
  1109. package/src/obj/bind.ts +1345 -0
  1110. package/src/obj/chest.ts +249 -0
  1111. package/src/obj/desc.ts +699 -0
  1112. package/src/obj/effects-info.ts +349 -0
  1113. package/src/obj/flavor.ts +256 -0
  1114. package/src/obj/ignore.ts +387 -0
  1115. package/src/obj/knowledge.ts +1577 -0
  1116. package/src/obj/known-object.ts +718 -0
  1117. package/src/obj/make.ts +1367 -0
  1118. package/src/obj/object-info.ts +1737 -0
  1119. package/src/obj/object.ts +1296 -0
  1120. package/src/obj/power.ts +674 -0
  1121. package/src/obj/randart-build.ts +1705 -0
  1122. package/src/obj/randart-data.ts +1344 -0
  1123. package/src/obj/randart.ts +552 -0
  1124. package/src/obj/randname.ts +128 -0
  1125. package/src/obj/recharge.ts +62 -0
  1126. package/src/obj/types.ts +559 -0
  1127. package/src/obj/value.ts +215 -0
  1128. package/src/player/abilities.ts +142 -0
  1129. package/src/player/best-digger.ts +118 -0
  1130. package/src/player/bind.ts +1021 -0
  1131. package/src/player/birth.ts +794 -0
  1132. package/src/player/calcs.ts +1522 -0
  1133. package/src/player/combat-regen.ts +33 -0
  1134. package/src/player/exp.ts +275 -0
  1135. package/src/player/history.ts +211 -0
  1136. package/src/player/options.ts +244 -0
  1137. package/src/player/player.ts +356 -0
  1138. package/src/player/shape-lore.ts +250 -0
  1139. package/src/player/spell.ts +627 -0
  1140. package/src/player/take-hit.ts +173 -0
  1141. package/src/player/timed.ts +482 -0
  1142. package/src/player/types.ts +415 -0
  1143. package/src/rational.ts +388 -0
  1144. package/src/rng.ts +476 -0
  1145. package/src/save/buffer.ts +287 -0
  1146. package/src/save/compress.ts +132 -0
  1147. package/src/save/description.ts +70 -0
  1148. package/src/save/integrity.ts +133 -0
  1149. package/src/score/display.ts +125 -0
  1150. package/src/score/score.ts +341 -0
  1151. package/src/score/types.ts +104 -0
  1152. package/src/session/boot.ts +338 -0
  1153. package/src/session/game.ts +3712 -0
  1154. package/src/session/save.ts +2074 -0
  1155. package/src/sound/engine.ts +375 -0
  1156. package/src/sound/index.ts +13 -0
  1157. package/src/sound/sound-prefs-data.ts +173 -0
  1158. package/src/sound/types.ts +68 -0
  1159. package/src/store/bind.ts +145 -0
  1160. package/src/store/price.ts +97 -0
  1161. package/src/store/store-cmd.ts +73 -0
  1162. package/src/store/store.ts +723 -0
  1163. package/src/store/transact.ts +644 -0
  1164. package/src/store/types.ts +110 -0
  1165. package/src/version.ts +26 -0
  1166. package/src/visuals/engine.ts +444 -0
  1167. package/src/visuals/glyph-table.ts +191 -0
  1168. package/src/visuals/grafmode-data.ts +90 -0
  1169. package/src/visuals/grafmode.ts +123 -0
  1170. package/src/visuals/index.ts +19 -0
  1171. package/src/visuals/map-text.ts +152 -0
  1172. package/src/visuals/pref-expr.ts +182 -0
  1173. package/src/visuals/prefs.ts +981 -0
  1174. package/src/visuals/tile-prefs.ts +183 -0
  1175. package/src/world/chunk.ts +531 -0
  1176. package/src/world/feature.ts +215 -0
  1177. package/src/world/flow.ts +128 -0
  1178. package/src/world/project.ts +687 -0
  1179. package/src/world/projection.ts +228 -0
  1180. package/src/world/scatter.ts +72 -0
  1181. package/src/world/trap.ts +155 -0
  1182. package/src/world/view.ts +513 -0
  1183. package/src/z-queue.ts +300 -0
@@ -0,0 +1,2074 @@
1
+ /**
2
+ * JSON save/load (PORT_PLAN.md decision 9): the entity serializers that
3
+ * turn a live GameState into plain JSON and back. The format is the port's
4
+ * own (the C binary blocks of save.c/load.c are replaced by design); WHAT
5
+ * is saved follows upstream savefile semantics - notably the RNG state is
6
+ * persisted (save.c wr_randomizer), which is what the no-save-scum posture
7
+ * rides on (decisions 16/22): reloading resumes the same stream.
8
+ *
9
+ * References into bound registries (races, kinds, egos, artifacts, trap
10
+ * kinds, classes) are saved as stable indices/names and re-resolved against
11
+ * the pack on load, so a save is data + pack, never code. Raw effect chains
12
+ * and kind-owned text re-point at the kind on load rather than being
13
+ * copied into the save.
14
+ *
15
+ * Integrity: serializeGame produces the JSON payload; callers stamp/verify
16
+ * bytes with save/integrity.ts (stampSavefile / verifyStampedSavefile),
17
+ * the decision-16b tamper deterrent.
18
+ *
19
+ * BLOCK MAP (save.c wr_* / load.c rd_* -> the field this file writes). There is
20
+ * deliberately no per-block function pair: the C block table is replaced by one
21
+ * JSON document, so W1's question is field coverage, not symbol presence. Each
22
+ * row is proved by save-fields.test.ts, the C-derived coverage guard.
23
+ *
24
+ * wr_description (save.c:49) -> not a FIELD: the exact string is ported in
25
+ * save/description.ts and derived from
26
+ * fullName / lev / race / class / depth,
27
+ * which this document already carries. In a
28
+ * JSON save it need not be stored at all, and
29
+ * storing it would create the second source
30
+ * of truth upstream's own readers avoid by
31
+ * scanning a directory and asking each file.
32
+ * wr_randomizer (save.c:286) -> SavedGame.rng
33
+ * wr_options (save.c:314) -> SavedGame.options (SIDEBAR_MODE is UI)
34
+ * wr_messages (save.c:339) -> SavedGame.messages
35
+ * wr_monster_memory(save.c:356) -> SavedGame.lore (whole record, not just
36
+ * pkills/thefts: the port has no lore.txt)
37
+ * wr_object_memory (save.c:377) -> flavor.aware / .tried, everseen.kinds,
38
+ * ignore.kindAware / .kindUnaware
39
+ * wr_quests (save.c:405) -> player.quests
40
+ * wr_player (save.c:418) -> SavedPlayer + SavedGame.actor / arena /
41
+ * nameSuffix / skipCmdCoercion / unignoring /
42
+ * restingTurn
43
+ * wr_ignore (save.c:514) -> SavedGame.ignore, .autoinscriptions,
44
+ * everseen.egos. GAP: the rune-note block
45
+ * (save.c:589-605) has no counterpart -
46
+ * rune_note is a ledgered deferral.
47
+ * wr_misc (save.c:610) -> randartSeed, seedFlavor, player.totalWinner,
48
+ * player.noscore, isDead, turn, player.objKnown
49
+ * wr_artifacts (save.c:674) -> artifactsCreated / Seen / Everseen
50
+ * wr_player_hp (save.c:692) -> player.playerHp
51
+ * wr_player_spells (save.c:702) -> player.spellFlags / .spellOrder
52
+ * wr_gear_aux (save.c:715) -> gear (+ player.equipment slot map)
53
+ * wr_gear (save.c:737) -> ditto; the gear_k known twin is N/A
54
+ * wr_stores (save.c:744) -> SavedGame.stores
55
+ * wr_dungeon_aux (save.c:774) -> ChunkSquaresData (+ currentJoins)
56
+ * wr_objects_aux (save.c:873) -> SavedGame.floor
57
+ * wr_monsters_aux (save.c:915) -> SavedGame.monsters / .groups
58
+ * wr_traps_aux (save.c:933) -> SavedGame.traps
59
+ * wr_dungeon (save.c:959) -> dungeonDepth, daycount, actor.grid
60
+ * wr_objects (save.c:980) -> floor (player->cave twin N/A)
61
+ * wr_monsters (save.c:986) -> monsters (player->cave twin N/A)
62
+ * wr_traps (save.c:992) -> traps (player->cave twin N/A)
63
+ * wr_chunks (save.c:1001) -> levelCache + townChunk
64
+ * wr_history (save.c:1048) -> player.hist
65
+ * wr_item (save.c:69) -> SavedObject (obj->oidx N/A)
66
+ * wr_monster (save.c:201) -> SavedMonster
67
+ * wr_trap (save.c:261) -> SavedTrap
68
+ */
69
+
70
+ import type { Loc } from "../loc.js";
71
+ import { loc } from "../loc.js";
72
+ import type { RngState } from "../rng.js";
73
+ import type { RandomValue } from "../rng.js";
74
+ import { FlagSet } from "../bitflag.js";
75
+ import { Chunk, SQUARE_SIZE } from "../world/chunk.js";
76
+ import type { ChunkSquaresData } from "../world/chunk.js";
77
+ import type { GameObject } from "../obj/object.js";
78
+ import type { ObjRegistry } from "../obj/bind.js";
79
+ import type { ElementInfo } from "../obj/types.js";
80
+ import type { AutoinscriptionRegistry, Rune } from "../obj/knowledge.js";
81
+ import { buildRuneList, runeKey } from "../obj/knowledge.js";
82
+ import type { IgnoreSettingsData } from "../obj/ignore.js";
83
+ import { blankMonster, GROUP_MAX } from "../mon/monster.js";
84
+ import type { Monster, MonsterGroupInfo } from "../mon/monster.js";
85
+ import type { MonsterLore } from "../mon/lore.js";
86
+ import type { MonsterRegistry } from "../mon/bind.js";
87
+ import { blankPlayer } from "../player/player.js";
88
+ import type { Player, PlayerQuest } from "../player/player.js";
89
+ import type { PlayerRegistry } from "../player/bind.js";
90
+ import type { TrapKind } from "../world/trap.js";
91
+ import type { GameState, MonsterGroup, StoredLevel } from "../game/context.js";
92
+ import { MessageLog } from "../msg.js";
93
+ import type { Trap } from "../game/trap.js";
94
+ import type { Gear } from "../game/gear.js";
95
+ import type { Store } from "../store/store.js";
96
+ import type { BoundStore } from "../store/types.js";
97
+ import { newKnownMap } from "../game/known.js";
98
+ import type { KnownMap } from "../game/known.js";
99
+ import {
100
+ fnv1aIntegrity,
101
+ stampSavefile,
102
+ verifyStampedSavefile,
103
+ } from "../save/integrity.js";
104
+ import type { SaveIntegrity } from "../save/integrity.js";
105
+ import { applyCodec, findCodec, stripCodec } from "../save/compress.js";
106
+ import type { SaveCodec } from "../save/compress.js";
107
+ import type { ContentIdResolver } from "../mod/ids.js";
108
+ import { PY_MAX_LEVEL, TMD_MAX } from "../player/types.js";
109
+ import type {
110
+ ModBag,
111
+ OrphanStore,
112
+ SaveManifest,
113
+ } from "../mod/save-blocks.js";
114
+
115
+ /**
116
+ * The save format version this build writes. Version 2 replaced every numeric
117
+ * content index (kidx/eidx/aidx/ridx/tidx/feat, and the positional curse/
118
+ * brand/slay arrays) with the namespaced string ids of mod/ids.ts, the
119
+ * load-bearing rule of the mod substrate (MOD_LIFECYCLE decision 1). Version 3
120
+ * finished that job: `flavor`, `everseen` and `ignore` were the last blocks
121
+ * still keyed by raw kidx/eidx, and the rune-autoinscription block of wr_ignore
122
+ * (save.c:586-605) arrived with them. Older saves are not migrated: the game is
123
+ * pre-1.0 and the save format is still settling, so loadGame rejects them and
124
+ * the host starts a fresh game.
125
+ */
126
+ export const SAVE_VERSION = 3;
127
+
128
+ /* ------------------------------------------------------------------ *
129
+ * Objects.
130
+ * ------------------------------------------------------------------ */
131
+
132
+ export interface SavedObject {
133
+ /** Namespaced kind id (mod/ids.ts), e.g. "core:sword:dagger". */
134
+ kindId: string;
135
+ /** Ego id, or null when the object has no ego. */
136
+ egoId: string | null;
137
+ /** Artifact id, or null when the object is not an artifact. */
138
+ artifactId: string | null;
139
+ grid: { x: number; y: number } | null;
140
+ tval: number;
141
+ sval: number;
142
+ pval: number;
143
+ knownPval?: number;
144
+ weight: number;
145
+ dd: number;
146
+ ds: number;
147
+ ac: number;
148
+ toA: number;
149
+ toH: number;
150
+ toD: number;
151
+ flags: number[];
152
+ modifiers: number[];
153
+ elInfo: ElementInfo[];
154
+ /** Brand ids present on the object (the sparse form of the boolean array). */
155
+ brands: string[] | null;
156
+ /** Slay ids present on the object. */
157
+ slays: string[] | null;
158
+ /** Curse ids with their rolled power/timeout (sparse form). */
159
+ curses: Array<{ id: string; power: number; timeout: number }> | null;
160
+ time: RandomValue;
161
+ timeout: number;
162
+ number: number;
163
+ notice: number;
164
+ heldMIdx: number;
165
+ mimickingMIdx: number;
166
+ origin: number;
167
+ originDepth: number;
168
+ /** Origin monster race id, or null when there is no origin race. */
169
+ originRaceId: string | null;
170
+ note: string | null;
171
+ /** C wr_item's effect-present byte (save.c:113-118). */
172
+ effectPresent?: boolean;
173
+ /** C wr_item's saved activation index (save.c:184-188). */
174
+ activationIndex?: number;
175
+ }
176
+
177
+ export function serializeObject(
178
+ obj: GameObject,
179
+ ids: ContentIdResolver,
180
+ ): SavedObject {
181
+ return {
182
+ kindId: ids.kindId(obj.kind.kidx),
183
+ egoId: obj.ego ? ids.egoId(obj.ego.eidx) : null,
184
+ artifactId: obj.artifact ? ids.artifactId(obj.artifact.aidx) : null,
185
+ grid: obj.grid ? { x: obj.grid.x, y: obj.grid.y } : null,
186
+ tval: obj.tval,
187
+ sval: obj.sval,
188
+ pval: obj.pval,
189
+ ...(obj.knownPval !== undefined ? { knownPval: obj.knownPval } : {}),
190
+ weight: obj.weight,
191
+ dd: obj.dd,
192
+ ds: obj.ds,
193
+ ac: obj.ac,
194
+ toA: obj.toA,
195
+ toH: obj.toH,
196
+ toD: obj.toD,
197
+ flags: Array.from(obj.flags.bits),
198
+ modifiers: [...obj.modifiers],
199
+ elInfo: obj.elInfo.map((e) => ({ ...e })),
200
+ brands: serializeBrandList(obj.brands, ids),
201
+ slays: serializeSlayList(obj.slays, ids),
202
+ curses: serializeCurseList(obj.curses, ids),
203
+ time: { ...obj.time },
204
+ timeout: obj.timeout,
205
+ number: obj.number,
206
+ notice: obj.notice,
207
+ heldMIdx: obj.heldMIdx,
208
+ mimickingMIdx: obj.mimickingMIdx,
209
+ origin: obj.origin,
210
+ originDepth: obj.originDepth,
211
+ originRaceId: obj.originRace ? ids.raceId(obj.originRace) : null,
212
+ note: obj.note,
213
+ effectPresent: obj.effect !== null,
214
+ activationIndex: obj.activation?.index ?? 0,
215
+ };
216
+ }
217
+
218
+ /** Brand booleans -> the ids of the set brands (drops the dense zeroes). */
219
+ function serializeBrandList(
220
+ brands: boolean[] | null,
221
+ ids: ContentIdResolver,
222
+ ): string[] | null {
223
+ if (!brands) return null;
224
+ const out: string[] = [];
225
+ for (let i = 1; i < brands.length; i++) if (brands[i]) out.push(ids.brandId(i));
226
+ return out;
227
+ }
228
+
229
+ /** Slay booleans -> the ids of the set slays. */
230
+ function serializeSlayList(
231
+ slays: boolean[] | null,
232
+ ids: ContentIdResolver,
233
+ ): string[] | null {
234
+ if (!slays) return null;
235
+ const out: string[] = [];
236
+ for (let i = 1; i < slays.length; i++) if (slays[i]) out.push(ids.slayId(i));
237
+ return out;
238
+ }
239
+
240
+ /** Curse data array -> {id,power,timeout} for the powered curses only. */
241
+ function serializeCurseList(
242
+ curses: Array<{ power: number; timeout: number }> | null,
243
+ ids: ContentIdResolver,
244
+ ): Array<{ id: string; power: number; timeout: number }> | null {
245
+ if (!curses) return null;
246
+ const out: Array<{ id: string; power: number; timeout: number }> = [];
247
+ for (let i = 1; i < curses.length; i++) {
248
+ const c = curses[i];
249
+ if (c && c.power > 0) {
250
+ out.push({ id: ids.curseId(i), power: c.power, timeout: c.timeout });
251
+ }
252
+ }
253
+ return out.length > 0 ? out : null;
254
+ }
255
+
256
+ /** obj_k->curses (power 1 = rune known) -> the ids of the known curses. */
257
+ function serializeKnownCurseList(
258
+ curses: number[],
259
+ ids: ContentIdResolver,
260
+ ): string[] {
261
+ const out: string[] = [];
262
+ for (let i = 1; i < curses.length; i++) if (curses[i]) out.push(ids.curseId(i));
263
+ return out;
264
+ }
265
+
266
+ export function deserializeObject(
267
+ data: SavedObject,
268
+ reg: ObjRegistry,
269
+ ids: ContentIdResolver,
270
+ ): GameObject {
271
+ const kidx = ids.kindIndex(data.kindId);
272
+ const kind = kidx !== undefined ? reg.kinds[kidx] : undefined;
273
+ if (!kind) throw new Error(`save: unknown object kind ${data.kindId}`);
274
+ const aIdx =
275
+ data.artifactId !== null ? ids.artifactIndex(data.artifactId) : undefined;
276
+ const artifact = aIdx !== undefined ? (reg.artifacts[aIdx] ?? null) : null;
277
+ const eIdx = data.egoId !== null ? ids.egoIndex(data.egoId) : undefined;
278
+ const ego = eIdx !== undefined ? (reg.egos[eIdx] ?? null) : null;
279
+ const originIdx =
280
+ data.originRaceId !== null ? ids.raceIndex(data.originRaceId) : undefined;
281
+ const effectPresent = data.effectPresent ?? true;
282
+ /* load.c:223-232 resolves the saved activation index, not kind defaults. */
283
+ const activation =
284
+ data.activationIndex !== undefined
285
+ ? data.activationIndex > 0
286
+ ? (reg.activations[data.activationIndex] ?? null)
287
+ : null
288
+ : (artifact?.activation ?? kind.activation);
289
+ return {
290
+ kind,
291
+ ego,
292
+ artifact,
293
+ grid: data.grid ? loc(data.grid.x, data.grid.y) : null,
294
+ tval: data.tval,
295
+ sval: data.sval,
296
+ pval: data.pval,
297
+ ...(data.knownPval !== undefined ? { knownPval: data.knownPval } : {}),
298
+ weight: data.weight,
299
+ dd: data.dd,
300
+ ds: data.ds,
301
+ ac: data.ac,
302
+ toA: data.toA,
303
+ toH: data.toH,
304
+ toD: data.toD,
305
+ flags: new FlagSet(Uint8Array.from(data.flags)),
306
+ modifiers: [...data.modifiers],
307
+ elInfo: data.elInfo.map((e) => ({ ...e })),
308
+ brands: deserializeBrandList(data.brands, reg, ids),
309
+ slays: deserializeSlayList(data.slays, reg, ids),
310
+ curses: deserializeCurseList(data.curses, reg, ids),
311
+ /* Kind-owned data re-points at the bound kind. */
312
+ effect: effectPresent ? kind.effect : null,
313
+ effectMsg: effectPresent ? kind.effectMsg : "",
314
+ activation,
315
+ time: { ...data.time },
316
+ timeout: data.timeout,
317
+ number: data.number,
318
+ notice: data.notice,
319
+ heldMIdx: data.heldMIdx,
320
+ mimickingMIdx: data.mimickingMIdx,
321
+ origin: data.origin,
322
+ originDepth: data.originDepth,
323
+ originRace: originIdx ?? 0,
324
+ note: data.note,
325
+ };
326
+ }
327
+
328
+ /** Brand ids -> the dense boolean array (length brandMax), or null. */
329
+ function deserializeBrandList(
330
+ saved: string[] | null,
331
+ reg: ObjRegistry,
332
+ ids: ContentIdResolver,
333
+ ): boolean[] | null {
334
+ if (!saved) return null;
335
+ const out = new Array<boolean>(reg.brandMax).fill(false);
336
+ for (const id of saved) {
337
+ const i = ids.brandIndex(id);
338
+ if (i !== undefined) out[i] = true;
339
+ }
340
+ return out;
341
+ }
342
+
343
+ /** Slay ids -> the dense boolean array (length slayMax), or null. */
344
+ function deserializeSlayList(
345
+ saved: string[] | null,
346
+ reg: ObjRegistry,
347
+ ids: ContentIdResolver,
348
+ ): boolean[] | null {
349
+ if (!saved) return null;
350
+ const out = new Array<boolean>(reg.slayMax).fill(false);
351
+ for (const id of saved) {
352
+ const i = ids.slayIndex(id);
353
+ if (i !== undefined) out[i] = true;
354
+ }
355
+ return out;
356
+ }
357
+
358
+ /** Curse id list -> the dense CurseData array (length curseMax), or null. */
359
+ function deserializeCurseList(
360
+ saved: Array<{ id: string; power: number; timeout: number }> | null,
361
+ reg: ObjRegistry,
362
+ ids: ContentIdResolver,
363
+ ): Array<{ power: number; timeout: number }> | null {
364
+ if (!saved) return null;
365
+ const out: Array<{ power: number; timeout: number }> = [];
366
+ for (let i = 0; i < reg.curseMax; i++) out.push({ power: 0, timeout: 0 });
367
+ for (const c of saved) {
368
+ const i = ids.curseIndex(c.id);
369
+ if (i !== undefined) out[i] = { power: c.power, timeout: c.timeout };
370
+ }
371
+ return out;
372
+ }
373
+
374
+ /** Known-curse ids -> the dense obj_k->curses array (power 1 = known). */
375
+ function deserializeKnownCurseList(
376
+ saved: string[],
377
+ reg: ObjRegistry,
378
+ ids: ContentIdResolver,
379
+ ): number[] {
380
+ const out = new Array<number>(reg.curseMax).fill(0);
381
+ for (const id of saved) {
382
+ const i = ids.curseIndex(id);
383
+ if (i !== undefined) out[i] = 1;
384
+ }
385
+ return out;
386
+ }
387
+
388
+ /* ------------------------------------------------------------------ *
389
+ * Monsters and groups.
390
+ * ------------------------------------------------------------------ */
391
+
392
+ export interface SavedMonster {
393
+ /** Namespaced monster race id, e.g. "core:kobold". */
394
+ raceId: string;
395
+ /** Original race id (for a polymorphed/shaped monster), or null. */
396
+ originalRaceId: string | null;
397
+ midx: number;
398
+ grid: { x: number; y: number };
399
+ hp: number;
400
+ maxhp: number;
401
+ mTimed: number[];
402
+ mspeed: number;
403
+ energy: number;
404
+ cdis: number;
405
+ mflag: number[];
406
+ mimickedObj: number;
407
+ heldObj: SavedObject[];
408
+ attr: number;
409
+ target: { grid: { x: number; y: number }; midx: number };
410
+ groupInfo: MonsterGroupInfo[];
411
+ minRange: number;
412
+ bestRange: number;
413
+ /** C wr_monster's known_pstate.flags (save.c:231-232). */
414
+ knownPstateFlags?: number[];
415
+ /** C wr_monster's known_pstate.el_info[].res_level (save.c:234-235). */
416
+ knownPstateElInfo?: number[];
417
+ }
418
+
419
+ export function serializeMonster(
420
+ mon: Monster,
421
+ ids: ContentIdResolver,
422
+ ): SavedMonster {
423
+ return {
424
+ raceId: ids.raceId(mon.race.ridx),
425
+ originalRaceId: mon.originalRace ? ids.raceId(mon.originalRace.ridx) : null,
426
+ midx: mon.midx,
427
+ grid: { x: mon.grid.x, y: mon.grid.y },
428
+ hp: mon.hp,
429
+ maxhp: mon.maxhp,
430
+ mTimed: Array.from(mon.mTimed),
431
+ mspeed: mon.mspeed,
432
+ energy: mon.energy,
433
+ cdis: mon.cdis,
434
+ mflag: Array.from(mon.mflag.bits),
435
+ mimickedObj: mon.mimickedObj,
436
+ heldObj: mon.heldObj.map((o) => serializeObject(o, ids)),
437
+ attr: mon.attr,
438
+ target: {
439
+ grid: { x: mon.target.grid.x, y: mon.target.grid.y },
440
+ midx: mon.target.midx,
441
+ },
442
+ groupInfo: mon.groupInfo.map((g) => ({ ...g })),
443
+ minRange: mon.minRange,
444
+ bestRange: mon.bestRange,
445
+ knownPstateFlags: Array.from(mon.knownPstate.flags.bits),
446
+ knownPstateElInfo: Array.from(mon.knownPstate.elInfo),
447
+ };
448
+ }
449
+
450
+ /** rd_monster (load.c:259), over JSON instead of the binary block. */
451
+ export function deserializeMonster(
452
+ data: SavedMonster,
453
+ monsters: MonsterRegistry,
454
+ objects: ObjRegistry,
455
+ ids: ContentIdResolver,
456
+ ): Monster {
457
+ const ridx = ids.raceIndex(data.raceId);
458
+ const race = ridx !== undefined ? monsters.races[ridx] : undefined;
459
+ if (!race) throw new Error(`save: unknown race ${data.raceId}`);
460
+ const mon = blankMonster(race);
461
+ const origRidx =
462
+ data.originalRaceId !== null
463
+ ? ids.raceIndex(data.originalRaceId)
464
+ : undefined;
465
+ mon.originalRace = origRidx !== undefined ? (monsters.races[origRidx] ?? null) : null;
466
+ mon.midx = data.midx;
467
+ mon.grid = loc(data.grid.x, data.grid.y);
468
+ mon.hp = data.hp;
469
+ mon.maxhp = data.maxhp;
470
+ mon.mTimed.set(data.mTimed);
471
+ mon.mspeed = data.mspeed;
472
+ mon.energy = data.energy;
473
+ mon.cdis = data.cdis;
474
+ mon.mflag.bits.set(data.mflag);
475
+ if (data.knownPstateFlags) {
476
+ /* load.c:302 restores known_pstate.flags before the monster goes live. */
477
+ mon.knownPstate.flags.bits.set(data.knownPstateFlags);
478
+ }
479
+ if (data.knownPstateElInfo) {
480
+ /* load.c:305 restores known_pstate.el_info[].res_level before live use. */
481
+ mon.knownPstate.elInfo.set(data.knownPstateElInfo);
482
+ }
483
+ mon.mimickedObj = data.mimickedObj;
484
+ mon.heldObj = data.heldObj.map((o) => deserializeObject(o, objects, ids));
485
+ mon.attr = data.attr;
486
+ mon.target = {
487
+ grid: loc(data.target.grid.x, data.target.grid.y),
488
+ midx: data.target.midx,
489
+ };
490
+ for (let i = 0; i < GROUP_MAX; i++) {
491
+ const g = data.groupInfo[i];
492
+ if (g) mon.groupInfo[i] = { ...g };
493
+ }
494
+ mon.minRange = data.minRange;
495
+ mon.bestRange = data.bestRange;
496
+ return mon;
497
+ }
498
+
499
+ /* ------------------------------------------------------------------ *
500
+ * Player.
501
+ * ------------------------------------------------------------------ */
502
+
503
+ export interface SavedPlayer {
504
+ raceName: string;
505
+ clsName: string;
506
+ hitdie: number;
507
+ expFactor: number;
508
+ age: number;
509
+ ht: number;
510
+ wt: number;
511
+ au: number;
512
+ maxLev: number;
513
+ lev: number;
514
+ maxExp: number;
515
+ exp: number;
516
+ expFrac: number;
517
+ /** Recall / descent state (absent in older saves; default 0). */
518
+ maxDepth?: number;
519
+ recallDepth?: number;
520
+ wordRecall?: number;
521
+ deepDescent?: number;
522
+ mhp: number;
523
+ chp: number;
524
+ chpFrac: number;
525
+ msp: number;
526
+ csp: number;
527
+ cspFrac: number;
528
+ statMax: number[];
529
+ statCur: number[];
530
+ statMap: number[];
531
+ statBirth: number[];
532
+ timed: number[];
533
+ spellFlags: number[];
534
+ spellOrder: number[];
535
+ playerHp: number[];
536
+ auBirth: number;
537
+ htBirth: number;
538
+ wtBirth: number;
539
+ /**
540
+ * full_name (save.c:422): the character's name. Optional: absent in saves
541
+ * written before the field, which load with an empty name (buildScore falls
542
+ * back to the empty string exactly as before this field existed).
543
+ */
544
+ fullName?: string;
545
+ /**
546
+ * died_from (save.c:424): the cause-of-death string. Optional: absent saves
547
+ * load with an empty string.
548
+ */
549
+ diedFrom?: string;
550
+ /**
551
+ * noscore (save.c:623, wr_u16b): the cheater bit mask. Optional: absent saves
552
+ * load as 0 (a clean, scored character).
553
+ */
554
+ noscore?: number;
555
+ history: string;
556
+ /**
557
+ * hist (player-history.h struct player_history): the runtime auto-history
558
+ * event log - absent in saves predating this field, which load as an
559
+ * empty log (SAVEFILE_IMPORT-tolerant posture, matching load.c's
560
+ * best-effort read of older savefiles).
561
+ */
562
+ hist?: SavedHistoryInfo[];
563
+ equipment: number[];
564
+ /**
565
+ * obj_k, every rune variety (wr_player's object knowledge). Older saves
566
+ * (pre rune learn-by-use) carried only objKnownModifiers; the reader
567
+ * accepts both.
568
+ */
569
+ objKnown?: {
570
+ modifiers: number[];
571
+ toA: number;
572
+ toH: number;
573
+ toD: number;
574
+ /**
575
+ * obj_k->dd / ds / ac (the "know dice"/"know ac" runes). Optional: saves
576
+ * written before these fields existed omit them; the reader defaults each
577
+ * to 1 (obvious birth knowledge, always correct - see player_outfit).
578
+ */
579
+ dd?: number;
580
+ ds?: number;
581
+ ac?: number;
582
+ elInfo: ElementInfo[];
583
+ flags: number[];
584
+ /** Ids of the brand runes the player has learned. */
585
+ brands: string[];
586
+ /** Ids of the slay runes the player has learned. */
587
+ slays: string[];
588
+ /** Ids of the curse runes the player has learned. */
589
+ curses: string[];
590
+ };
591
+ /** Legacy (save version 1 pre-#13): modifier runes only. */
592
+ objKnownModifiers?: number[];
593
+ shapeName: string | null;
594
+ skills: number[];
595
+ upkeep: { playing: boolean; newSpells: number; totalWeight: number };
596
+ /**
597
+ * quests (player-quest.h): the per-character quest history. Optional: absent
598
+ * in saves written before the quest system, which reload with no quests (and
599
+ * hence no win condition until re-birthed) - the SAVEFILE_IMPORT-tolerant
600
+ * posture matching the other late-added fields.
601
+ */
602
+ quests?: PlayerQuest[];
603
+ /** total_winner: the victory flag. Optional; absent saves load as false. */
604
+ totalWinner?: boolean;
605
+ }
606
+
607
+ /** Serialized history entry; C save.c:1063-1067 writes the artifact NAME. */
608
+ export interface SavedHistoryInfo {
609
+ type: number;
610
+ dlev: number;
611
+ clev: number;
612
+ artifactName: string;
613
+ turn: number;
614
+ event: string;
615
+ /** Legacy JSON saves used a numeric aIdx; never written by this version. */
616
+ aIdx?: number;
617
+ }
618
+
619
+ export function serializePlayer(
620
+ p: Player,
621
+ ids: ContentIdResolver,
622
+ ): SavedPlayer {
623
+ return {
624
+ raceName: p.race.name,
625
+ clsName: p.cls.name,
626
+ hitdie: p.hitdie,
627
+ expFactor: p.expFactor,
628
+ age: p.age,
629
+ ht: p.ht,
630
+ wt: p.wt,
631
+ au: p.au,
632
+ maxLev: p.maxLev,
633
+ lev: p.lev,
634
+ maxExp: p.maxExp,
635
+ exp: p.exp,
636
+ expFrac: p.expFrac,
637
+ maxDepth: p.maxDepth,
638
+ recallDepth: p.recallDepth,
639
+ wordRecall: p.wordRecall,
640
+ deepDescent: p.deepDescent,
641
+ mhp: p.mhp,
642
+ chp: p.chp,
643
+ chpFrac: p.chpFrac,
644
+ msp: p.msp,
645
+ csp: p.csp,
646
+ cspFrac: p.cspFrac,
647
+ statMax: [...p.statMax],
648
+ statCur: [...p.statCur],
649
+ statMap: [...p.statMap],
650
+ statBirth: [...p.statBirth],
651
+ timed: Array.from(p.timed),
652
+ spellFlags: [...p.spellFlags],
653
+ spellOrder: [...p.spellOrder],
654
+ playerHp: [...p.playerHp],
655
+ auBirth: p.auBirth,
656
+ htBirth: p.htBirth,
657
+ wtBirth: p.wtBirth,
658
+ fullName: p.fullName,
659
+ diedFrom: p.diedFrom,
660
+ noscore: p.noscore,
661
+ history: p.history,
662
+ hist: p.hist.map((e) => ({
663
+ type: e.type,
664
+ dlev: e.dlev,
665
+ clev: e.clev,
666
+ artifactName: ids.artifactName(e.aIdx) ?? "",
667
+ turn: e.turn,
668
+ event: e.event,
669
+ })),
670
+ equipment: [...p.equipment],
671
+ objKnown: {
672
+ modifiers: [...p.objKnown.modifiers],
673
+ toA: p.objKnown.toA,
674
+ toH: p.objKnown.toH,
675
+ toD: p.objKnown.toD,
676
+ dd: p.objKnown.dd,
677
+ ds: p.objKnown.ds,
678
+ ac: p.objKnown.ac,
679
+ elInfo: p.objKnown.elInfo.map((e) => ({ ...e })),
680
+ flags: Array.from(p.objKnown.flags.bits),
681
+ /* The learned runes save as the ids of the known brands/slays/curses. */
682
+ brands: serializeBrandList(p.objKnown.brands, ids) ?? [],
683
+ slays: serializeSlayList(p.objKnown.slays, ids) ?? [],
684
+ curses: serializeKnownCurseList(p.objKnown.curses, ids),
685
+ },
686
+ shapeName: p.shape ? p.shape.name : null,
687
+ skills: [...p.skills],
688
+ upkeep: { ...p.upkeep },
689
+ quests: p.quests.map((q) => ({ ...q })),
690
+ totalWinner: p.totalWinner,
691
+ };
692
+ }
693
+
694
+ export function deserializePlayer(
695
+ data: SavedPlayer,
696
+ players: PlayerRegistry,
697
+ objReg: ObjRegistry,
698
+ ids: ContentIdResolver,
699
+ ): Player {
700
+ const race = players.raceByName(data.raceName);
701
+ const cls = players.classByName(data.clsName);
702
+ if (!race || !cls) {
703
+ throw new Error(`save: unknown race/class ${data.raceName}/${data.clsName}`);
704
+ }
705
+ const body = players.bodies[race.body] ?? players.bodies[0]!;
706
+ const p = blankPlayer(race, cls, body);
707
+ p.hitdie = data.hitdie;
708
+ p.expFactor = data.expFactor;
709
+ p.age = data.age;
710
+ p.ht = data.ht;
711
+ p.wt = data.wt;
712
+ p.au = data.au;
713
+ /* load.c:767-780 rejects levels outside 1..PY_MAX_LEVEL. */
714
+ if (data.lev < 1 || data.lev > PY_MAX_LEVEL) {
715
+ throw new Error(`save: invalid player level ${data.lev}`);
716
+ }
717
+ /* load.c:785-789 repairs max_lev, max_depth and recall_depth. */
718
+ p.maxLev = data.maxLev < data.lev ? data.lev : data.maxLev;
719
+ p.lev = data.lev;
720
+ p.maxExp = data.maxExp;
721
+ p.exp = data.exp;
722
+ p.expFrac = data.expFrac;
723
+ p.maxDepth = data.maxDepth ?? 0;
724
+ if (p.maxDepth < 0) p.maxDepth = 1;
725
+ p.recallDepth = data.recallDepth ?? 0;
726
+ if (p.recallDepth <= 0) p.recallDepth = p.maxDepth;
727
+ p.wordRecall = data.wordRecall ?? 0;
728
+ p.deepDescent = data.deepDescent ?? 0;
729
+ p.mhp = data.mhp;
730
+ p.chp = data.chp;
731
+ p.chpFrac = data.chpFrac;
732
+ p.msp = data.msp;
733
+ p.csp = data.csp;
734
+ p.cspFrac = data.cspFrac;
735
+ p.statMax = [...data.statMax];
736
+ p.statCur = [...data.statCur];
737
+ p.statMap = [...data.statMap];
738
+ p.statBirth = [...data.statBirth];
739
+ /* load.c:807-823 reads only supported timed entries and discards extras. */
740
+ p.timed.set(data.timed.slice(0, TMD_MAX));
741
+ p.spellFlags = [...data.spellFlags];
742
+ p.spellOrder = [...data.spellOrder];
743
+ p.playerHp = [...data.playerHp];
744
+ p.auBirth = data.auBirth;
745
+ p.htBirth = data.htBirth;
746
+ p.wtBirth = data.wtBirth;
747
+ /* full_name / died_from / noscore (load.c:661, load.c:966): absent in saves
748
+ * predating the fields, which restore the blankPlayer defaults ("" / "" / 0). */
749
+ p.fullName = data.fullName ?? "";
750
+ /* load.c:791-793 resets died_from whenever HP is non-negative. */
751
+ p.diedFrom = p.chp >= 0 ? "(alive and well)" : (data.diedFrom ?? "");
752
+ p.noscore = data.noscore ?? 0;
753
+ p.history = data.history;
754
+ p.hist = [];
755
+ for (const e of data.hist ?? []) {
756
+ let aIdx = 0;
757
+ const artifactName = e.artifactName ?? "";
758
+ if (artifactName) {
759
+ const artifact = objReg.artifacts.find((a) => a?.name === artifactName);
760
+ if (!artifact) continue;
761
+ aIdx = artifact.aidx;
762
+ } else if (e.aIdx !== undefined) {
763
+ /* Tolerate the pre-fix JSON shape, while all new saves use names. */
764
+ aIdx = e.aIdx;
765
+ }
766
+ p.hist.push({
767
+ type: e.type,
768
+ dlev: e.dlev,
769
+ clev: e.clev,
770
+ aIdx,
771
+ turn: e.turn,
772
+ event: e.event,
773
+ });
774
+ }
775
+ p.equipment = [...data.equipment];
776
+ if (data.objKnown) {
777
+ p.objKnown = {
778
+ modifiers: [...data.objKnown.modifiers],
779
+ toA: data.objKnown.toA,
780
+ toH: data.objKnown.toH,
781
+ toD: data.objKnown.toD,
782
+ /* Default to 1 for pre-field saves: dd/ds/ac are obvious birth knowledge
783
+ * (player_outfit), always 1, so an absent value restores exactly. */
784
+ dd: data.objKnown.dd ?? 1,
785
+ ds: data.objKnown.ds ?? 1,
786
+ ac: data.objKnown.ac ?? 1,
787
+ elInfo: data.objKnown.elInfo.map((e) => ({ ...e })),
788
+ flags: new FlagSet(Uint8Array.from(data.objKnown.flags)),
789
+ brands: deserializeBrandList(data.objKnown.brands, objReg, ids) ?? [],
790
+ slays: deserializeSlayList(data.objKnown.slays, objReg, ids) ?? [],
791
+ curses: deserializeKnownCurseList(data.objKnown.curses, objReg, ids),
792
+ };
793
+ } else if (data.objKnownModifiers) {
794
+ /* Legacy pre-#13 save: only the modifier runes were tracked. */
795
+ p.objKnown.modifiers = [...data.objKnownModifiers];
796
+ }
797
+ p.shape =
798
+ data.shapeName !== null
799
+ ? (players.shapes.find((s) => s.name === data.shapeName) ?? null)
800
+ : null;
801
+ p.skills = [...data.skills];
802
+ p.upkeep = { ...data.upkeep };
803
+ p.quests = data.quests ? data.quests.map((q) => ({ ...q })) : [];
804
+ p.totalWinner = data.totalWinner ?? false;
805
+ return p;
806
+ }
807
+
808
+ /* ------------------------------------------------------------------ *
809
+ * The whole game.
810
+ * ------------------------------------------------------------------ */
811
+
812
+ export interface SavedTrap {
813
+ /** Namespaced trap-kind id, e.g. "core:trap-door". */
814
+ trapId: string;
815
+ grid: { x: number; y: number };
816
+ power: number;
817
+ timeout: number;
818
+ flags: number[];
819
+ }
820
+
821
+ export interface SavedGame {
822
+ version: number;
823
+ player: SavedPlayer;
824
+ actor: {
825
+ grid: { x: number; y: number };
826
+ energy: number;
827
+ totalEnergy: number;
828
+ };
829
+ gear: { next: number; pack: number[]; store: Array<[number, SavedObject]> };
830
+ /** Omitted for dead saves, matching save.c:873-1045. */
831
+ chunk?: ChunkSquaresData;
832
+ /**
833
+ * Feature legend: every terrain index that appears in chunk.feats or
834
+ * known.feat, paired with its namespaced feature id. The grid stays a
835
+ * compact numeric array (one small legend, not a string per cell); on load
836
+ * the numeric feats are remapped through the legend to the current pack's
837
+ * indices, so terrain references survive pack changes exactly like every
838
+ * other content reference (MOD_LIFECYCLE decision 1). Optional only for the
839
+ * degenerate empty-level case.
840
+ */
841
+ featLegend?: Array<[number, string]>;
842
+ monsters?: Array<SavedMonster | null>;
843
+ groups?: Array<MonsterGroup | null>;
844
+ /** Floor piles in pile order (head first), keyed by grid. */
845
+ floor?: Array<{ x: number; y: number; objs: SavedObject[] }>;
846
+ traps?: Array<{ x: number; y: number; traps: SavedTrap[] }>;
847
+ /** The dungeon header retained by C even when the player is dead. */
848
+ dungeonDepth?: number;
849
+ rng: RngState;
850
+ turn: number;
851
+ playing: boolean;
852
+ isDead: boolean;
853
+ /**
854
+ * kind->aware / kind->tried (wr_object_memory, save.c:399-401), by namespaced
855
+ * kind id. Upstream writes these positionally by kidx and rd_object_memory
856
+ * reads them back positionally (load.c:602-620), so a data change that
857
+ * re-orders k_info silently re-targets them; the port's format keys every
858
+ * content reference by id instead (SAVE_VERSION 3).
859
+ */
860
+ flavor: { aware: string[]; tried: string[] };
861
+ /**
862
+ * kind->everseen / ego->everseen (save.c L397, L533): the per-game "ever
863
+ * seen" flags for object kinds and egos, by namespaced id (see `flavor`).
864
+ * Optional: absent in saves written before everseen tracking, which reload
865
+ * with an empty set (the object browser then falls back to flavour-aware
866
+ * membership only).
867
+ */
868
+ everseen?: { kinds: string[]; egos: string[] };
869
+ /**
870
+ * seed_flavor (game-world.c): the seed flavor_init used to assign object
871
+ * colours/titles. Optional: absent in saves written before flavour
872
+ * assignment, which reload with a stable seed-0 assignment.
873
+ */
874
+ seedFlavor?: number;
875
+ /**
876
+ * The player's map knowledge (game/known.ts). Optional: absent in
877
+ * version-1 saves written before the knowledge layer, which load with
878
+ * an all-unknown map.
879
+ */
880
+ known?: SavedKnown;
881
+ /**
882
+ * Monster memory (mon/lore.ts), keyed by monster race id. Optional: absent
883
+ * in saves written before lore, which load with no memory. Upstream splits
884
+ * this between the savefile (pkills/thefts) and the user lore file; the
885
+ * JSON save carries the whole record.
886
+ */
887
+ lore?: Array<[string, SavedLore]>;
888
+ /**
889
+ * Single combat in progress (upkeep->arena_level + player->old_grid).
890
+ * The stashed pre-arena level does not survive the save boundary:
891
+ * winning after a reload exits to a fresh level of the same depth.
892
+ */
893
+ arena?: { oldGrid: { x: number; y: number } };
894
+ /**
895
+ * The town stores (store.c wr_stores, save.c:744-765): every shop's current
896
+ * proprietor and full stock, in registry order - including FEAT_HOME, whose
897
+ * stock is the player's home stash (gap 12.1: the home stash must survive
898
+ * save/load). Optional: absent in saves written before store persistence and
899
+ * in saves taken before the player ever reached town, which reload and
900
+ * re-stock fresh (the pre-fix behaviour). See serializeStores.
901
+ */
902
+ stores?: SavedStore[];
903
+ /**
904
+ * daycount (game-world.c / save.c:963): store turnovers accrued while in the
905
+ * dungeon, consumed by store_update on the return to town (gap 12.3). Optional
906
+ * / 0 when no days have elapsed.
907
+ */
908
+ daycount?: number;
909
+ /**
910
+ * player->resting_turn (save.c:507; wr_player): the cumulative count of player
911
+ * turns spent resting. Gap 12.6. Optional / omitted when 0 (a character that
912
+ * has never rested), which loads back as 0. See GameState.restingTurn.
913
+ */
914
+ restingTurn?: number;
915
+ /**
916
+ * player->skip_cmd_coercion (save.c:490; wr_player): the bloodlust command
917
+ * -coercion skip state (0/1/2). Gap 12.6. Optional / omitted when 0.
918
+ */
919
+ skipCmdCoercion?: number;
920
+ /**
921
+ * player->unignoring (save.c:491; wr_player): the temporary "show ignored
922
+ * items" toggle. Gap 12.6. Optional / omitted when 0 (ignoring active).
923
+ */
924
+ unignoring?: number;
925
+ /**
926
+ * player->opts.name_suffix (save.c:432; wr_player): the numeric name suffix
927
+ * for the high-score table. Gap 12.6. Optional / omitted when 0.
928
+ */
929
+ nameSuffix?: number;
930
+ /**
931
+ * The rolling message log (wr_messages/rd_messages, save.c:339-353 /
932
+ * load.c:471-495). Gap 12.8. Each entry is text + MSG_* type, in the savefile's
933
+ * oldest-first order and capped at the 80 newest messages (save.c:345). The
934
+ * per-entry repeat count is NOT persisted - upstream wr_messages writes only
935
+ * message_str/message_type, so a reload collapses through message_add and every
936
+ * count resets to 1 (the quirk is preserved). Optional / omitted for an empty
937
+ * log and in saves written before message persistence, which load empty.
938
+ */
939
+ messages?: SavedMessage[];
940
+ /**
941
+ * The player's ignore settings (obj-ignore.c). Optional: absent in saves
942
+ * written before ignoring, which load with everything shown.
943
+ */
944
+ ignore?: SavedIgnoreSettings;
945
+ /**
946
+ * The player option store (option.c): every option value, hitpoint_warn /
947
+ * delay_factor, and the immutable birth-option snapshot. Optional: absent in
948
+ * saves written before the option store, which load with the table defaults.
949
+ */
950
+ options?: import("../player/options.js").OptionStateData;
951
+ /**
952
+ * randart_seed (obj-randart.c): the seed do_randart used when birth_randarts
953
+ * is on. Optional / 0 when the standard artifact set is in use. Persisted so
954
+ * a reload rebuilds the identical random artifact set.
955
+ */
956
+ randartSeed?: number;
957
+ /**
958
+ * aup_info[] (obj-make.c): the ids of the artifacts already created. Stored
959
+ * as an id list (not a by-aidx boolean array) so the created set survives
960
+ * pack changes. Optional / absent in saves written before artifact
961
+ * generation landed, which load with an all-false set.
962
+ */
963
+ artifactsCreated?: string[];
964
+ /** aup_info[].seen (save.c:684-685). */
965
+ artifactsSeen?: string[];
966
+ /** aup_info[].everseen (save.c:685-686). */
967
+ artifactsEverseen?: string[];
968
+ /**
969
+ * The manifest block (mod/save-blocks.ts, P7.2): the pack set + resolved load
970
+ * order + core-owned determinism mode that produced this save - its profile
971
+ * fingerprint. Optional: absent in saves written before the mod substrate,
972
+ * which load as core-only + deterministic (coreOnlyManifest).
973
+ */
974
+ manifest?: SaveManifest;
975
+ /**
976
+ * Per-mod private bags (mod:<id>), keyed by pack id: opaque JSON the engine
977
+ * never interprets, versioned by each mod's saveSchema. Absent when no mod
978
+ * persisted state. Round-tripped verbatim; migrated only by the owning mod.
979
+ */
980
+ mods?: Record<string, ModBag>;
981
+ /**
982
+ * The orphans store (orphans:<id>@<version>): entities quarantined because
983
+ * their defining pack is missing or shadowed (mod/save-blocks.ts). Frozen and
984
+ * inert, restored by rehydrateSave when the pack returns. Absent when nothing
985
+ * is quarantined.
986
+ */
987
+ orphans?: OrphanStore;
988
+ /**
989
+ * decision-8 seam: whether the one-time keep/purge orphan prompt has already
990
+ * been shown for this save. Core computes the orphan count; the UI shows the
991
+ * prompt once and sets this so it never nags again. Absent = not yet shown.
992
+ */
993
+ orphansAcknowledged?: boolean;
994
+ /**
995
+ * birth_levels_persist (#30) frozen-level cache (game/context.ts StoredLevel),
996
+ * one entry per cached depth, reusing the same chunk / monster / floor / trap /
997
+ * known serializers as the current level. Optional / absent when the option is
998
+ * off (the default) or no level has been frozen: older and default saves load
999
+ * with an empty cache (back-compat, like every other optional field here).
1000
+ */
1001
+ levelCache?: SavedStoredLevel[];
1002
+ /**
1003
+ * GameState.currentJoins: the in-play level's stair connectors (chunk->join,
1004
+ * generate.c L1203-1214), so a first-visit persistent level can align stairs
1005
+ * with the level just left. Absent when the option is off / no joins recorded.
1006
+ */
1007
+ currentJoins?: Array<{ x: number; y: number; feat: number; info?: number[] }>;
1008
+ /**
1009
+ * The per-kind autoinscription registry (obj-ignore.c note_aware/note_unaware,
1010
+ * obj/knowledge.ts AutoinscriptionRegistry). Keyed by the namespaced kind id
1011
+ * (like every other content reference, MOD_LIFECYCLE decision 1) so notes
1012
+ * survive pack reordering. Optional / absent when nothing is registered:
1013
+ * older saves and the default (no autoinscriptions) load with an empty
1014
+ * registry, back-compat like every other optional field here.
1015
+ */
1016
+ autoinscriptions?: SavedAutoinscription[];
1017
+ /**
1018
+ * The per-RUNE autoinscriptions (rune_list[i].note; wr_ignore save.c:586-605,
1019
+ * rd_ignore load.c:937-945). Keyed by runeKey (obj/knowledge.ts), the
1020
+ * pack-stable form of the raw index upstream writes (`wr_s16b(k)`,
1021
+ * save.c:600); the runtime registry is still index-keyed like rune_list.
1022
+ * Absent when no rune carries a note.
1023
+ */
1024
+ runeNotes?: Array<[string, string]>;
1025
+ /**
1026
+ * Terrain-only Town chunk (wr_chunks always saves the Town entry even when
1027
+ * birth_levels_persist is OFF; generate.c:1371-1373 / save.c:1001-1044).
1028
+ * Present after leaving depth 0 without persist; consumed on town re-entry.
1029
+ * Optional / absent in older saves and before the player has left town.
1030
+ */
1031
+ townChunk?: ChunkSquaresData;
1032
+ /** Feature legend for townChunk feats (same remap contract as levelCache). */
1033
+ townFeatLegend?: Array<[number, string]>;
1034
+ }
1035
+
1036
+ /** One serialized per-kind autoinscription entry (namespaced kind id + notes). */
1037
+ export interface SavedAutoinscription {
1038
+ kindId: string;
1039
+ aware?: string;
1040
+ unaware?: string;
1041
+ }
1042
+
1043
+ /**
1044
+ * One serialized message-log entry (wr_messages, save.c:350-351): the message
1045
+ * text and its MSG_* type. The repeat count is intentionally NOT stored -
1046
+ * upstream persists only message_str/message_type, so counts do not survive a
1047
+ * reload (see SavedGame.messages).
1048
+ */
1049
+ export interface SavedMessage {
1050
+ str: string;
1051
+ type: number;
1052
+ }
1053
+
1054
+ /**
1055
+ * Serialize the rolling message log (wr_messages, save.c:339-353): the newest
1056
+ * `cap` messages (default 80, the upstream limit) written oldest-first, each as
1057
+ * text + type. Returns undefined for an empty / absent log so a clean save omits
1058
+ * the block. Faithful to the C: the per-entry repeat count is dropped, so a
1059
+ * reload recollapses through MessageLog.add and every count resets to 1.
1060
+ */
1061
+ export function serializeMessages(
1062
+ log: MessageLog | undefined,
1063
+ cap = 80,
1064
+ ): SavedMessage[] | undefined {
1065
+ if (!log || log.num() === 0) return undefined;
1066
+ const num = Math.min(log.num(), cap);
1067
+ const out: SavedMessage[] = [];
1068
+ /* save.c:349 dumps oldest-of-the-kept first (i = num - 1 down to 0). */
1069
+ for (let age = num - 1; age >= 0; age--) {
1070
+ out.push({ str: log.str(age), type: log.type(age) });
1071
+ }
1072
+ return out;
1073
+ }
1074
+
1075
+ /**
1076
+ * Rebuild a message log from its saved form (rd_messages, load.c:471-495):
1077
+ * re-add each entry oldest-first via MessageLog.add, exactly as upstream calls
1078
+ * message_add in a forward loop. An absent block (older save / empty log) yields
1079
+ * a fresh empty log.
1080
+ */
1081
+ export function deserializeMessages(
1082
+ data: SavedMessage[] | undefined,
1083
+ ): MessageLog {
1084
+ const log = new MessageLog();
1085
+ if (!data) return log;
1086
+ for (const m of data) log.add(m.str, m.type);
1087
+ return log;
1088
+ }
1089
+
1090
+ /** Serialized map knowledge (remembered terrain and floor objects). */
1091
+ export interface SavedKnown {
1092
+ feat: number[];
1093
+ objects: Array<[number, { ch: string | null; attr: string }]>;
1094
+ }
1095
+
1096
+ /** One serialized race-lore record. */
1097
+ export interface SavedLore {
1098
+ sights: number;
1099
+ deaths: number;
1100
+ pkills: number;
1101
+ thefts: number;
1102
+ tkills: number;
1103
+ wake: number;
1104
+ ignore: number;
1105
+ dropGold: number;
1106
+ dropItem: number;
1107
+ castInnate: number;
1108
+ castSpell: number;
1109
+ blowTimesSeen: number[];
1110
+ blowKnown: boolean[];
1111
+ flags: number[];
1112
+ spellFlags: number[];
1113
+ allKnown: boolean;
1114
+ armourKnown: boolean;
1115
+ dropKnown: boolean;
1116
+ sleepKnown: boolean;
1117
+ spellFreqKnown: boolean;
1118
+ innateFreqKnown: boolean;
1119
+ }
1120
+
1121
+ /** Build the feature legend for every fidx appearing in the terrain grids. */
1122
+ function buildFeatLegend(
1123
+ feats: readonly number[],
1124
+ known: readonly number[],
1125
+ ids: ContentIdResolver,
1126
+ ): Array<[number, string]> {
1127
+ const present = new Set<number>();
1128
+ for (const f of feats) present.add(f);
1129
+ for (const f of known) present.add(f);
1130
+ const legend: Array<[number, string]> = [];
1131
+ for (const f of present) {
1132
+ /* Skip sentinels (an unset cell is -1): they carry no feature id and are
1133
+ * pack-independent, so remapFeats leaves them untouched. */
1134
+ const id = ids.featIdOrNull(f);
1135
+ if (id !== null) legend.push([f, id]);
1136
+ }
1137
+ return legend;
1138
+ }
1139
+
1140
+ /** aup_info boolean[] (by aidx) -> the ids of the created artifacts. */
1141
+ function serializeArtifactsCreated(
1142
+ created: readonly boolean[],
1143
+ ids: ContentIdResolver,
1144
+ ): string[] {
1145
+ const out: string[] = [];
1146
+ for (let i = 1; i < created.length; i++) {
1147
+ if (created[i]) out.push(ids.artifactId(i));
1148
+ }
1149
+ return out;
1150
+ }
1151
+
1152
+ /** Serialize one aup_info boolean field as stable artifact names. */
1153
+ function serializeArtifactFlags(
1154
+ flags: readonly boolean[],
1155
+ ids: ContentIdResolver,
1156
+ ): string[] {
1157
+ const out: string[] = [];
1158
+ for (let i = 1; i < flags.length; i++) {
1159
+ if (flags[i]) out.push(ids.artifactId(i));
1160
+ }
1161
+ return out;
1162
+ }
1163
+
1164
+ /** Serialize a live game (state + flavor knowledge) into plain JSON data. */
1165
+ export function serializeGame(
1166
+ state: GameState,
1167
+ flavor: { snapshot(): { aware: number[]; tried: number[] } },
1168
+ seedFlavor: number,
1169
+ ids: ContentIdResolver,
1170
+ randartSeed = 0,
1171
+ everseen?: { snapshot(): { kinds: number[]; egos: number[] } },
1172
+ ): SavedGame {
1173
+ const floor: NonNullable<SavedGame["floor"]> = [];
1174
+ for (const pile of state.floor.values()) {
1175
+ const head = pile[0];
1176
+ if (!head || !head.grid) continue;
1177
+ floor.push({
1178
+ x: head.grid.x,
1179
+ y: head.grid.y,
1180
+ objs: pile.map((o) => serializeObject(o, ids)),
1181
+ });
1182
+ }
1183
+ const traps: NonNullable<SavedGame["traps"]> = [];
1184
+ for (const list of state.traps.values()) {
1185
+ const head = list[0];
1186
+ if (!head) continue;
1187
+ traps.push({
1188
+ x: head.grid.x,
1189
+ y: head.grid.y,
1190
+ traps: list.map((t) => ({
1191
+ trapId: ids.trapId(t.tidx),
1192
+ grid: { x: t.grid.x, y: t.grid.y },
1193
+ power: t.power,
1194
+ timeout: t.timeout,
1195
+ flags: Array.from(t.flags.bits),
1196
+ })),
1197
+ });
1198
+ }
1199
+ /* The noise/scent seam (mod/hooks.ts saveNoiseScent). Faithful 4.2.6 omits the
1200
+ * heatmaps from the save - that is upstream's behaviour and upstream's bug - so
1201
+ * with no hook installed the `?? false` below IS the faithful answer. */
1202
+ const chunk = state.chunk.snapshotSquares(
1203
+ state.modHooks?.saveNoiseScent?.() ?? false,
1204
+ );
1205
+ const knownFeat = Array.from(state.known.feat);
1206
+ const savedLevelCache = serializeLevelCache(state.levelCache, ids);
1207
+ const autoinscriptions = state.autoinscribe
1208
+ ? serializeAutoinscriptions(state.autoinscribe, ids)
1209
+ : undefined;
1210
+ /* wr_ignore's rune-autoinscription block (save.c:586-605): the count of runes
1211
+ * carrying a note, then `wr_s16b(k)` + the note string for each. The index is
1212
+ * converted to its pack-stable runeKey (see obj/knowledge.ts runeKey); a rune
1213
+ * the running pack no longer builds is dropped. */
1214
+ const runeNoteEntries: Array<[string, string]> = [];
1215
+ const liveRuneNotes = state.runeNotes?.entries() ?? [];
1216
+ if (liveRuneNotes.length > 0) {
1217
+ const runes: readonly Rune[] = buildRuneList(state.runeEnv);
1218
+ for (const [i, note] of liveRuneNotes) {
1219
+ const rune = runes[i];
1220
+ if (!rune) continue;
1221
+ runeNoteEntries.push([runeKey(rune), note]);
1222
+ }
1223
+ }
1224
+ const runeNotes = runeNoteEntries.length > 0 ? runeNoteEntries : undefined;
1225
+ return {
1226
+ version: SAVE_VERSION,
1227
+ player: serializePlayer(state.actor.player, ids),
1228
+ actor: {
1229
+ grid: { x: state.actor.grid.x, y: state.actor.grid.y },
1230
+ energy: state.actor.energy,
1231
+ totalEnergy: state.actor.totalEnergy,
1232
+ },
1233
+ gear: {
1234
+ next: state.gear.next,
1235
+ pack: [...state.gear.pack],
1236
+ store: Array.from(state.gear.store.entries()).map(([h, obj]) => [
1237
+ h,
1238
+ serializeObject(obj, ids),
1239
+ ]),
1240
+ },
1241
+ /* C save.c:873-1045 omits the live dungeon objects, monsters, traps and
1242
+ * chunk-list data for a dead player. */
1243
+ ...(state.isDead
1244
+ ? { dungeonDepth: state.chunk.depth }
1245
+ : {
1246
+ chunk,
1247
+ featLegend: buildFeatLegend(chunk.feats, knownFeat, ids),
1248
+ monsters: state.monsters.map((m) =>
1249
+ m ? serializeMonster(m, ids) : null,
1250
+ ),
1251
+ groups: state.groups.map((g) =>
1252
+ g ? { index: g.index, leader: g.leader, members: [...g.members] } : null,
1253
+ ),
1254
+ floor,
1255
+ traps,
1256
+ }),
1257
+ rng: state.rng.getState(),
1258
+ turn: state.turn,
1259
+ playing: state.playing,
1260
+ isDead: state.isDead,
1261
+ flavor: ((): { aware: string[]; tried: string[] } => {
1262
+ const raw = flavor.snapshot();
1263
+ return {
1264
+ aware: kidxSetToIds(raw.aware, ids),
1265
+ tried: kidxSetToIds(raw.tried, ids),
1266
+ };
1267
+ })(),
1268
+ ...(everseen
1269
+ ? {
1270
+ everseen: ((): { kinds: string[]; egos: string[] } => {
1271
+ const raw = everseen.snapshot();
1272
+ const egos: string[] = [];
1273
+ for (const eidx of raw.egos) {
1274
+ const id = ids.egoIdOrNull(eidx);
1275
+ if (id !== null) egos.push(id);
1276
+ }
1277
+ return { kinds: kidxSetToIds(raw.kinds, ids), egos };
1278
+ })(),
1279
+ }
1280
+ : {}),
1281
+ seedFlavor,
1282
+ ...(state.options ? { options: state.options.snapshot() } : {}),
1283
+ ...(randartSeed ? { randartSeed } : {}),
1284
+ ...(state.artifacts
1285
+ ? {
1286
+ artifactsCreated: serializeArtifactsCreated(
1287
+ state.artifacts.snapshot(),
1288
+ ids,
1289
+ ),
1290
+ artifactsSeen: serializeArtifactFlags(
1291
+ state.artifacts.snapshotState().seen,
1292
+ ids,
1293
+ ),
1294
+ artifactsEverseen: serializeArtifactFlags(
1295
+ state.artifacts.snapshotState().everseen,
1296
+ ids,
1297
+ ),
1298
+ }
1299
+ : {}),
1300
+ ...(!state.isDead && savedLevelCache ? { levelCache: savedLevelCache } : {}),
1301
+ /* chunk->join of the level in play (gap 9.4/9.6): persisted so a first-visit
1302
+ * persistent level aligns its stairs after a reload. Omitted when empty. */
1303
+ ...(!state.isDead && state.currentJoins && state.currentJoins.length > 0
1304
+ ? {
1305
+ currentJoins: state.currentJoins.map((j) => ({
1306
+ x: j.grid.x,
1307
+ y: j.grid.y,
1308
+ feat: j.feat,
1309
+ /* save.c:850-866 writes x, y, feature and every SQUARE_SIZE byte. */
1310
+ info: Array.from({ length: SQUARE_SIZE }, (_, i) => j.info?.[i] ?? 0),
1311
+ })),
1312
+ }
1313
+ : {}),
1314
+ ...(autoinscriptions ? { autoinscriptions } : {}),
1315
+ ...(runeNotes ? { runeNotes } : {}),
1316
+ /*
1317
+ * Terrain-only Town cache (wr_chunks, save.c:1001-1044): C always writes
1318
+ * the Town chunk even when birth_levels_persist is off, so a dungeon save
1319
+ * after leaving town reloads the same shops/stairs without redrawing.
1320
+ */
1321
+ ...(() => {
1322
+ if (state.isDead || !state.townChunk) return {};
1323
+ const tc = state.townChunk.snapshotSquares();
1324
+ return {
1325
+ townChunk: tc,
1326
+ townFeatLegend: buildFeatLegend(tc.feats, [], ids),
1327
+ };
1328
+ })(),
1329
+ /* Town stores + accrued daycount (wr_stores / save.c:963). Persisted so the
1330
+ * home stash and shop stock survive save/load (gaps 12.1/12.2/12.3). */
1331
+ ...(() => {
1332
+ const stores = serializeStores(state.stores, ids);
1333
+ return stores ? { stores } : {};
1334
+ })(),
1335
+ ...(state.daycount ? { daycount: state.daycount } : {}),
1336
+ /* Minor persisted player fields (gap 12.6, wr_player). Omitted when at their
1337
+ * defaults so a clean save stays clean and old saves load with 0. */
1338
+ ...(state.restingTurn ? { restingTurn: state.restingTurn } : {}),
1339
+ ...(state.skipCmdCoercion ? { skipCmdCoercion: state.skipCmdCoercion } : {}),
1340
+ ...(state.unignoring ? { unignoring: state.unignoring } : {}),
1341
+ ...(state.nameSuffix ? { nameSuffix: state.nameSuffix } : {}),
1342
+ /* Running message log (gap 12.8, wr_messages): the 80 newest messages,
1343
+ * oldest-first. Omitted for an empty log. */
1344
+ ...(() => {
1345
+ const messages = serializeMessages(state.messages);
1346
+ return messages ? { messages } : {};
1347
+ })(),
1348
+ ...(!state.isDead
1349
+ ? {
1350
+ known: {
1351
+ feat: knownFeat,
1352
+ objects: Array.from(state.known.objects.entries()).map(([i, m]) => [
1353
+ i,
1354
+ { ch: m.ch, attr: m.attr },
1355
+ ]),
1356
+ },
1357
+ }
1358
+ : {}),
1359
+ ...(state.arenaLevel
1360
+ ? {
1361
+ arena: {
1362
+ oldGrid: {
1363
+ x: state.oldGrid?.x ?? state.actor.grid.x,
1364
+ y: state.oldGrid?.y ?? state.actor.grid.y,
1365
+ },
1366
+ },
1367
+ }
1368
+ : {}),
1369
+ ignore: serializeIgnore(state.ignore.snapshot(), ids),
1370
+ lore: Array.from(state.lore.entries()).map(([ridx, l]) => [
1371
+ ids.raceId(ridx),
1372
+ {
1373
+ sights: l.sights,
1374
+ deaths: l.deaths,
1375
+ pkills: l.pkills,
1376
+ thefts: l.thefts,
1377
+ tkills: l.tkills,
1378
+ wake: l.wake,
1379
+ ignore: l.ignore,
1380
+ dropGold: l.dropGold,
1381
+ dropItem: l.dropItem,
1382
+ castInnate: l.castInnate,
1383
+ castSpell: l.castSpell,
1384
+ blowTimesSeen: [...l.blowTimesSeen],
1385
+ blowKnown: [...l.blowKnown],
1386
+ flags: Array.from(l.flags.bits),
1387
+ spellFlags: Array.from(l.spellFlags.bits),
1388
+ allKnown: l.allKnown,
1389
+ armourKnown: l.armourKnown,
1390
+ dropKnown: l.dropKnown,
1391
+ sleepKnown: l.sleepKnown,
1392
+ spellFreqKnown: l.spellFreqKnown,
1393
+ innateFreqKnown: l.innateFreqKnown,
1394
+ },
1395
+ ]),
1396
+ };
1397
+ }
1398
+
1399
+ /** Rebuild the monster memory (absent in older saves: none). */
1400
+ export function deserializeLore(
1401
+ data: SavedGame["lore"],
1402
+ ids: ContentIdResolver,
1403
+ ): Map<number, MonsterLore> {
1404
+ const store = new Map<number, MonsterLore>();
1405
+ if (!data) return store;
1406
+ for (const [raceId, l] of data) {
1407
+ const ridx = ids.raceIndex(raceId);
1408
+ if (ridx === undefined) continue; // race gone (mod removed): drop its lore
1409
+ store.set(ridx, {
1410
+ sights: l.sights,
1411
+ deaths: l.deaths,
1412
+ pkills: l.pkills,
1413
+ thefts: l.thefts,
1414
+ tkills: l.tkills,
1415
+ wake: l.wake,
1416
+ ignore: l.ignore,
1417
+ dropGold: l.dropGold,
1418
+ dropItem: l.dropItem,
1419
+ castInnate: l.castInnate,
1420
+ castSpell: l.castSpell,
1421
+ blowTimesSeen: [...l.blowTimesSeen],
1422
+ blowKnown: [...l.blowKnown],
1423
+ flags: new FlagSet(Uint8Array.from(l.flags)),
1424
+ spellFlags: new FlagSet(Uint8Array.from(l.spellFlags)),
1425
+ allKnown: l.allKnown,
1426
+ armourKnown: l.armourKnown,
1427
+ dropKnown: l.dropKnown,
1428
+ sleepKnown: l.sleepKnown,
1429
+ spellFreqKnown: l.spellFreqKnown,
1430
+ innateFreqKnown: l.innateFreqKnown,
1431
+ });
1432
+ }
1433
+ return store;
1434
+ }
1435
+
1436
+ /**
1437
+ * Serialize the per-kind autoinscription registry (obj/knowledge.ts): every
1438
+ * kind with a registered note, keyed by its namespaced kind id (mod-stable,
1439
+ * like serializeObject). Returns undefined when nothing is registered, so a
1440
+ * clean game omits the block entirely. A kind whose id no longer resolves
1441
+ * (unbound in this pack) is dropped.
1442
+ */
1443
+ export function serializeAutoinscriptions(
1444
+ registry: AutoinscriptionRegistry,
1445
+ ids: ContentIdResolver,
1446
+ ): SavedAutoinscription[] | undefined {
1447
+ const out: SavedAutoinscription[] = [];
1448
+ for (const [kidx, note] of registry.entries()) {
1449
+ const kindId = ids.kindIdOrNull(kidx);
1450
+ if (kindId === null) continue; // kind unbound in this pack: drop
1451
+ const entry: SavedAutoinscription = { kindId };
1452
+ if (note.aware !== undefined) entry.aware = note.aware;
1453
+ if (note.unaware !== undefined) entry.unaware = note.unaware;
1454
+ out.push(entry);
1455
+ }
1456
+ return out.length > 0 ? out : undefined;
1457
+ }
1458
+
1459
+ /**
1460
+ * Restore serialized autoinscriptions into a registry (absent in older saves:
1461
+ * nothing to restore). A kind whose id is gone (its defining pack was removed)
1462
+ * is dropped, exactly like deserializeLore drops a removed race's memory.
1463
+ */
1464
+ export function deserializeAutoinscriptions(
1465
+ data: SavedAutoinscription[],
1466
+ registry: AutoinscriptionRegistry,
1467
+ ids: ContentIdResolver,
1468
+ ): void {
1469
+ for (const entry of data) {
1470
+ const kidx = ids.kindIndex(entry.kindId);
1471
+ if (kidx === undefined) continue; // kind gone (mod removed): drop its notes
1472
+ if (entry.aware !== undefined) registry.set(kidx, entry.aware, true);
1473
+ if (entry.unaware !== undefined) registry.set(kidx, entry.unaware, false);
1474
+ }
1475
+ }
1476
+
1477
+ /**
1478
+ * The player's ignore settings as they appear in the savefile: the wr_ignore /
1479
+ * wr_object_memory choices with every content reference converted to its
1480
+ * namespaced id.
1481
+ *
1482
+ * Upstream stores all three of these positionally - ignore_level[] by itype,
1483
+ * ego_ignore_types[] by eidx (save.c:530-541 / load.c:872-892), and the per-kind
1484
+ * ignore bits by kidx (save.c:399-407 / load.c:602-620) - so a data change that
1485
+ * re-orders e_info or k_info silently re-targets the player's choices onto
1486
+ * different items. `ignore_level` is genuinely keyed by the ITYPE_* enum, which
1487
+ * is compiled-in in both trees, so it stays an array; the ego and kind keys
1488
+ * become ids, matching every other content reference in the document
1489
+ * (MOD_LIFECYCLE decision 1).
1490
+ */
1491
+ export interface SavedIgnoreSettings {
1492
+ /** ignore_level[itype] (save.c:522-523), keyed by the compiled ITYPE_* enum. */
1493
+ level: number[];
1494
+ /** ego_ignore_types: one [namespaced ego id, itype] pair per set bit. */
1495
+ ego: Array<[string, number]>;
1496
+ /** kind_is_ignored_aware, by namespaced kind id. */
1497
+ kindAware: string[];
1498
+ /** kind_is_ignored_unaware, by namespaced kind id. */
1499
+ kindUnaware: string[];
1500
+ /** player->unignoring (save.c L491). Optional: absent in older saves. */
1501
+ unignoring?: boolean;
1502
+ }
1503
+
1504
+ /**
1505
+ * Convert an IgnoreSettings.snapshot() (raw kidx/eidx, the runtime keying
1506
+ * upstream also uses) into the id-keyed savefile form. A kind or ego the
1507
+ * running pack no longer binds is dropped, like deserializeAutoinscriptions.
1508
+ */
1509
+ export function serializeIgnore(
1510
+ data: IgnoreSettingsData,
1511
+ ids: ContentIdResolver,
1512
+ ): SavedIgnoreSettings {
1513
+ const ego: Array<[string, number]> = [];
1514
+ for (const key of data.ego) {
1515
+ const sep = key.indexOf(":");
1516
+ const eidx = Number(key.slice(0, sep));
1517
+ const itype = Number(key.slice(sep + 1));
1518
+ const egoId = ids.egoIdOrNull(eidx);
1519
+ if (egoId === null) continue;
1520
+ ego.push([egoId, itype]);
1521
+ }
1522
+ const kindIds = (kidxs: readonly number[]): string[] => {
1523
+ const out: string[] = [];
1524
+ for (const kidx of kidxs) {
1525
+ const id = ids.kindIdOrNull(kidx);
1526
+ if (id !== null) out.push(id);
1527
+ }
1528
+ return out;
1529
+ };
1530
+ return {
1531
+ level: [...data.level],
1532
+ ego,
1533
+ kindAware: kindIds(data.kindAware),
1534
+ kindUnaware: kindIds(data.kindUnaware),
1535
+ unignoring: data.unignoring ?? false,
1536
+ };
1537
+ }
1538
+
1539
+ /** The inverse of serializeIgnore; an id the pack no longer binds is dropped. */
1540
+ export function deserializeIgnore(
1541
+ data: SavedIgnoreSettings,
1542
+ ids: ContentIdResolver,
1543
+ ): IgnoreSettingsData {
1544
+ const ego: string[] = [];
1545
+ for (const [egoId, itype] of data.ego) {
1546
+ const eidx = ids.egoIndex(egoId);
1547
+ if (eidx === undefined) continue;
1548
+ ego.push(`${eidx}:${itype}`);
1549
+ }
1550
+ const kidxs = (idList: readonly string[]): number[] => {
1551
+ const out: number[] = [];
1552
+ for (const id of idList) {
1553
+ const kidx = ids.kindIndex(id);
1554
+ if (kidx !== undefined) out.push(kidx);
1555
+ }
1556
+ return out;
1557
+ };
1558
+ return {
1559
+ level: [...data.level],
1560
+ ego,
1561
+ kindAware: kidxs(data.kindAware),
1562
+ kindUnaware: kidxs(data.kindUnaware),
1563
+ unignoring: data.unignoring ?? false,
1564
+ };
1565
+ }
1566
+
1567
+ /**
1568
+ * kind->aware / kind->tried (wr_object_memory) and kind/ego ->everseen, by id.
1569
+ * Same reasoning as serializeIgnore: upstream writes them positionally, the
1570
+ * port's document keys them by namespaced id.
1571
+ */
1572
+ function kidxSetToIds(kidxs: readonly number[], ids: ContentIdResolver): string[] {
1573
+ const out: string[] = [];
1574
+ for (const kidx of kidxs) {
1575
+ const id = ids.kindIdOrNull(kidx);
1576
+ if (id !== null) out.push(id);
1577
+ }
1578
+ return out;
1579
+ }
1580
+
1581
+ function idsToKidxSet(idList: readonly string[], ids: ContentIdResolver): number[] {
1582
+ const out: number[] = [];
1583
+ for (const id of idList) {
1584
+ const kidx = ids.kindIndex(id);
1585
+ if (kidx !== undefined) out.push(kidx);
1586
+ }
1587
+ return out;
1588
+ }
1589
+
1590
+ /** Restore the id-keyed flavor block into the raw-kidx form FlavorKnowledge takes. */
1591
+ export function deserializeFlavor(
1592
+ data: { aware: string[]; tried: string[] },
1593
+ ids: ContentIdResolver,
1594
+ ): { aware: number[]; tried: number[] } {
1595
+ return {
1596
+ aware: idsToKidxSet(data.aware, ids),
1597
+ tried: idsToKidxSet(data.tried, ids),
1598
+ };
1599
+ }
1600
+
1601
+ /** Restore the id-keyed everseen block into the raw-index form it takes. */
1602
+ export function deserializeEverseen(
1603
+ data: { kinds: string[]; egos: string[] },
1604
+ ids: ContentIdResolver,
1605
+ ): { kinds: number[]; egos: number[] } {
1606
+ const egos: number[] = [];
1607
+ for (const id of data.egos) {
1608
+ const eidx = ids.egoIndex(id);
1609
+ if (eidx !== undefined) egos.push(eidx);
1610
+ }
1611
+ return { kinds: idsToKidxSet(data.kinds, ids), egos };
1612
+ }
1613
+
1614
+ /**
1615
+ * Build the feature index remap from a save's legend: each saved fidx maps to
1616
+ * the current pack's index for the same feature id. When the save and the
1617
+ * running pack agree (the common case), every entry is the identity; the map
1618
+ * is still applied so a re-ordered or extended terrain set loads correctly.
1619
+ * Throws on a legend id the current pack cannot resolve (a removed terrain -
1620
+ * graceful degradation is a Phase 2 quarantine concern).
1621
+ */
1622
+ export function buildFeatRemap(
1623
+ legend: Array<[number, string]> | undefined,
1624
+ ids: ContentIdResolver,
1625
+ ): Map<number, number> {
1626
+ const remap = new Map<number, number>();
1627
+ if (!legend) return remap;
1628
+ for (const [oldFidx, id] of legend) {
1629
+ const newFidx = ids.featIndex(id);
1630
+ if (newFidx === undefined) {
1631
+ throw new Error(`save: unknown terrain feature ${id}`);
1632
+ }
1633
+ remap.set(oldFidx, newFidx);
1634
+ }
1635
+ return remap;
1636
+ }
1637
+
1638
+ /** Apply a feature remap to a terrain index array in place (identity-safe). */
1639
+ function remapFeats(
1640
+ feats: readonly number[],
1641
+ remap: Map<number, number>,
1642
+ ): number[] {
1643
+ if (remap.size === 0) return [...feats];
1644
+ return feats.map((feat) => remap.get(feat) ?? feat);
1645
+ }
1646
+
1647
+ /** Rebuild the map knowledge (absent in older saves: all unknown). */
1648
+ export function deserializeKnown(
1649
+ data: SavedKnown | undefined,
1650
+ width: number,
1651
+ height: number,
1652
+ featRemap: Map<number, number>,
1653
+ ): KnownMap {
1654
+ const known = newKnownMap(width, height);
1655
+ if (!data) return known;
1656
+ const feat = remapFeats(
1657
+ data.feat.slice(0, known.feat.length),
1658
+ featRemap,
1659
+ );
1660
+ known.feat.set(feat);
1661
+ for (const [i, m] of data.objects) {
1662
+ known.objects.set(i, { ch: m.ch, attr: m.attr });
1663
+ }
1664
+ return known;
1665
+ }
1666
+
1667
+ /** Rebuild a Gear store from its saved form. */
1668
+ export function deserializeGear(
1669
+ data: SavedGame["gear"],
1670
+ reg: ObjRegistry,
1671
+ ids: ContentIdResolver,
1672
+ ): Gear {
1673
+ const store = new Map<number, GameObject>();
1674
+ for (const [h, saved] of data.store) {
1675
+ store.set(h, deserializeObject(saved, reg, ids));
1676
+ }
1677
+ return { store, next: data.next, pack: [...data.pack] };
1678
+ }
1679
+
1680
+ /** Rebuild the floor pile map (grid-keyed, pile order preserved). */
1681
+ export function deserializeFloor(
1682
+ data: SavedGame["floor"],
1683
+ reg: ObjRegistry,
1684
+ width: number,
1685
+ ids: ContentIdResolver,
1686
+ ): Map<number, GameObject[]> {
1687
+ const floor = new Map<number, GameObject[]>();
1688
+ for (const entry of data ?? []) {
1689
+ floor.set(
1690
+ entry.y * width + entry.x,
1691
+ entry.objs.map((o) => deserializeObject(o, reg, ids)),
1692
+ );
1693
+ }
1694
+ return floor;
1695
+ }
1696
+
1697
+ /**
1698
+ * Rebuild the trap map from saved instances against the bound kinds. This is
1699
+ * rd_trap (load.c:359) plus its caller rd_traps_aux (L1473): the per-trap read
1700
+ * is not a separate function here because the port stores traps per grid rather
1701
+ * than as a linked list off the square.
1702
+ */
1703
+ export function deserializeTraps(
1704
+ data: SavedGame["traps"],
1705
+ kinds: readonly TrapKind[],
1706
+ width: number,
1707
+ ids: ContentIdResolver,
1708
+ onDecoy?: (grid: Loc) => void,
1709
+ ): Map<number, Trap[]> {
1710
+ const traps = new Map<number, Trap[]>();
1711
+ for (const entry of data ?? []) {
1712
+ traps.set(
1713
+ entry.y * width + entry.x,
1714
+ entry.traps.map((t) => {
1715
+ const tidx = ids.trapIndex(t.trapId);
1716
+ const kind = tidx !== undefined ? kinds[tidx] : undefined;
1717
+ if (kind === undefined || tidx === undefined) {
1718
+ throw new Error(`save: unknown trap kind ${t.trapId}`);
1719
+ }
1720
+ const grid = loc(t.grid.x, t.grid.y);
1721
+ if (kind.name === "decoy" || kind.desc === "decoy") onDecoy?.(grid);
1722
+ return {
1723
+ tidx,
1724
+ kind,
1725
+ grid,
1726
+ power: t.power,
1727
+ timeout: t.timeout,
1728
+ flags: new FlagSet(Uint8Array.from(t.flags)),
1729
+ };
1730
+ }),
1731
+ );
1732
+ }
1733
+ return traps;
1734
+ }
1735
+
1736
+ /* ------------------------------------------------------------------ *
1737
+ * Town stores (store.c wr_stores / rd_stores, save.c:744-765).
1738
+ * ------------------------------------------------------------------ */
1739
+
1740
+ /** One serialized store: its proprietor index and its full stock, in order. */
1741
+ export interface SavedStore {
1742
+ /** store->owner->oidx (save.c:754): the current proprietor's index. */
1743
+ ownerIndex: number;
1744
+ /** store->stock (save.c:761), head-first; the home stash for FEAT_HOME. */
1745
+ stock: SavedObject[];
1746
+ }
1747
+
1748
+ /**
1749
+ * Serialize the live town stores (wr_stores): each shop's proprietor and stock,
1750
+ * in registry order. Returns undefined when the game has no stores yet (never
1751
+ * reached town), so a fresh dungeon-start save omits the block entirely.
1752
+ */
1753
+ export function serializeStores(
1754
+ stores: readonly Store[] | undefined,
1755
+ ids: ContentIdResolver,
1756
+ ): SavedStore[] | undefined {
1757
+ if (!stores || stores.length === 0) return undefined;
1758
+ return stores.map((store) => ({
1759
+ ownerIndex: store.owner.index,
1760
+ stock: store.stock.map((o) => serializeObject(o, ids)),
1761
+ }));
1762
+ }
1763
+
1764
+ /**
1765
+ * Rebuild the live town stores (rd_stores): re-bind each store's immutable
1766
+ * tables from the pack (owners, stocking tables, buy list, bounds) and overlay
1767
+ * the saved proprietor + stock. Matches upstream's positional keying (store i
1768
+ * <-> saved i). Draws NO RNG - the caller has already restored the exact
1769
+ * stream, so store restore must not perturb it (decision 22). A saved store
1770
+ * beyond the current registry length is dropped; a registry store with no saved
1771
+ * counterpart keeps an empty, default-owner shell (re-stocked on town entry).
1772
+ */
1773
+ export function deserializeStores(
1774
+ bound: readonly BoundStore[],
1775
+ saved: SavedStore[] | undefined,
1776
+ reg: ObjRegistry,
1777
+ ids: ContentIdResolver,
1778
+ storeInvenMax: number,
1779
+ ): Store[] {
1780
+ return bound.map((b, i): Store => {
1781
+ const s = saved?.[i];
1782
+ const owner =
1783
+ (s ? b.owners.find((o) => o.index === s.ownerIndex) : undefined) ??
1784
+ b.owners[0];
1785
+ if (!owner) throw new Error(`save: store ${b.featName} has no owners`);
1786
+ return {
1787
+ feat: b.feat,
1788
+ featName: b.featName,
1789
+ owners: b.owners,
1790
+ owner,
1791
+ alwaysTable: b.alwaysTable,
1792
+ normalTable: b.normalTable,
1793
+ buy: b.buy,
1794
+ turnover: b.turnover,
1795
+ normalStockMin: b.normalStockMin,
1796
+ normalStockMax: b.normalStockMax,
1797
+ stock: s ? s.stock.map((o) => deserializeObject(o, reg, ids)) : [],
1798
+ stockSize: storeInvenMax,
1799
+ };
1800
+ });
1801
+ }
1802
+
1803
+ /** aup_info id list -> the boolean[] by aidx that ArtifactState.restore wants. */
1804
+ export function deserializeArtifactsCreated(
1805
+ saved: string[] | undefined,
1806
+ length: number,
1807
+ ids: ContentIdResolver,
1808
+ ): boolean[] {
1809
+ const out = new Array<boolean>(length).fill(false);
1810
+ if (!saved) return out;
1811
+ for (const id of saved) {
1812
+ const i = ids.artifactIndex(id);
1813
+ if (i !== undefined && i < length) out[i] = true;
1814
+ }
1815
+ return out;
1816
+ }
1817
+
1818
+ /** Restore one aup_info boolean field from stable artifact ids. */
1819
+ export function deserializeArtifactFlags(
1820
+ saved: string[] | undefined,
1821
+ length: number,
1822
+ ids: ContentIdResolver,
1823
+ ): boolean[] {
1824
+ const out = new Array<boolean>(length).fill(false);
1825
+ for (const id of saved ?? []) {
1826
+ const i = ids.artifactIndex(id);
1827
+ if (i !== undefined && i < length) out[i] = true;
1828
+ }
1829
+ return out;
1830
+ }
1831
+
1832
+ /**
1833
+ * Rebuild a chunk of the saved dimensions and restore its squares, remapping
1834
+ * the terrain grid through the save's feature legend so feature references
1835
+ * survive a pack change.
1836
+ */
1837
+ export function deserializeChunk(
1838
+ data: ChunkSquaresData,
1839
+ features: Chunk["features"],
1840
+ featRemap: Map<number, number>,
1841
+ ): Chunk {
1842
+ const chunk = new Chunk(features, data.height, data.width);
1843
+ /* load.c:1307-1355 decodes into a fresh cave. Keep the JSON source intact
1844
+ * while applying the equivalent feature remap to fresh data. */
1845
+ const remapped =
1846
+ featRemap.size > 0
1847
+ ? { ...data, feats: remapFeats(data.feats, featRemap) }
1848
+ : data;
1849
+ chunk.restoreSquares(remapped);
1850
+ return chunk;
1851
+ }
1852
+
1853
+ /* ------------------------------------------------------------------ *
1854
+ * birth_levels_persist frozen-level cache (game/context.ts StoredLevel).
1855
+ * ------------------------------------------------------------------ */
1856
+
1857
+ /** One serialized frozen level: the same field-set as the current level. */
1858
+ export interface SavedStoredLevel {
1859
+ depth: number;
1860
+ /** Game turn the level was frozen at (restore_monsters recovery baseline). */
1861
+ turn: number;
1862
+ chunk: ChunkSquaresData;
1863
+ featLegend?: Array<[number, string]>;
1864
+ monsters: Array<SavedMonster | null>;
1865
+ groups: Array<MonsterGroup | null>;
1866
+ floor: Array<{ x: number; y: number; objs: SavedObject[] }>;
1867
+ traps: Array<{ x: number; y: number; traps: SavedTrap[] }>;
1868
+ known: SavedKnown;
1869
+ decoy?: { x: number; y: number } | null;
1870
+ /** chunk->join stair connectors (generate.c L1203-1214); absent in old saves. */
1871
+ join?: Array<{ x: number; y: number; feat: number; info?: number[] }>;
1872
+ }
1873
+
1874
+ /** Serialize one frozen level, reusing the current-level serializers. */
1875
+ function serializeStoredLevel(
1876
+ depth: number,
1877
+ level: StoredLevel,
1878
+ ids: ContentIdResolver,
1879
+ ): SavedStoredLevel {
1880
+ const floor: SavedStoredLevel["floor"] = [];
1881
+ for (const pile of level.floor.values()) {
1882
+ const head = pile[0];
1883
+ if (!head || !head.grid) continue;
1884
+ floor.push({
1885
+ x: head.grid.x,
1886
+ y: head.grid.y,
1887
+ objs: pile.map((o) => serializeObject(o, ids)),
1888
+ });
1889
+ }
1890
+ const traps: SavedStoredLevel["traps"] = [];
1891
+ for (const list of level.traps.values()) {
1892
+ const head = list[0];
1893
+ if (!head) continue;
1894
+ traps.push({
1895
+ x: head.grid.x,
1896
+ y: head.grid.y,
1897
+ traps: list.map((t) => ({
1898
+ trapId: ids.trapId(t.tidx),
1899
+ grid: { x: t.grid.x, y: t.grid.y },
1900
+ power: t.power,
1901
+ timeout: t.timeout,
1902
+ flags: Array.from(t.flags.bits),
1903
+ })),
1904
+ });
1905
+ }
1906
+ const chunk = level.chunk.snapshotSquares();
1907
+ const knownFeat = Array.from(level.known.feat);
1908
+ return {
1909
+ depth,
1910
+ turn: level.turn,
1911
+ chunk,
1912
+ featLegend: buildFeatLegend(chunk.feats, knownFeat, ids),
1913
+ monsters: level.monsters.map((m) => (m ? serializeMonster(m, ids) : null)),
1914
+ groups: level.groups.map((g) =>
1915
+ g ? { index: g.index, leader: g.leader, members: [...g.members] } : null,
1916
+ ),
1917
+ floor,
1918
+ traps,
1919
+ known: {
1920
+ feat: knownFeat,
1921
+ objects: Array.from(level.known.objects.entries()).map(([i, m]) => [
1922
+ i,
1923
+ { ch: m.ch, attr: m.attr },
1924
+ ]),
1925
+ },
1926
+ decoy: level.decoy ? { x: level.decoy.x, y: level.decoy.y } : null,
1927
+ join: level.join.map((j) => ({
1928
+ x: j.grid.x,
1929
+ y: j.grid.y,
1930
+ feat: j.feat,
1931
+ info: Array.from({ length: SQUARE_SIZE }, (_, i) => j.info?.[i] ?? 0),
1932
+ })),
1933
+ };
1934
+ }
1935
+
1936
+ /** Serialize the whole frozen-level cache (empty / absent => omitted). */
1937
+ export function serializeLevelCache(
1938
+ cache: Map<number, StoredLevel> | undefined,
1939
+ ids: ContentIdResolver,
1940
+ ): SavedStoredLevel[] | undefined {
1941
+ if (!cache || cache.size === 0) return undefined;
1942
+ return Array.from(cache.entries()).map(([depth, level]) =>
1943
+ serializeStoredLevel(depth, level, ids),
1944
+ );
1945
+ }
1946
+
1947
+ /**
1948
+ * Rebuild the frozen-level cache (absent in older / default saves: empty).
1949
+ * Reuses the current-level deserializers so a cached level round-trips exactly
1950
+ * like the live one, including per-level feature-legend remapping.
1951
+ */
1952
+ export function deserializeLevelCache(
1953
+ data: SavedStoredLevel[] | undefined,
1954
+ features: Chunk["features"],
1955
+ monsters: MonsterRegistry,
1956
+ objects: ObjRegistry,
1957
+ traps: readonly TrapKind[] | null | undefined,
1958
+ ids: ContentIdResolver,
1959
+ ): Map<number, StoredLevel> {
1960
+ const cache = new Map<number, StoredLevel>();
1961
+ if (!data) return cache;
1962
+ for (const entry of data) {
1963
+ const featRemap = buildFeatRemap(entry.featLegend, ids);
1964
+ const chunk = deserializeChunk(entry.chunk, features, featRemap);
1965
+ chunk.turn = entry.turn;
1966
+ cache.set(entry.depth, {
1967
+ chunk,
1968
+ monsters: entry.monsters.map((m) =>
1969
+ m ? deserializeMonster(m, monsters, objects, ids) : null,
1970
+ ),
1971
+ groups: entry.groups.map((g) =>
1972
+ g
1973
+ ? { index: g.index, leader: g.leader, members: [...g.members] }
1974
+ : null,
1975
+ ),
1976
+ floor: deserializeFloor(entry.floor, objects, chunk.width, ids),
1977
+ traps: traps
1978
+ ? deserializeTraps(entry.traps, traps, chunk.width, ids)
1979
+ : new Map(),
1980
+ known: deserializeKnown(entry.known, chunk.width, chunk.height, featRemap),
1981
+ decoy: entry.decoy ? loc(entry.decoy.x, entry.decoy.y) : null,
1982
+ turn: entry.turn,
1983
+ /* chunk->join stair connectors; tolerate absence for pre-field saves. */
1984
+ /* load.c:1366-1383 restores connector info; load.c:1653-1678 remaps
1985
+ * the saved feature through the current feature table. */
1986
+ join: (entry.join ?? []).map((j) => ({
1987
+ grid: loc(j.x, j.y),
1988
+ feat: featRemap.get(j.feat) ?? j.feat,
1989
+ ...(j.info ? { info: [...j.info] } : {}),
1990
+ })),
1991
+ });
1992
+ }
1993
+ return cache;
1994
+ }
1995
+
1996
+ /* ------------------------------------------------------------------ *
1997
+ * Stamped bytes (the file/localStorage form).
1998
+ * ------------------------------------------------------------------ */
1999
+
2000
+ /**
2001
+ * JSON-encode a save, optionally compress it, and stamp it with the integrity
2002
+ * trailer (16b).
2003
+ *
2004
+ * ORDER: JSON -> codec -> stamp. The digest therefore covers the bytes that are
2005
+ * actually stored, and the trailer stays findable without running a decompressor
2006
+ * first - which matters because an unknown codec must still be diagnosable.
2007
+ * Passing no codec writes the bare JSON every earlier build wrote.
2008
+ */
2009
+ export function encodeSavedGame(
2010
+ save: SavedGame,
2011
+ provider: SaveIntegrity = fnv1aIntegrity,
2012
+ codec?: SaveCodec,
2013
+ ): Uint8Array {
2014
+ const json = new TextEncoder().encode(JSON.stringify(save));
2015
+ return stampSavefile(codec ? applyCodec(json, codec) : json, provider);
2016
+ }
2017
+
2018
+ /** The decoded form of a stamped save. */
2019
+ export interface DecodedSave {
2020
+ save: SavedGame | null;
2021
+ /** The integrity digest matched. */
2022
+ verified: boolean;
2023
+ /** No trailer was present at all. */
2024
+ unstamped: boolean;
2025
+ /** The codec that wrote it, or null for an uncompressed save. */
2026
+ codecId?: string | null;
2027
+ /**
2028
+ * Set when the save names a codec this build does not have - a save from a
2029
+ * NEWER build, which is a different thing from a corrupt one and must be
2030
+ * reported differently: nothing is wrong with the file, and telling the player
2031
+ * it is damaged would invite them to delete a perfectly good character.
2032
+ */
2033
+ unknownCodec?: string;
2034
+ }
2035
+
2036
+ /**
2037
+ * Verify and parse stamped save bytes. A failed digest still parses (the
2038
+ * warn-and-label posture of decision 16b - the deterrent is honest, not a
2039
+ * lock), with verified=false for the caller to surface.
2040
+ *
2041
+ * `codecs` are the compressors this build can run. An uncompressed save needs
2042
+ * none, so a caller that never compresses can keep ignoring this argument.
2043
+ */
2044
+ export function decodeSavedGame(
2045
+ bytes: Uint8Array,
2046
+ provider: SaveIntegrity = fnv1aIntegrity,
2047
+ codecs: readonly SaveCodec[] = [],
2048
+ ): DecodedSave {
2049
+ const result = verifyStampedSavefile(bytes, provider);
2050
+ const { codecId, body } = stripCodec(result.payload);
2051
+ const base = {
2052
+ verified: result.verified,
2053
+ unstamped: result.unstamped ?? false,
2054
+ codecId,
2055
+ };
2056
+ let payload = body;
2057
+ if (codecId !== null) {
2058
+ const codec = findCodec(codecId, codecs);
2059
+ if (!codec) return { ...base, save: null, unknownCodec: codecId };
2060
+ try {
2061
+ payload = codec.decompress(body);
2062
+ } catch {
2063
+ /* A codec we HAVE that cannot read these bytes is genuine damage. */
2064
+ return { ...base, save: null };
2065
+ }
2066
+ }
2067
+ let save: SavedGame | null = null;
2068
+ try {
2069
+ save = JSON.parse(new TextDecoder().decode(payload)) as SavedGame;
2070
+ } catch {
2071
+ save = null;
2072
+ }
2073
+ return { ...base, save };
2074
+ }