@rpgm-tools/neo-angband-core 0.18.0 → 0.19.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 (848) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/perceive.d.ts.map +1 -1
  3. package/dist/agent/perceive.js +2 -2
  4. package/dist/agent/perceive.js.map +1 -1
  5. package/dist/combat/blow-vectors.d.ts +78 -0
  6. package/dist/combat/blow-vectors.d.ts.map +1 -0
  7. package/dist/combat/blow-vectors.fixtures.d.ts +19 -0
  8. package/dist/combat/blow-vectors.fixtures.d.ts.map +1 -0
  9. package/dist/combat/blow-vectors.fixtures.js +81 -0
  10. package/dist/combat/blow-vectors.fixtures.js.map +1 -0
  11. package/dist/combat/blow-vectors.js +195 -0
  12. package/dist/combat/blow-vectors.js.map +1 -0
  13. package/dist/combat/brand-slay.d.ts +22 -9
  14. package/dist/combat/brand-slay.d.ts.map +1 -1
  15. package/dist/combat/brand-slay.js +37 -10
  16. package/dist/combat/brand-slay.js.map +1 -1
  17. package/dist/combat/index.d.ts +1 -1
  18. package/dist/combat/index.js +1 -1
  19. package/dist/combat/melee.d.ts +25 -13
  20. package/dist/combat/melee.d.ts.map +1 -1
  21. package/dist/combat/melee.js +23 -19
  22. package/dist/combat/melee.js.map +1 -1
  23. package/dist/combat/mon-melee.d.ts +166 -7
  24. package/dist/combat/mon-melee.d.ts.map +1 -1
  25. package/dist/combat/mon-melee.js +469 -338
  26. package/dist/combat/mon-melee.js.map +1 -1
  27. package/dist/combat/ranged.d.ts +15 -13
  28. package/dist/combat/ranged.d.ts.map +1 -1
  29. package/dist/combat/ranged.js +31 -29
  30. package/dist/combat/ranged.js.map +1 -1
  31. package/dist/constants.d.ts +14 -0
  32. package/dist/constants.d.ts.map +1 -1
  33. package/dist/constants.js +14 -0
  34. package/dist/constants.js.map +1 -1
  35. package/dist/datafile.d.ts +48 -0
  36. package/dist/datafile.d.ts.map +1 -0
  37. package/dist/datafile.js +102 -0
  38. package/dist/datafile.js.map +1 -0
  39. package/dist/effects/effect-info-registry.d.ts +237 -0
  40. package/dist/effects/effect-info-registry.d.ts.map +1 -0
  41. package/dist/effects/effect-info-registry.js +138 -0
  42. package/dist/effects/effect-info-registry.js.map +1 -0
  43. package/dist/effects/effect-info-vectors.d.ts +92 -0
  44. package/dist/effects/effect-info-vectors.d.ts.map +1 -0
  45. package/dist/effects/effect-info-vectors.fixtures.d.ts +18 -0
  46. package/dist/effects/effect-info-vectors.fixtures.d.ts.map +1 -0
  47. package/dist/effects/effect-info-vectors.fixtures.js +51 -0
  48. package/dist/effects/effect-info-vectors.fixtures.js.map +1 -0
  49. package/dist/effects/effect-info-vectors.js +315 -0
  50. package/dist/effects/effect-info-vectors.js.map +1 -0
  51. package/dist/effects/effect-info.d.ts +5 -1
  52. package/dist/effects/effect-info.d.ts.map +1 -1
  53. package/dist/effects/effect-info.js +245 -143
  54. package/dist/effects/effect-info.js.map +1 -1
  55. package/dist/effects/effect.d.ts +4 -3
  56. package/dist/effects/effect.d.ts.map +1 -1
  57. package/dist/effects/effect.js +88 -70
  58. package/dist/effects/effect.js.map +1 -1
  59. package/dist/effects/handlers.d.ts +1 -1
  60. package/dist/effects/handlers.d.ts.map +1 -1
  61. package/dist/effects/handlers.js +5 -2
  62. package/dist/effects/handlers.js.map +1 -1
  63. package/dist/effects/interpreter.d.ts +11 -0
  64. package/dist/effects/interpreter.d.ts.map +1 -1
  65. package/dist/effects/interpreter.js +13 -0
  66. package/dist/effects/interpreter.js.map +1 -1
  67. package/dist/game/cave-cmd.d.ts +9 -5
  68. package/dist/game/cave-cmd.d.ts.map +1 -1
  69. package/dist/game/cave-cmd.js +56 -11
  70. package/dist/game/cave-cmd.js.map +1 -1
  71. package/dist/game/cave-square.d.ts +42 -0
  72. package/dist/game/cave-square.d.ts.map +1 -0
  73. package/dist/game/cave-square.js +73 -0
  74. package/dist/game/cave-square.js.map +1 -0
  75. package/dist/game/char-sheet.d.ts +12 -8
  76. package/dist/game/char-sheet.d.ts.map +1 -1
  77. package/dist/game/char-sheet.js +41 -8
  78. package/dist/game/char-sheet.js.map +1 -1
  79. package/dist/game/chest.d.ts +2 -3
  80. package/dist/game/chest.d.ts.map +1 -1
  81. package/dist/game/chest.js +17 -6
  82. package/dist/game/chest.js.map +1 -1
  83. package/dist/game/context.d.ts +213 -22
  84. package/dist/game/context.d.ts.map +1 -1
  85. package/dist/game/context.js +109 -17
  86. package/dist/game/context.js.map +1 -1
  87. package/dist/game/describe.d.ts +13 -0
  88. package/dist/game/describe.d.ts.map +1 -1
  89. package/dist/game/describe.js +32 -4
  90. package/dist/game/describe.js.map +1 -1
  91. package/dist/game/display.d.ts +97 -8
  92. package/dist/game/display.d.ts.map +1 -1
  93. package/dist/game/display.js +157 -13
  94. package/dist/game/display.js.map +1 -1
  95. package/dist/game/dump-level.d.ts.map +1 -1
  96. package/dist/game/dump-level.js +3 -2
  97. package/dist/game/dump-level.js.map +1 -1
  98. package/dist/game/effect-attack.d.ts.map +1 -1
  99. package/dist/game/effect-attack.js +36 -27
  100. package/dist/game/effect-attack.js.map +1 -1
  101. package/dist/game/effect-detect.d.ts +4 -4
  102. package/dist/game/effect-detect.d.ts.map +1 -1
  103. package/dist/game/effect-detect.js +42 -9
  104. package/dist/game/effect-detect.js.map +1 -1
  105. package/dist/game/effect-env.d.ts +7 -1
  106. package/dist/game/effect-env.d.ts.map +1 -1
  107. package/dist/game/effect-env.js +1 -0
  108. package/dist/game/effect-env.js.map +1 -1
  109. package/dist/game/effect-general.d.ts +5 -3
  110. package/dist/game/effect-general.d.ts.map +1 -1
  111. package/dist/game/effect-general.js +94 -43
  112. package/dist/game/effect-general.js.map +1 -1
  113. package/dist/game/effect-item.d.ts +2 -1
  114. package/dist/game/effect-item.d.ts.map +1 -1
  115. package/dist/game/effect-item.js +136 -82
  116. package/dist/game/effect-item.js.map +1 -1
  117. package/dist/game/effect-melee.d.ts +10 -4
  118. package/dist/game/effect-melee.d.ts.map +1 -1
  119. package/dist/game/effect-melee.js +44 -19
  120. package/dist/game/effect-melee.js.map +1 -1
  121. package/dist/game/effect-monster.d.ts +26 -4
  122. package/dist/game/effect-monster.d.ts.map +1 -1
  123. package/dist/game/effect-monster.js +52 -9
  124. package/dist/game/effect-monster.js.map +1 -1
  125. package/dist/game/effect-teleport.d.ts +58 -19
  126. package/dist/game/effect-teleport.d.ts.map +1 -1
  127. package/dist/game/effect-teleport.js +143 -27
  128. package/dist/game/effect-teleport.js.map +1 -1
  129. package/dist/game/effect-terrain.d.ts +1 -3
  130. package/dist/game/effect-terrain.d.ts.map +1 -1
  131. package/dist/game/effect-terrain.js +38 -22
  132. package/dist/game/effect-terrain.js.map +1 -1
  133. package/dist/game/equip-cmp.d.ts.map +1 -1
  134. package/dist/game/equip-cmp.js +10 -7
  135. package/dist/game/equip-cmp.js.map +1 -1
  136. package/dist/game/floor.d.ts +16 -4
  137. package/dist/game/floor.d.ts.map +1 -1
  138. package/dist/game/floor.js +22 -4
  139. package/dist/game/floor.js.map +1 -1
  140. package/dist/game/gear.d.ts +101 -13
  141. package/dist/game/gear.d.ts.map +1 -1
  142. package/dist/game/gear.js +183 -24
  143. package/dist/game/gear.js.map +1 -1
  144. package/dist/game/harness.d.ts +2 -1
  145. package/dist/game/harness.d.ts.map +1 -1
  146. package/dist/game/harness.js +12 -1
  147. package/dist/game/harness.js.map +1 -1
  148. package/dist/game/ignore-cmd.d.ts +47 -8
  149. package/dist/game/ignore-cmd.d.ts.map +1 -1
  150. package/dist/game/ignore-cmd.js +76 -8
  151. package/dist/game/ignore-cmd.js.map +1 -1
  152. package/dist/game/known.d.ts +223 -34
  153. package/dist/game/known.d.ts.map +1 -1
  154. package/dist/game/known.js +301 -67
  155. package/dist/game/known.js.map +1 -1
  156. package/dist/game/loop.d.ts +4 -3
  157. package/dist/game/loop.d.ts.map +1 -1
  158. package/dist/game/loop.js +49 -29
  159. package/dist/game/loop.js.map +1 -1
  160. package/dist/game/lore-color.d.ts.map +1 -1
  161. package/dist/game/lore-color.js +13 -1
  162. package/dist/game/lore-color.js.map +1 -1
  163. package/dist/game/mon-cast.d.ts +3 -4
  164. package/dist/game/mon-cast.d.ts.map +1 -1
  165. package/dist/game/mon-cast.js +30 -2
  166. package/dist/game/mon-cast.js.map +1 -1
  167. package/dist/game/mon-cmd.d.ts +4 -5
  168. package/dist/game/mon-cmd.d.ts.map +1 -1
  169. package/dist/game/mon-cmd.js +80 -27
  170. package/dist/game/mon-cmd.js.map +1 -1
  171. package/dist/game/mon-death.d.ts +8 -4
  172. package/dist/game/mon-death.d.ts.map +1 -1
  173. package/dist/game/mon-death.js +6 -9
  174. package/dist/game/mon-death.js.map +1 -1
  175. package/dist/game/mon-group.d.ts +9 -5
  176. package/dist/game/mon-group.d.ts.map +1 -1
  177. package/dist/game/mon-group.js +9 -5
  178. package/dist/game/mon-group.js.map +1 -1
  179. package/dist/game/mon-message.d.ts +90 -40
  180. package/dist/game/mon-message.d.ts.map +1 -1
  181. package/dist/game/mon-message.js +267 -59
  182. package/dist/game/mon-message.js.map +1 -1
  183. package/dist/game/mon-place.d.ts +11 -1
  184. package/dist/game/mon-place.d.ts.map +1 -1
  185. package/dist/game/mon-place.js +15 -3
  186. package/dist/game/mon-place.js.map +1 -1
  187. package/dist/game/mon-ranged.d.ts +2 -1
  188. package/dist/game/mon-ranged.d.ts.map +1 -1
  189. package/dist/game/mon-ranged.js +11 -5
  190. package/dist/game/mon-ranged.js.map +1 -1
  191. package/dist/game/mon-side.d.ts +3 -2
  192. package/dist/game/mon-side.d.ts.map +1 -1
  193. package/dist/game/mon-side.js +15 -14
  194. package/dist/game/mon-side.js.map +1 -1
  195. package/dist/game/monster-turn.d.ts +20 -10
  196. package/dist/game/monster-turn.d.ts.map +1 -1
  197. package/dist/game/monster-turn.js +110 -83
  198. package/dist/game/monster-turn.js.map +1 -1
  199. package/dist/game/notice.d.ts +36 -0
  200. package/dist/game/notice.d.ts.map +1 -0
  201. package/dist/game/notice.js +79 -0
  202. package/dist/game/notice.js.map +1 -0
  203. package/dist/game/obj-cmd.d.ts +14 -3
  204. package/dist/game/obj-cmd.d.ts.map +1 -1
  205. package/dist/game/obj-cmd.js +109 -35
  206. package/dist/game/obj-cmd.js.map +1 -1
  207. package/dist/game/obj-list.d.ts +13 -2
  208. package/dist/game/obj-list.d.ts.map +1 -1
  209. package/dist/game/obj-list.js +42 -27
  210. package/dist/game/obj-list.js.map +1 -1
  211. package/dist/game/object-inspect.d.ts +9 -0
  212. package/dist/game/object-inspect.d.ts.map +1 -1
  213. package/dist/game/object-inspect.js +4 -0
  214. package/dist/game/object-inspect.js.map +1 -1
  215. package/dist/game/pickup.d.ts +0 -2
  216. package/dist/game/pickup.d.ts.map +1 -1
  217. package/dist/game/pickup.js +18 -3
  218. package/dist/game/pickup.js.map +1 -1
  219. package/dist/game/player-side-vectors.d.ts +174 -0
  220. package/dist/game/player-side-vectors.d.ts.map +1 -0
  221. package/dist/game/player-side-vectors.fixtures.d.ts +33 -0
  222. package/dist/game/player-side-vectors.fixtures.d.ts.map +1 -0
  223. package/dist/game/player-side-vectors.fixtures.js +191 -0
  224. package/dist/game/player-side-vectors.fixtures.js.map +1 -0
  225. package/dist/game/player-side-vectors.js +292 -0
  226. package/dist/game/player-side-vectors.js.map +1 -0
  227. package/dist/game/player-side.d.ts +129 -1
  228. package/dist/game/player-side.d.ts.map +1 -1
  229. package/dist/game/player-side.js +520 -338
  230. package/dist/game/player-side.js.map +1 -1
  231. package/dist/game/player-turn.d.ts +26 -7
  232. package/dist/game/player-turn.d.ts.map +1 -1
  233. package/dist/game/player-turn.js +180 -33
  234. package/dist/game/player-turn.js.map +1 -1
  235. package/dist/game/poly.d.ts +38 -0
  236. package/dist/game/poly.d.ts.map +1 -0
  237. package/dist/game/poly.js +82 -0
  238. package/dist/game/poly.js.map +1 -0
  239. package/dist/game/project-cast.d.ts +20 -3
  240. package/dist/game/project-cast.d.ts.map +1 -1
  241. package/dist/game/project-cast.js +49 -6
  242. package/dist/game/project-cast.js.map +1 -1
  243. package/dist/game/project-feat-vectors.d.ts +124 -0
  244. package/dist/game/project-feat-vectors.d.ts.map +1 -0
  245. package/dist/game/project-feat-vectors.fixtures.d.ts +13 -0
  246. package/dist/game/project-feat-vectors.fixtures.d.ts.map +1 -0
  247. package/dist/game/project-feat-vectors.fixtures.js +94 -0
  248. package/dist/game/project-feat-vectors.fixtures.js.map +1 -0
  249. package/dist/game/project-feat-vectors.js +229 -0
  250. package/dist/game/project-feat-vectors.js.map +1 -0
  251. package/dist/game/project-feat.d.ts +53 -1
  252. package/dist/game/project-feat.d.ts.map +1 -1
  253. package/dist/game/project-feat.js +374 -247
  254. package/dist/game/project-feat.js.map +1 -1
  255. package/dist/game/project-monster.d.ts +21 -8
  256. package/dist/game/project-monster.d.ts.map +1 -1
  257. package/dist/game/project-monster.js +45 -21
  258. package/dist/game/project-monster.js.map +1 -1
  259. package/dist/game/project-obj.d.ts +62 -0
  260. package/dist/game/project-obj.d.ts.map +1 -1
  261. package/dist/game/project-obj.js +72 -41
  262. package/dist/game/project-obj.js.map +1 -1
  263. package/dist/game/project-player.d.ts +8 -0
  264. package/dist/game/project-player.d.ts.map +1 -1
  265. package/dist/game/project-player.js.map +1 -1
  266. package/dist/game/projection-handlers.d.ts +81 -0
  267. package/dist/game/projection-handlers.d.ts.map +1 -0
  268. package/dist/game/projection-handlers.js +93 -0
  269. package/dist/game/projection-handlers.js.map +1 -0
  270. package/dist/game/quest.d.ts +23 -0
  271. package/dist/game/quest.d.ts.map +1 -1
  272. package/dist/game/quest.js +42 -0
  273. package/dist/game/quest.js.map +1 -1
  274. package/dist/game/ranged-cmd.d.ts +13 -2
  275. package/dist/game/ranged-cmd.d.ts.map +1 -1
  276. package/dist/game/ranged-cmd.js +32 -15
  277. package/dist/game/ranged-cmd.js.map +1 -1
  278. package/dist/game/repeat.d.ts +78 -0
  279. package/dist/game/repeat.d.ts.map +1 -0
  280. package/dist/game/repeat.js +88 -0
  281. package/dist/game/repeat.js.map +1 -0
  282. package/dist/game/shape-inspect.d.ts +74 -0
  283. package/dist/game/shape-inspect.d.ts.map +1 -0
  284. package/dist/game/shape-inspect.js +114 -0
  285. package/dist/game/shape-inspect.js.map +1 -0
  286. package/dist/game/spell-cmd.d.ts +15 -4
  287. package/dist/game/spell-cmd.d.ts.map +1 -1
  288. package/dist/game/spell-cmd.js +35 -6
  289. package/dist/game/spell-cmd.js.map +1 -1
  290. package/dist/game/spoil.d.ts.map +1 -1
  291. package/dist/game/spoil.js +89 -40
  292. package/dist/game/spoil.js.map +1 -1
  293. package/dist/game/steal.d.ts +3 -3
  294. package/dist/game/steal.d.ts.map +1 -1
  295. package/dist/game/steal.js +9 -4
  296. package/dist/game/steal.js.map +1 -1
  297. package/dist/game/target-loop.d.ts +10 -4
  298. package/dist/game/target-loop.d.ts.map +1 -1
  299. package/dist/game/target-loop.js +14 -8
  300. package/dist/game/target-loop.js.map +1 -1
  301. package/dist/game/target.d.ts +4 -4
  302. package/dist/game/target.d.ts.map +1 -1
  303. package/dist/game/target.js +19 -8
  304. package/dist/game/target.js.map +1 -1
  305. package/dist/game/ui-entry.d.ts +63 -4
  306. package/dist/game/ui-entry.d.ts.map +1 -1
  307. package/dist/game/ui-entry.js +151 -23
  308. package/dist/game/ui-entry.js.map +1 -1
  309. package/dist/game/wizard.d.ts +23 -5
  310. package/dist/game/wizard.d.ts.map +1 -1
  311. package/dist/game/wizard.js +93 -11
  312. package/dist/game/wizard.js.map +1 -1
  313. package/dist/game/world.d.ts.map +1 -1
  314. package/dist/game/world.js +6 -0
  315. package/dist/game/world.js.map +1 -1
  316. package/dist/gen/cave.d.ts +49 -8
  317. package/dist/gen/cave.d.ts.map +1 -1
  318. package/dist/gen/cave.js +200 -28
  319. package/dist/gen/cave.js.map +1 -1
  320. package/dist/gen/generate.d.ts +54 -9
  321. package/dist/gen/generate.d.ts.map +1 -1
  322. package/dist/gen/generate.js +55 -8
  323. package/dist/gen/generate.js.map +1 -1
  324. package/dist/gen/glyph-vectors.d.ts +109 -0
  325. package/dist/gen/glyph-vectors.d.ts.map +1 -0
  326. package/dist/gen/glyph-vectors.fixtures.d.ts +18 -0
  327. package/dist/gen/glyph-vectors.fixtures.d.ts.map +1 -0
  328. package/dist/gen/glyph-vectors.fixtures.js +94 -0
  329. package/dist/gen/glyph-vectors.fixtures.js.map +1 -0
  330. package/dist/gen/glyph-vectors.js +218 -0
  331. package/dist/gen/glyph-vectors.js.map +1 -0
  332. package/dist/gen/glyph.d.ts +104 -0
  333. package/dist/gen/glyph.d.ts.map +1 -0
  334. package/dist/gen/glyph.js +72 -0
  335. package/dist/gen/glyph.js.map +1 -0
  336. package/dist/gen/room.d.ts +26 -2
  337. package/dist/gen/room.d.ts.map +1 -1
  338. package/dist/gen/room.js +414 -264
  339. package/dist/gen/room.js.map +1 -1
  340. package/dist/gen/util.d.ts +11 -2
  341. package/dist/gen/util.d.ts.map +1 -1
  342. package/dist/gen/util.js +31 -9
  343. package/dist/gen/util.js.map +1 -1
  344. package/dist/i18n/i18n.d.ts +207 -0
  345. package/dist/i18n/i18n.d.ts.map +1 -0
  346. package/dist/i18n/i18n.js +405 -0
  347. package/dist/i18n/i18n.js.map +1 -0
  348. package/dist/i18n/text.d.ts +62 -0
  349. package/dist/i18n/text.d.ts.map +1 -0
  350. package/dist/i18n/text.js +120 -0
  351. package/dist/i18n/text.js.map +1 -0
  352. package/dist/index.d.ts +25 -0
  353. package/dist/index.d.ts.map +1 -1
  354. package/dist/index.js +38 -0
  355. package/dist/index.js.map +1 -1
  356. package/dist/mod/extension.d.ts +115 -0
  357. package/dist/mod/extension.d.ts.map +1 -0
  358. package/dist/mod/extension.js +85 -0
  359. package/dist/mod/extension.js.map +1 -0
  360. package/dist/mod/ids.d.ts +10 -0
  361. package/dist/mod/ids.d.ts.map +1 -1
  362. package/dist/mod/ids.js +90 -21
  363. package/dist/mod/ids.js.map +1 -1
  364. package/dist/mod/record-keys.d.ts +43 -0
  365. package/dist/mod/record-keys.d.ts.map +1 -0
  366. package/dist/mod/record-keys.js +100 -0
  367. package/dist/mod/record-keys.js.map +1 -0
  368. package/dist/mod/registry-host.d.ts +472 -0
  369. package/dist/mod/registry-host.d.ts.map +1 -1
  370. package/dist/mod/registry-host.js +313 -0
  371. package/dist/mod/registry-host.js.map +1 -1
  372. package/dist/mod/save-blocks.d.ts +1 -1
  373. package/dist/mon/bind.d.ts.map +1 -1
  374. package/dist/mon/bind.js +6 -3
  375. package/dist/mon/bind.js.map +1 -1
  376. package/dist/mon/knowledge-groups.d.ts +0 -6
  377. package/dist/mon/knowledge-groups.d.ts.map +1 -1
  378. package/dist/mon/knowledge-groups.js +12 -8
  379. package/dist/mon/knowledge-groups.js.map +1 -1
  380. package/dist/mon/lore-describe.d.ts +46 -10
  381. package/dist/mon/lore-describe.d.ts.map +1 -1
  382. package/dist/mon/lore-describe.js +74 -22
  383. package/dist/mon/lore-describe.js.map +1 -1
  384. package/dist/mon/lore-file.d.ts +126 -0
  385. package/dist/mon/lore-file.d.ts.map +1 -0
  386. package/dist/mon/lore-file.js +291 -0
  387. package/dist/mon/lore-file.js.map +1 -0
  388. package/dist/mon/lore.d.ts +9 -3
  389. package/dist/mon/lore.d.ts.map +1 -1
  390. package/dist/mon/lore.js +9 -3
  391. package/dist/mon/lore.js.map +1 -1
  392. package/dist/mon/make.d.ts +4 -2
  393. package/dist/mon/make.d.ts.map +1 -1
  394. package/dist/mon/make.js +4 -2
  395. package/dist/mon/make.js.map +1 -1
  396. package/dist/mon/monster.d.ts +1 -1
  397. package/dist/mon/monster.js +1 -1
  398. package/dist/mon/predicate.d.ts +14 -9
  399. package/dist/mon/predicate.d.ts.map +1 -1
  400. package/dist/mon/predicate.js +14 -9
  401. package/dist/mon/predicate.js.map +1 -1
  402. package/dist/mon/project-mon.d.ts +16 -3
  403. package/dist/mon/project-mon.d.ts.map +1 -1
  404. package/dist/mon/project-mon.js +14 -5
  405. package/dist/mon/project-mon.js.map +1 -1
  406. package/dist/mon/steal.d.ts +31 -4
  407. package/dist/mon/steal.d.ts.map +1 -1
  408. package/dist/mon/steal.js +20 -7
  409. package/dist/mon/steal.js.map +1 -1
  410. package/dist/mon/take-hit.d.ts +8 -3
  411. package/dist/mon/take-hit.d.ts.map +1 -1
  412. package/dist/mon/take-hit.js +8 -3
  413. package/dist/mon/take-hit.js.map +1 -1
  414. package/dist/mon/timed.d.ts +15 -9
  415. package/dist/mon/timed.d.ts.map +1 -1
  416. package/dist/mon/timed.js +13 -8
  417. package/dist/mon/timed.js.map +1 -1
  418. package/dist/mon/types.d.ts +11 -4
  419. package/dist/mon/types.d.ts.map +1 -1
  420. package/dist/mon/types.js.map +1 -1
  421. package/dist/msg.d.ts +40 -1
  422. package/dist/msg.d.ts.map +1 -1
  423. package/dist/msg.js +42 -1
  424. package/dist/msg.js.map +1 -1
  425. package/dist/obj/artifact-fake.d.ts +46 -14
  426. package/dist/obj/artifact-fake.d.ts.map +1 -1
  427. package/dist/obj/artifact-fake.js +46 -17
  428. package/dist/obj/artifact-fake.js.map +1 -1
  429. package/dist/obj/bind.d.ts +23 -0
  430. package/dist/obj/bind.d.ts.map +1 -1
  431. package/dist/obj/bind.js +49 -9
  432. package/dist/obj/bind.js.map +1 -1
  433. package/dist/obj/desc-vectors.d.ts +68 -0
  434. package/dist/obj/desc-vectors.d.ts.map +1 -0
  435. package/dist/obj/desc-vectors.fixtures.d.ts +12 -0
  436. package/dist/obj/desc-vectors.fixtures.d.ts.map +1 -0
  437. package/dist/obj/desc-vectors.fixtures.js +95 -0
  438. package/dist/obj/desc-vectors.fixtures.js.map +1 -0
  439. package/dist/obj/desc-vectors.js +86 -0
  440. package/dist/obj/desc-vectors.js.map +1 -0
  441. package/dist/obj/desc.d.ts +17 -2
  442. package/dist/obj/desc.d.ts.map +1 -1
  443. package/dist/obj/desc.js +129 -54
  444. package/dist/obj/desc.js.map +1 -1
  445. package/dist/obj/effects-info.d.ts.map +1 -1
  446. package/dist/obj/effects-info.js +87 -58
  447. package/dist/obj/effects-info.js.map +1 -1
  448. package/dist/obj/fake-object.d.ts +70 -0
  449. package/dist/obj/fake-object.d.ts.map +1 -0
  450. package/dist/obj/fake-object.js +104 -0
  451. package/dist/obj/fake-object.js.map +1 -0
  452. package/dist/obj/ignore.d.ts +55 -12
  453. package/dist/obj/ignore.d.ts.map +1 -1
  454. package/dist/obj/ignore.js +68 -31
  455. package/dist/obj/ignore.js.map +1 -1
  456. package/dist/obj/knowledge.d.ts +46 -3
  457. package/dist/obj/knowledge.d.ts.map +1 -1
  458. package/dist/obj/knowledge.js +169 -151
  459. package/dist/obj/knowledge.js.map +1 -1
  460. package/dist/obj/known-object.d.ts +68 -8
  461. package/dist/obj/known-object.d.ts.map +1 -1
  462. package/dist/obj/known-object.js +90 -10
  463. package/dist/obj/known-object.js.map +1 -1
  464. package/dist/obj/make.d.ts +15 -4
  465. package/dist/obj/make.d.ts.map +1 -1
  466. package/dist/obj/make.js +49 -27
  467. package/dist/obj/make.js.map +1 -1
  468. package/dist/obj/object-info.d.ts +10 -0
  469. package/dist/obj/object-info.d.ts.map +1 -1
  470. package/dist/obj/object-info.js +93 -7
  471. package/dist/obj/object-info.js.map +1 -1
  472. package/dist/obj/object.d.ts +42 -4
  473. package/dist/obj/object.d.ts.map +1 -1
  474. package/dist/obj/object.js +362 -147
  475. package/dist/obj/object.js.map +1 -1
  476. package/dist/obj/power.d.ts +23 -4
  477. package/dist/obj/power.d.ts.map +1 -1
  478. package/dist/obj/power.js +231 -65
  479. package/dist/obj/power.js.map +1 -1
  480. package/dist/obj/randart-build.d.ts +27 -22
  481. package/dist/obj/randart-build.d.ts.map +1 -1
  482. package/dist/obj/randart-build.js +435 -357
  483. package/dist/obj/randart-build.js.map +1 -1
  484. package/dist/obj/randart-data.d.ts +62 -24
  485. package/dist/obj/randart-data.d.ts.map +1 -1
  486. package/dist/obj/randart-data.js +333 -189
  487. package/dist/obj/randart-data.js.map +1 -1
  488. package/dist/obj/randart-file.d.ts +21 -0
  489. package/dist/obj/randart-file.d.ts.map +1 -0
  490. package/dist/obj/randart-file.js +119 -0
  491. package/dist/obj/randart-file.js.map +1 -0
  492. package/dist/obj/randart-log.d.ts +63 -0
  493. package/dist/obj/randart-log.d.ts.map +1 -0
  494. package/dist/obj/randart-log.js +72 -0
  495. package/dist/obj/randart-log.js.map +1 -0
  496. package/dist/obj/randart-registry.d.ts +178 -0
  497. package/dist/obj/randart-registry.d.ts.map +1 -0
  498. package/dist/obj/randart-registry.js +140 -0
  499. package/dist/obj/randart-registry.js.map +1 -0
  500. package/dist/obj/randart-vectors.d.ts +97 -0
  501. package/dist/obj/randart-vectors.d.ts.map +1 -0
  502. package/dist/obj/randart-vectors.fixtures.d.ts +18 -0
  503. package/dist/obj/randart-vectors.fixtures.d.ts.map +1 -0
  504. package/dist/obj/randart-vectors.fixtures.js +50 -0
  505. package/dist/obj/randart-vectors.fixtures.js.map +1 -0
  506. package/dist/obj/randart-vectors.js +311 -0
  507. package/dist/obj/randart-vectors.js.map +1 -0
  508. package/dist/obj/randart.d.ts +53 -15
  509. package/dist/obj/randart.d.ts.map +1 -1
  510. package/dist/obj/randart.js +166 -44
  511. package/dist/obj/randart.js.map +1 -1
  512. package/dist/obj/rune-registry.d.ts +180 -0
  513. package/dist/obj/rune-registry.d.ts.map +1 -0
  514. package/dist/obj/rune-registry.js +161 -0
  515. package/dist/obj/rune-registry.js.map +1 -0
  516. package/dist/obj/rune-vectors.d.ts +99 -0
  517. package/dist/obj/rune-vectors.d.ts.map +1 -0
  518. package/dist/obj/rune-vectors.fixtures.d.ts +44 -0
  519. package/dist/obj/rune-vectors.fixtures.d.ts.map +1 -0
  520. package/dist/obj/rune-vectors.fixtures.js +125 -0
  521. package/dist/obj/rune-vectors.fixtures.js.map +1 -0
  522. package/dist/obj/rune-vectors.js +159 -0
  523. package/dist/obj/rune-vectors.js.map +1 -0
  524. package/dist/obj/tval-registry.d.ts +174 -0
  525. package/dist/obj/tval-registry.d.ts.map +1 -0
  526. package/dist/obj/tval-registry.js +151 -0
  527. package/dist/obj/tval-registry.js.map +1 -0
  528. package/dist/obj/tval-vectors.d.ts +85 -0
  529. package/dist/obj/tval-vectors.d.ts.map +1 -0
  530. package/dist/obj/tval-vectors.fixtures.d.ts +24 -0
  531. package/dist/obj/tval-vectors.fixtures.d.ts.map +1 -0
  532. package/dist/obj/tval-vectors.fixtures.js +41 -0
  533. package/dist/obj/tval-vectors.fixtures.js.map +1 -0
  534. package/dist/obj/tval-vectors.js +76 -0
  535. package/dist/obj/tval-vectors.js.map +1 -0
  536. package/dist/obj/types.d.ts +18 -7
  537. package/dist/obj/types.d.ts.map +1 -1
  538. package/dist/obj/types.js.map +1 -1
  539. package/dist/obj/value.d.ts.map +1 -1
  540. package/dist/obj/value.js +23 -20
  541. package/dist/obj/value.js.map +1 -1
  542. package/dist/parser.d.ts +116 -0
  543. package/dist/parser.d.ts.map +1 -0
  544. package/dist/parser.js +205 -0
  545. package/dist/parser.js.map +1 -0
  546. package/dist/player/bind.d.ts.map +1 -1
  547. package/dist/player/bind.js +3 -2
  548. package/dist/player/bind.js.map +1 -1
  549. package/dist/player/birth.d.ts +2 -1
  550. package/dist/player/birth.d.ts.map +1 -1
  551. package/dist/player/birth.js +2 -1
  552. package/dist/player/birth.js.map +1 -1
  553. package/dist/player/calcs.d.ts +58 -23
  554. package/dist/player/calcs.d.ts.map +1 -1
  555. package/dist/player/calcs.js +93 -36
  556. package/dist/player/calcs.js.map +1 -1
  557. package/dist/player/death.d.ts +50 -0
  558. package/dist/player/death.d.ts.map +1 -0
  559. package/dist/player/death.js +46 -0
  560. package/dist/player/death.js.map +1 -0
  561. package/dist/player/options-file.d.ts +129 -0
  562. package/dist/player/options-file.d.ts.map +1 -0
  563. package/dist/player/options-file.js +309 -0
  564. package/dist/player/options-file.js.map +1 -0
  565. package/dist/player/options.d.ts +2 -1
  566. package/dist/player/options.d.ts.map +1 -1
  567. package/dist/player/options.js +2 -1
  568. package/dist/player/options.js.map +1 -1
  569. package/dist/player/player.d.ts +50 -3
  570. package/dist/player/player.d.ts.map +1 -1
  571. package/dist/player/player.js +10 -1
  572. package/dist/player/player.js.map +1 -1
  573. package/dist/player/shape-lore.d.ts +11 -7
  574. package/dist/player/shape-lore.d.ts.map +1 -1
  575. package/dist/player/shape-lore.js +24 -11
  576. package/dist/player/shape-lore.js.map +1 -1
  577. package/dist/player/timed.d.ts +5 -3
  578. package/dist/player/timed.d.ts.map +1 -1
  579. package/dist/player/timed.js +11 -2
  580. package/dist/player/timed.js.map +1 -1
  581. package/dist/player/types.d.ts +29 -4
  582. package/dist/player/types.d.ts.map +1 -1
  583. package/dist/player/types.js +24 -1
  584. package/dist/player/types.js.map +1 -1
  585. package/dist/session/boot.d.ts +26 -2
  586. package/dist/session/boot.d.ts.map +1 -1
  587. package/dist/session/boot.js +21 -2
  588. package/dist/session/boot.js.map +1 -1
  589. package/dist/session/game.d.ts +15 -0
  590. package/dist/session/game.d.ts.map +1 -1
  591. package/dist/session/game.js +616 -140
  592. package/dist/session/game.js.map +1 -1
  593. package/dist/session/save-migrate.d.ts.map +1 -1
  594. package/dist/session/save-migrate.js +76 -1
  595. package/dist/session/save-migrate.js.map +1 -1
  596. package/dist/session/save.d.ts +75 -12
  597. package/dist/session/save.d.ts.map +1 -1
  598. package/dist/session/save.js +177 -18
  599. package/dist/session/save.js.map +1 -1
  600. package/dist/store/bind.d.ts +1 -1
  601. package/dist/store/bind.d.ts.map +1 -1
  602. package/dist/store/bind.js +4 -3
  603. package/dist/store/bind.js.map +1 -1
  604. package/dist/store/mass-produce-vectors.d.ts +40 -0
  605. package/dist/store/mass-produce-vectors.d.ts.map +1 -0
  606. package/dist/store/mass-produce-vectors.fixtures.d.ts +13 -0
  607. package/dist/store/mass-produce-vectors.fixtures.d.ts.map +1 -0
  608. package/dist/store/mass-produce-vectors.fixtures.js +52 -0
  609. package/dist/store/mass-produce-vectors.fixtures.js.map +1 -0
  610. package/dist/store/mass-produce-vectors.js +44 -0
  611. package/dist/store/mass-produce-vectors.js.map +1 -0
  612. package/dist/store/store.d.ts +104 -7
  613. package/dist/store/store.d.ts.map +1 -1
  614. package/dist/store/store.js +199 -77
  615. package/dist/store/store.js.map +1 -1
  616. package/dist/store/transact.d.ts +14 -5
  617. package/dist/store/transact.d.ts.map +1 -1
  618. package/dist/store/transact.js +17 -16
  619. package/dist/store/transact.js.map +1 -1
  620. package/dist/store/types.d.ts +3 -2
  621. package/dist/store/types.d.ts.map +1 -1
  622. package/dist/store/types.js +1 -1
  623. package/dist/version.d.ts +1 -1
  624. package/dist/version.js +1 -1
  625. package/dist/visuals/index.d.ts +1 -0
  626. package/dist/visuals/index.d.ts.map +1 -1
  627. package/dist/visuals/index.js +1 -0
  628. package/dist/visuals/index.js.map +1 -1
  629. package/dist/visuals/map-text.d.ts +8 -0
  630. package/dist/visuals/map-text.d.ts.map +1 -1
  631. package/dist/visuals/map-text.js +1 -1
  632. package/dist/visuals/map-text.js.map +1 -1
  633. package/dist/visuals/object-glyph.d.ts +56 -0
  634. package/dist/visuals/object-glyph.d.ts.map +1 -0
  635. package/dist/visuals/object-glyph.js +52 -0
  636. package/dist/visuals/object-glyph.js.map +1 -0
  637. package/dist/visuals/prefs.d.ts +14 -22
  638. package/dist/visuals/prefs.d.ts.map +1 -1
  639. package/dist/visuals/prefs.js +4 -23
  640. package/dist/visuals/prefs.js.map +1 -1
  641. package/dist/visuals/tile-prefs.d.ts +26 -0
  642. package/dist/visuals/tile-prefs.d.ts.map +1 -1
  643. package/dist/visuals/tile-prefs.js +28 -0
  644. package/dist/visuals/tile-prefs.js.map +1 -1
  645. package/dist/world/feature.d.ts +2 -1
  646. package/dist/world/feature.d.ts.map +1 -1
  647. package/dist/world/feature.js +2 -0
  648. package/dist/world/feature.js.map +1 -1
  649. package/dist/world/project.d.ts +10 -2
  650. package/dist/world/project.d.ts.map +1 -1
  651. package/dist/world/project.js +18 -6
  652. package/dist/world/project.js.map +1 -1
  653. package/dist/world/projection.d.ts +37 -3
  654. package/dist/world/projection.d.ts.map +1 -1
  655. package/dist/world/projection.js +69 -7
  656. package/dist/world/projection.js.map +1 -1
  657. package/dist/world/trap.d.ts +2 -1
  658. package/dist/world/trap.d.ts.map +1 -1
  659. package/dist/world/trap.js +2 -1
  660. package/dist/world/trap.js.map +1 -1
  661. package/dist/world/view.d.ts +1 -1
  662. package/dist/world/view.d.ts.map +1 -1
  663. package/dist/world/view.js +11 -3
  664. package/dist/world/view.js.map +1 -1
  665. package/package.json +1 -1
  666. package/src/agent/perceive.ts +2 -3
  667. package/src/combat/blow-vectors.fixtures.ts +99 -0
  668. package/src/combat/blow-vectors.json +482 -0
  669. package/src/combat/blow-vectors.ts +280 -0
  670. package/src/combat/brand-slay.ts +42 -11
  671. package/src/combat/index.ts +1 -1
  672. package/src/combat/melee.ts +37 -19
  673. package/src/combat/mon-melee.ts +611 -364
  674. package/src/combat/ranged.ts +43 -26
  675. package/src/constants.ts +15 -0
  676. package/src/datafile.ts +148 -0
  677. package/src/effects/effect-info-registry.ts +313 -0
  678. package/src/effects/effect-info-vectors.fixtures.ts +61 -0
  679. package/src/effects/effect-info-vectors.json +11532 -0
  680. package/src/effects/effect-info-vectors.ts +376 -0
  681. package/src/effects/effect-info.ts +1005 -859
  682. package/src/effects/effect.ts +96 -62
  683. package/src/effects/handlers.ts +5 -2
  684. package/src/effects/interpreter.ts +14 -0
  685. package/src/game/cave-cmd.ts +54 -11
  686. package/src/game/cave-square.ts +80 -0
  687. package/src/game/char-sheet.ts +59 -16
  688. package/src/game/chest.ts +25 -8
  689. package/src/game/context.ts +293 -27
  690. package/src/game/describe.ts +34 -4
  691. package/src/game/display.ts +215 -19
  692. package/src/game/dump-level.ts +3 -2
  693. package/src/game/effect-attack.ts +41 -28
  694. package/src/game/effect-detect.ts +45 -8
  695. package/src/game/effect-env.ts +8 -0
  696. package/src/game/effect-general.ts +110 -47
  697. package/src/game/effect-item.ts +1075 -999
  698. package/src/game/effect-melee.ts +51 -18
  699. package/src/game/effect-monster.ts +55 -9
  700. package/src/game/effect-teleport.ts +173 -38
  701. package/src/game/effect-terrain.ts +38 -22
  702. package/src/game/equip-cmp.ts +483 -480
  703. package/src/game/floor.ts +22 -4
  704. package/src/game/gear.ts +210 -21
  705. package/src/game/harness.ts +12 -1
  706. package/src/game/ignore-cmd.ts +96 -8
  707. package/src/game/known.ts +392 -73
  708. package/src/game/loop.ts +52 -27
  709. package/src/game/lore-color.ts +13 -1
  710. package/src/game/mon-cast.ts +31 -6
  711. package/src/game/mon-cmd.ts +95 -30
  712. package/src/game/mon-death.ts +14 -12
  713. package/src/game/mon-group.ts +9 -5
  714. package/src/game/mon-message.ts +363 -65
  715. package/src/game/mon-place.ts +15 -3
  716. package/src/game/mon-ranged.ts +14 -5
  717. package/src/game/mon-side.ts +16 -12
  718. package/src/game/monster-turn.ts +120 -87
  719. package/src/game/notice.ts +85 -0
  720. package/src/game/obj-cmd.ts +126 -37
  721. package/src/game/obj-list.ts +44 -26
  722. package/src/game/object-inspect.ts +13 -0
  723. package/src/game/pickup.ts +29 -5
  724. package/src/game/player-side-vectors.fixtures.ts +215 -0
  725. package/src/game/player-side-vectors.json +6914 -0
  726. package/src/game/player-side-vectors.ts +420 -0
  727. package/src/game/player-side.ts +622 -340
  728. package/src/game/player-turn.ts +228 -31
  729. package/src/game/poly.ts +107 -0
  730. package/src/game/project-cast.ts +63 -6
  731. package/src/game/project-feat-vectors.fixtures.ts +106 -0
  732. package/src/game/project-feat-vectors.json +6554 -0
  733. package/src/game/project-feat-vectors.ts +323 -0
  734. package/src/game/project-feat.ts +417 -248
  735. package/src/game/project-monster.ts +69 -30
  736. package/src/game/project-obj-vectors.json +1 -0
  737. package/src/game/project-obj.ts +115 -47
  738. package/src/game/project-player.ts +8 -0
  739. package/src/game/projection-handlers.ts +110 -0
  740. package/src/game/quest.ts +48 -0
  741. package/src/game/ranged-cmd.ts +45 -11
  742. package/src/game/repeat.ts +92 -0
  743. package/src/game/shape-inspect.ts +166 -0
  744. package/src/game/spell-cmd.ts +39 -6
  745. package/src/game/spoil.ts +92 -39
  746. package/src/game/steal.ts +9 -4
  747. package/src/game/target-loop.ts +14 -7
  748. package/src/game/target.ts +18 -7
  749. package/src/game/ui-entry.ts +172 -22
  750. package/src/game/wizard.ts +97 -10
  751. package/src/game/world.ts +6 -0
  752. package/src/gen/cave.ts +225 -26
  753. package/src/gen/generate.ts +62 -9
  754. package/src/gen/glyph-vectors.fixtures.ts +122 -0
  755. package/src/gen/glyph-vectors.json +5996 -0
  756. package/src/gen/glyph-vectors.ts +280 -0
  757. package/src/gen/glyph.ts +132 -0
  758. package/src/gen/room.ts +446 -265
  759. package/src/gen/util.ts +32 -9
  760. package/src/i18n/i18n.ts +492 -0
  761. package/src/i18n/text.ts +117 -0
  762. package/src/index.ts +307 -254
  763. package/src/mod/extension.ts +136 -0
  764. package/src/mod/ids.ts +92 -21
  765. package/src/mod/record-keys.ts +102 -0
  766. package/src/mod/registry-host.ts +1063 -234
  767. package/src/mon/bind.ts +14 -8
  768. package/src/mon/knowledge-groups.ts +12 -12
  769. package/src/mon/lore-describe.ts +93 -26
  770. package/src/mon/lore-file.ts +365 -0
  771. package/src/mon/lore.ts +9 -3
  772. package/src/mon/make.ts +4 -2
  773. package/src/mon/monster.ts +1 -1
  774. package/src/mon/predicate.ts +14 -9
  775. package/src/mon/project-mon.ts +23 -5
  776. package/src/mon/steal.ts +39 -7
  777. package/src/mon/take-hit.ts +8 -3
  778. package/src/mon/timed.ts +15 -9
  779. package/src/mon/types.ts +11 -4
  780. package/src/msg.ts +55 -1
  781. package/src/obj/artifact-fake.ts +47 -17
  782. package/src/obj/bind.ts +52 -9
  783. package/src/obj/desc-vectors.fixtures.ts +119 -0
  784. package/src/obj/desc-vectors.json +2360 -0
  785. package/src/obj/desc-vectors.ts +128 -0
  786. package/src/obj/desc.ts +159 -55
  787. package/src/obj/effects-info.ts +90 -59
  788. package/src/obj/fake-object.ts +135 -0
  789. package/src/obj/ignore.ts +95 -28
  790. package/src/obj/knowledge.ts +291 -139
  791. package/src/obj/known-object.ts +126 -11
  792. package/src/obj/make.ts +53 -29
  793. package/src/obj/object-info.ts +103 -6
  794. package/src/obj/object.ts +392 -151
  795. package/src/obj/power.ts +297 -59
  796. package/src/obj/randart-build.ts +1875 -1705
  797. package/src/obj/randart-data.ts +1547 -1344
  798. package/src/obj/randart-file.ts +130 -0
  799. package/src/obj/randart-log.ts +78 -0
  800. package/src/obj/randart-registry.ts +247 -0
  801. package/src/obj/randart-vectors.fixtures.ts +61 -0
  802. package/src/obj/randart-vectors.json +836 -0
  803. package/src/obj/randart-vectors.ts +369 -0
  804. package/src/obj/randart.ts +722 -552
  805. package/src/obj/rune-registry.ts +270 -0
  806. package/src/obj/rune-vectors.fixtures.ts +164 -0
  807. package/src/obj/rune-vectors.json +121 -0
  808. package/src/obj/rune-vectors.ts +229 -0
  809. package/src/obj/tval-kind-vectors.json +391 -0
  810. package/src/obj/tval-registry.ts +243 -0
  811. package/src/obj/tval-vectors.fixtures.ts +50 -0
  812. package/src/obj/tval-vectors.json +38 -0
  813. package/src/obj/tval-vectors.ts +126 -0
  814. package/src/obj/types.ts +18 -7
  815. package/src/obj/value.ts +26 -20
  816. package/src/parser.ts +214 -0
  817. package/src/player/bind.ts +5 -2
  818. package/src/player/birth.ts +2 -1
  819. package/src/player/calcs.ts +123 -42
  820. package/src/player/death.ts +78 -0
  821. package/src/player/options-file.ts +387 -0
  822. package/src/player/options.ts +2 -1
  823. package/src/player/player.ts +60 -4
  824. package/src/player/shape-lore.ts +33 -14
  825. package/src/player/timed.ts +14 -4
  826. package/src/player/types.ts +30 -4
  827. package/src/session/boot.ts +51 -1
  828. package/src/session/game.ts +720 -163
  829. package/src/session/save-migrate.ts +81 -1
  830. package/src/session/save.ts +264 -22
  831. package/src/store/bind.ts +4 -3
  832. package/src/store/mass-produce-vectors.fixtures.ts +64 -0
  833. package/src/store/mass-produce-vectors.json +1169 -0
  834. package/src/store/mass-produce-vectors.ts +67 -0
  835. package/src/store/store.ts +266 -63
  836. package/src/store/transact.ts +39 -16
  837. package/src/store/types.ts +3 -2
  838. package/src/version.ts +1 -1
  839. package/src/visuals/index.ts +1 -0
  840. package/src/visuals/map-text.ts +1 -1
  841. package/src/visuals/object-glyph.ts +76 -0
  842. package/src/visuals/prefs.ts +22 -32
  843. package/src/visuals/tile-prefs.ts +32 -0
  844. package/src/world/feature.ts +4 -1
  845. package/src/world/project.ts +19 -4
  846. package/src/world/projection.ts +79 -8
  847. package/src/world/trap.ts +25 -21
  848. package/src/world/view.ts +11 -1
@@ -17,10 +17,10 @@
17
17
  * ui-game.c:757-760: store_reset, seed_randart, seed_flavor (back-to-back),
18
18
  * then prepare_next_level generation.
19
19
  */
20
- import { loc } from "../loc.js";
20
+ import { DDGRID, loc, locSum } from "../loc.js";
21
21
  import { MessageLog } from "../msg.js";
22
- import { SKILL, STAT_MAX } from "../player/types.js";
23
- import { EF, ELEM, HIST, OF, PF, RF, STAT, TMD } from "../generated/index.js";
22
+ import { PN, SKILL, STAT_MAX } from "../player/types.js";
23
+ import { EF, ELEM, HIST, MSG, OF, PF, RF, STAT, TMD } from "../generated/index.js";
24
24
  import { bindPlayer } from "../player/bind.js";
25
25
  import { generatePlayer, optionsInitCheat, flavorSetAllAware, } from "../player/birth.js";
26
26
  import { bonusChangeMessages, calcBonuses, calcHitpoints, toCombatState, toDefenderState, } from "../player/calcs.js";
@@ -28,15 +28,17 @@ import { playerBestDiggerDigging, playerBestDiggerWithClause, } from "../player/
28
28
  import { playerExpGain, playerKillExp } from "../player/exp.js";
29
29
  import { historyAdd, historyFindArtifact, historyLoseArtifact } from "../player/history.js";
30
30
  import { artifactHistoryName, historyStamp } from "../game/history.js";
31
- import { makePlayerSideEffects, makeIncCheckQueries } from "../game/player-side.js";
31
+ import { makePlayerSideEffects, makeIncCheckHooks, makeIncCheckQueries, makeTimedNotifyQueries, } from "../game/player-side.js";
32
+ import { ProjectionHandlerRegistry } from "../game/projection-handlers.js";
32
33
  import { makeTakeHitHooks } from "../game/take-hit-hooks.js";
33
34
  import { makeMonBlowEnv } from "../game/mon-side.js";
35
+ import { BlowEffectRegistry, registerCoreBlowEffects, } from "../combat/mon-melee.js";
34
36
  import { adj_dex_safe } from "../player/calcs.js";
35
37
  import { processCurseTimeouts } from "../game/curse-tick.js";
36
38
  import { buildEffectContext } from "../game/effect-env.js";
37
39
  import { attachGameEnv } from "../game/effect-game-env.js";
38
40
  import { sourceMonster, sourceNone, sourcePlayer } from "../effects/interpreter.js";
39
- import { DEFAULT_GAME_CONSTANTS, addMonster, deleteMonster, placePlayer, squareMonster, updateMonsterDistances, } from "../game/context.js";
41
+ import { DEFAULT_GAME_CONSTANTS, addMonster, deleteMonster, placePlayer, playerOfHas, squareMonster, updateMonsterDistances, } from "../game/context.js";
40
42
  import { Chunk } from "../world/chunk.js";
41
43
  import { chunkWriteTerrain } from "../gen/cave.js";
42
44
  import { FEAT, SQUARE } from "../generated/index.js";
@@ -57,11 +59,11 @@ import { registerItemHandlers } from "../game/effect-item.js";
57
59
  import { registerMeleeHandlers } from "../game/effect-melee.js";
58
60
  import { registerSummonHandlers } from "../game/effect-summon.js";
59
61
  import { registerDetectHandlers } from "../game/effect-detect.js";
60
- import { becomeAware, caveIlluminateKnown, caveKnown, newKnownMap, noteSpots, viewerStateOf, } from "../game/known.js";
62
+ import { becomeAware, caveIlluminateKnown, caveKnown, newKnownMap, noteSpots, monsterLightSources, updateMon, viewerStateOf, } from "../game/known.js";
61
63
  import { updateView } from "../world/view.js";
62
64
  import { PY_EXERT, compactMonsters, isDaytime, playerOverExert } from "../game/world.js";
63
65
  import { restoreMonsters } from "../game/scheduler.js";
64
- import { newTargetState, targetSetMonster } from "../game/target.js";
66
+ import { newTargetState, targetGet, targetOkay, targetSetMonster, } from "../game/target.js";
65
67
  import { getLore, loreCountU16, loreLearnFlagIfVisible, loreLearnSpellIfVisible, loreUpdate, } from "../mon/lore.js";
66
68
  import { monsterIsVisible } from "../mon/predicate.js";
67
69
  import { countMonsterRaces, multiplyMonster, pickAndPlaceDistantMonster, squareIsEmptyLive, wipeMonsterCounts, } from "../game/mon-place.js";
@@ -70,6 +72,7 @@ import { MonAllocTable } from "../mon/make.js";
70
72
  import { thrustAway } from "../game/thrust.js";
71
73
  import { basicPlayerActor } from "../game/project-cast.js";
72
74
  import { installMonsterCasting } from "../game/mon-ranged.js";
75
+ import { polyRace, polymorphMonster } from "../game/poly.js";
73
76
  import { buildMonSpellHooks, buildFailRuneEnv } from "../game/mon-cast.js";
74
77
  import { installMonCommand } from "../game/mon-cmd.js";
75
78
  import { monsterChangeShape, monsterRevertShape } from "../game/mon-shape.js";
@@ -83,29 +86,36 @@ import { calcMana, calcSpells, cumberArmorFrom, playerSpellsInit, registerBookKi
83
86
  import { installSpellCommands, makeSpellChanceEnv } from "../game/spell-cmd.js";
84
87
  import { installRangedCommands } from "../game/ranged-cmd.js";
85
88
  import { markNoscore, NOSCORE } from "../game/wizard.js";
86
- import { createTownStores, storeUpdate, storeWillBuy } from "../store/store.js";
89
+ import { createTownStores, registerCoreStoreBehaviour, StoreBehaviourRegistry, storeUpdate, storeWillBuy, } from "../store/store.js";
87
90
  import { homeCarry, homeRetrieve, homeStash, storeBuy, storeSell, storeSellFloor, } from "../store/transact.js";
88
91
  import { storeCheckNum } from "../store/store.js";
89
92
  import { priceItem } from "../store/price.js";
90
- import { formatMonsterMessage, formatMonsterMessageByName, formatMonsterMessageShowDamage, formatPainMessage, formatPainMessageShowDamage, monMessageSoundType, painMessageCode, } from "../game/mon-message.js";
91
- import { AutoinscriptionRegistry, RuneNoteRegistry, buildRuneList, runeKey, FlavorKnowledge, EverseenKnowledge, equipLearnElement, makeRuneEnv, NOOP_FLAVOR_AWARE_DEPS, OBJ_NOTICE, objectLearnOnWield, playerLearnInnate, playerLearnAllRunes, } from "../obj/knowledge.js";
93
+ import { addMonsterMessage, addMonsterMessageShowDamage, messagePain, messagePainShowDamage, monMessageCodeByName, } from "../game/mon-message.js";
94
+ import { AutoinscriptionRegistry, RuneNoteRegistry, buildRuneList, runeKey, FlavorKnowledge, EverseenKnowledge, equipLearnElement, equipLearnFlag, makeRuneEnv, NOOP_FLAVOR_AWARE_DEPS, OBJ_NOTICE, objectLearnOnWield, playerLearnInnate, playerLearnAllRunes, } from "../obj/knowledge.js";
92
95
  import { flavorInit } from "../obj/flavor.js";
93
96
  import { ELEM_MAX } from "../obj/types.js";
94
97
  import { ArtifactState, ObjAllocState } from "../obj/make.js";
95
98
  import { monsterDeath, installNonplayerHitDeps } from "../game/mon-death.js";
96
- import { newGear, outfitPlayer, gearGet, calcInventory, minusAc as applyMinusAc, objectCopyAmt, } from "../game/gear.js";
99
+ import { newGear, outfitPlayer, gearGet, gearTotalWeight, calcInventory, combinePackForPlayer, minusAc as applyMinusAc, objectCopyAmt, } from "../game/gear.js";
97
100
  import { objectValue as computeObjectValue } from "../obj/value.js";
98
101
  import { buildCurseTimedFoil } from "../obj/object.js";
99
102
  import { createDefaultRegistry, installMeleeSideEffects, search } from "../game/player-turn.js";
103
+ import { noticeNewLevel } from "../game/notice.js";
104
+ import { cmdDisableRepeatFloorItem } from "../game/repeat.js";
100
105
  import { buildTempBrandSlay, playerIncCheck } from "../player/timed.js";
106
+ import { describeObject, knownDescOf, objectKnownView } from "../game/describe.js";
107
+ import { knownBonusView, objectFlagIsKnown, objectKnownShadow, } from "../obj/known-object.js";
108
+ import { ODESC, objDescNameFormat } from "../obj/desc.js";
101
109
  import { disturb, installRunning } from "../game/player-path.js";
102
110
  import { bindCore, bootLevel, genDeps } from "./boot.js";
103
111
  import { dungeonGetNextLevel, isQuest, playerQuestsReset, questCheck, } from "../game/quest.js";
104
112
  import { Rng } from "../rng.js";
105
113
  import { OptionState } from "../player/options.js";
114
+ import { optionsInitDefaults } from "../player/options-file.js";
115
+ import { host } from "../host/io.js";
106
116
  import { doRandart, RANDNAME_TOLKIEN } from "../obj/randart.js";
107
117
  import { makeActivationSummarizer } from "../obj/effects-info.js";
108
- import { generateLevel, getJoinInfo } from "../gen/generate.js";
118
+ import { generateLevel, getJoinInfo, getMinLevelSize, } from "../gen/generate.js";
109
119
  import { iToGrid } from "../gen/util.js";
110
120
  import { deserializeAutoinscriptions, deserializeEverseen, deserializeFlavor, deserializeIgnore, deserializeChunk, deserializeFloor, buildFeatRemap, deserializeArtifactsCreated, deserializeArtifactFlags, deserializeGear, deserializeKnown, deserializeLevelCache, deserializeLore, deserializeMessages, deserializeMonster, deserializePlayer, deserializeStores, deserializeTraps, serializeGame, } from "./save.js";
111
121
  import { migrateSave } from "./save-migrate.js";
@@ -139,6 +149,28 @@ export function resolveTargetItem(state, req) {
139
149
  state.itemRequest = req;
140
150
  return null;
141
151
  }
152
+ /**
153
+ * The getAimTarget seam body: effect_handler_TELEPORT_TO's own get_aim_dir
154
+ * (effect-handler-general.c:2770-2778), resolved from the direction the shell
155
+ * pre-asked and rode on the command.
156
+ *
157
+ * do { if (!get_aim_dir(&dir)) return false; } while (dir == DIR_TARGET && !target_okay());
158
+ * if (dir == DIR_TARGET) target_get(&aim);
159
+ * else aim = loc_offset(start, ddx[dir], ddy[dir]);
160
+ *
161
+ * The C loop re-asks until the answer is usable; a port that cannot block
162
+ * instead treats an unusable answer as the cancel the loop's ESC produces. No
163
+ * RNG either way.
164
+ */
165
+ export function resolveEffectAim(state) {
166
+ const dir = state.effectAimDir;
167
+ if (dir === null || dir === undefined || dir < 1 || dir > 9)
168
+ return null;
169
+ /* DIR_TARGET: the while() means a target that is gone is not a direction. */
170
+ if (dir === 5)
171
+ return targetOkay(state) ? targetGet(state) : null;
172
+ return locSum(state.actor.grid, DDGRID[dir] ?? loc(0, 0));
173
+ }
142
174
  /**
143
175
  * The chooseCurse seam body (get_curse, effect-handler-general.c): return the
144
176
  * shell's preset curse index when it is one of the removable curses, else the
@@ -155,18 +187,22 @@ export function resolveTargetCurse(state, removable) {
155
187
  /**
156
188
  * The real, in-play FlavorAwareDeps for object_flavor_aware's ignore fix
157
189
  * (obj-knowledge.c L2276-2279, #89): reads the live ignore settings so a
158
- * kind ignored while unaware keeps being ignored once identified, and flags
159
- * player->upkeep->notice's PN_IGNORE (state.noticeIgnore) so a later
160
- * ignore_drop() pass (#25 UI, not yet wired) can react. Shared by the two
161
- * in-play becomes-aware sites: game/obj-cmd.ts's item-use knowledge gain
190
+ * kind ignored while unaware keeps being ignored once identified, and raises
191
+ * player->upkeep->notice's PN_IGNORE (obj-knowledge.c L2279) so the next
192
+ * notice_stuff runs an ignore_drop pass. Shared by the two in-play
193
+ * becomes-aware sites: game/obj-cmd.ts's item-use knowledge gain
162
194
  * (installObjCommands below) and store/transact.ts's buy/sell (makeStoreApi).
195
+ *
196
+ * This used to set a bespoke `state.noticeIgnore` boolean because there was no
197
+ * notice mask to raise a bit in - and nothing ever read the boolean, which is
198
+ * PORT_TODO 2.5. It raises the real bit now.
163
199
  */
164
200
  function flavorAwareDeps(state) {
165
201
  return {
166
202
  isIgnoredUnaware: (kidx) => state.ignore.kindIsIgnoredUnaware(kidx),
167
203
  ignoreWhenAware: (kidx) => state.ignore.kindIgnoreWhenAware(kidx),
168
204
  requestIgnoreNotice: () => {
169
- state.noticeIgnore = true;
205
+ state.actor.player.upkeep.notice |= PN.IGNORE;
170
206
  },
171
207
  };
172
208
  }
@@ -178,7 +214,7 @@ function flavorAwareDeps(state) {
178
214
  * captured by every closure, so a level change may swap the state's chunk
179
215
  * and entity stores in place without rewiring.
180
216
  */
181
- function wireGame(state, reg, players, pstate, seedFlavor, pack) {
217
+ function wireGame(state, reg, players, pstate, seedFlavor) {
182
218
  // Rolling message log (message.c file-statics; gap 12.8). Shared producer for
183
219
  // both new-game and load paths: startGame arrives with no log so this creates
184
220
  // it; loadGame already restored one via deserializeMessages, so preserve it
@@ -212,7 +248,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
212
248
  // ignore_item_ok (obj-ignore.c): the player's ignore settings resolved with
213
249
  // live flavor awareness. Everything reads it through state.isIgnored so the
214
250
  // floor / pickup / running / projection paths need no flavor coupling.
215
- state.isIgnored = (obj) => ignoreItemOk(obj, state.ignore, flavor.isAware(obj.kind));
251
+ state.isIgnored = (obj) => ignoreItemOk(obj, objectKnownView(state, obj), state.ignore, flavor.isAware(obj.kind));
216
252
  // object_flavor_is_aware (obj-knowledge.c): the presentation view models
217
253
  // (obj-list.c, #25) read kind awareness through this seam, keeping them
218
254
  // decoupled from the flavor store just like isIgnored.
@@ -247,12 +283,16 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
247
283
  });
248
284
  // Rune learning (obj-knowledge.c learn-by-use): the registry tables plus
249
285
  // live equipment access. Reads through the state object so level changes
250
- // and gear swaps need no rewiring.
286
+ // and gear swaps need no rewiring. `describeBase` is the real ODESC_BASE for
287
+ // the six rune / flag / curse messages (PORT_TODO 3.23); it is supplied HERE
288
+ // and only here, because the two other makeRuneEnv calls in this file are the
289
+ // documented placeholders wireGame replaces.
251
290
  state.runeEnv = makeRuneEnv((slot) => state.gear.store.get(state.actor.player.equipment[slot] ?? 0) ?? null, (v) => state.rng.randcalcVaries(v), {
252
291
  brands: reg.objects.brands,
253
292
  slays: reg.objects.slays,
254
293
  curses: reg.objects.curses,
255
294
  properties: reg.objects.properties,
295
+ describeBase: (obj) => describeObject(state, obj, ODESC.BASE),
256
296
  ...(reg.projections
257
297
  ? {
258
298
  elementNames: reg.projections
@@ -278,6 +318,31 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
278
318
  // fail-chance display always read the current stats.
279
319
  const liveStatInd = [...pstate.statInd];
280
320
  state.statInd = liveStatInd;
321
+ /**
322
+ * calc_bonuses(p, &known_state, TRUE, TRUE) (player-calcs.c:2349): the SECOND
323
+ * derive, the one the player is SHOWN. Same options and the same `update`
324
+ * flag upstream passes - its side effects (zeroing TMD_FASTCAST on a stun
325
+ * grade) are idempotent, so running them twice is what upstream does and
326
+ * costs nothing.
327
+ *
328
+ * knownBonusView is the whole difference: it hands the equipment loop each
329
+ * worn item's known twin, so an unlearned rune's to_a / to_h / to_d / resist
330
+ * / flag is left out of what gets printed while staying in what gets rolled.
331
+ *
332
+ * A named function rather than three lines inside refreshDerived because it
333
+ * is also called ONCE at the end of wiring. Without that seed, actor
334
+ * .knownCombat would hold the real state until the first equipment change,
335
+ * and a loaded character wearing unidentified gear would see true numbers on
336
+ * the sidebar for exactly as long as they stood still.
337
+ */
338
+ const refreshKnownCombat = (p, bonusOptions) => {
339
+ const known = calcBonuses(p, {
340
+ ...bonusOptions,
341
+ knownOnly: (obj) => knownBonusView(obj, p, state.runeEnv, knownDescOf(state)),
342
+ });
343
+ state.knownPlayerState = known;
344
+ state.actor.knownCombat = toCombatState(known);
345
+ };
281
346
  const refreshDerived = () => {
282
347
  const p = state.actor.player;
283
348
  const equipment = p.equipment.map((h) => h ? gearGet(state.gear, h) : null);
@@ -285,15 +350,17 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
285
350
  /* p->state before the memcpy at the end of calc_bonuses: the encumbrance
286
351
  * notices below diff against it (player-calcs.c:2412-2453). */
287
352
  const before = derived;
288
- derived = calcBonuses(p, {
353
+ const bonusOptions = {
289
354
  equipment,
290
355
  timedEffects: players.timed,
291
356
  curses: reg.objects.curses,
292
357
  update: true,
293
358
  depth: state.chunk.depth,
294
359
  isDaytime: daytime,
295
- });
360
+ };
361
+ derived = calcBonuses(p, bonusOptions);
296
362
  state.playerState = derived;
363
+ refreshKnownCombat(p, bonusOptions);
297
364
  /* calc_light's town-daytime branch (player-calcs.c 1608-1611) flags
298
365
  * PU_UPDATE_VIEW | PU_MONSTERS before returning; reinstate that refresh so
299
366
  * ambient town light tracks the day/night cycle. */
@@ -353,30 +420,47 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
353
420
  * with nothing installed.
354
421
  *
355
422
  * `??=` rather than `=`: a host that wants its own events bus or light sources
356
- * still replaces this, and the web does. Wired in wireGame so startGame and
357
- * loadGame both get it - a resumed character has the same right to a map as a
358
- * new one.
423
+ * can still replace this. NOTE that no shipped host does - the web calls core's
424
+ * (main.ts:8484 throws if wireGame did not install one) and so does the MCP
425
+ * session. This comment used to assert "and the web does", which is how the
426
+ * hardcoded `[]` below survived: a seam documented as host-supplied reads as
427
+ * deliberate rather than empty. Wired in wireGame so startGame and loadGame
428
+ * both get it - a resumed character has the same right to a map as a new one.
359
429
  *
360
430
  * The viewer fields come from `viewerStateOf` (game/known.ts) rather than being
361
431
  * spelled out again here, because spelling them out twice is how both hosts came
362
432
  * to pass `chunk.depth` where cave-view.c:778 reads `p->lev`.
363
433
  */
364
434
  state.updateFov ??= (s) => {
365
- updateView(s.chunk, viewerStateOf(s), { maxSight: reg.constants.maxSight, feelingNeed: reg.constants.feelingNeed }, [], s.events);
435
+ updateView(s.chunk, viewerStateOf(s), { maxSight: reg.constants.maxSight, feelingNeed: reg.constants.feelingNeed },
436
+ /* calc_lighting's monster scan (cave-view.c L696-719). Was `[]`. */
437
+ monsterLightSources(s), s.events,
438
+ /* cave-view.c:852: the object feeling, the moment the player has seen
439
+ * enough of the level to earn it. objOnly is display_feeling(true) - the
440
+ * one-line object half, not the joined ^F line - and the disturb is
441
+ * upstream's, so a run or a rest stops on the news. */
442
+ () => {
443
+ displayFeeling(s, { objOnly: true });
444
+ disturb(s);
445
+ });
366
446
  noteSpots(s);
367
447
  };
448
+ /* The live calc_inventory inputs, shared by every closure below that has to
449
+ * re-derive upkeep->inven[] / upkeep->quiver[]. One function rather than a
450
+ * copy per seam, so a change to the options bag reaches all of them. */
451
+ const liveCalcInv = () => ({
452
+ ammoTval: state.playerState?.ammoTval ?? 0,
453
+ objectValue: (obj) => computeObjectValue(reg.objects, obj, 1, true),
454
+ rogueLike: state.options?.get("rogue_like_commands") ?? false,
455
+ characterDungeon: true,
456
+ ...(state.msg ? { msg: state.msg } : {}),
457
+ });
368
458
  /* game-world.c:941-947: the C refreshes upkeep->inven[] before its
369
459
  * catch-all pack_overflow(NULL). This closure has the same live
370
460
  * calc_inventory inputs used by pickup and command paths, preserving the
371
461
  * earlier_object order of the derived gear.inven view. */
372
462
  state.overflowPack = () => {
373
- const calcInv = {
374
- ammoTval: state.playerState?.ammoTval ?? 0,
375
- objectValue: (obj) => computeObjectValue(reg.objects, obj, 1, true),
376
- rogueLike: state.options?.get("rogue_like_commands") ?? false,
377
- characterDungeon: true,
378
- ...(state.msg ? { msg: state.msg } : {}),
379
- };
463
+ const calcInv = liveCalcInv();
380
464
  /* notice_stuff()/handle_stuff() precede pack_overflow(NULL) at
381
465
  * game-world.c:941-947; materialize the current upkeep->inven[] analogue
382
466
  * before selecting its final entry. */
@@ -386,6 +470,11 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
386
470
  ...(state.msg ? { msg: state.msg } : {}),
387
471
  });
388
472
  };
473
+ /* notice_stuff's PN_COMBINE branch (player-calcs.c L2546-2549). combine_pack
474
+ * ends in its own calc_inventory, so this needs the same options bag. */
475
+ state.combinePack = () => {
476
+ combinePackForPlayer(state.gear, state.actor.player, reg.constants, liveCalcInv());
477
+ };
389
478
  /* Stash the class list so refreshTownStores can expand the bookseller's
390
479
  * town-book always lines (object_kind_to_book, store.c:208-231). */
391
480
  state.classes = players.classes;
@@ -467,8 +556,11 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
467
556
  const expRace = mon.race;
468
557
  /* Shapechanged monsters revert on death (mon-util.c L1027). */
469
558
  monsterRevertShape(state, mon);
470
- /* player_kill_monster: dead uniques stay dead (max_num = 0). The flag
471
- * is session-lifetime; persisting it rides the save format (ledgered). */
559
+ /* player_kill_monster: dead uniques stay dead (max_num = 0). This is NOT
560
+ * session-lifetime, contrary to what this comment used to say: the save
561
+ * carries the lore, and the load path re-derives maxNum = 0 for every
562
+ * unique with pkills > 0 (see the SV-01 block below), exactly as
563
+ * load.c:532-535 does. Nothing about it rides an unbuilt save format. */
472
564
  if (mon.race.flags.has(RF.UNIQUE)) {
473
565
  /* bug-fixes #4245 ("Unique coming back to life?"): a unique can produce
474
566
  * multiple "Killed X" kill-history entries via shape-change / projection
@@ -615,6 +707,26 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
615
707
  const floorEnv = {
616
708
  isIgnored: (obj) => state.isIgnored(obj),
617
709
  ...(preds ? { isTrap: preds.isTrap } : {}),
710
+ /* floor_carry_fail's message (obj-pile.c:992-1011). Neither of these two
711
+ * had a producer, so an item that broke on a throw, or vanished because
712
+ * the floor had no room for it, disappeared in total silence - the player
713
+ * was simply short an item with no line to explain it. object_desc's mode
714
+ * is ODESC_BASE (:1002), the bare name. */
715
+ onBreak: (obj, broke) => {
716
+ const name = describeObject(state, obj, ODESC.BASE);
717
+ const verb = broke
718
+ ? obj.number > 1
719
+ ? "break"
720
+ : "breaks"
721
+ : obj.number > 1
722
+ ? "disappear"
723
+ : "disappears";
724
+ state.msg?.(`The ${name} ${verb}.`);
725
+ },
726
+ /* sound(MSG_DROP) (obj-pile.c:1150), the moment an object lands. */
727
+ onDrop: () => {
728
+ state.sound?.(MSG.DROP);
729
+ },
618
730
  };
619
731
  // Monster-death loot deps: makeDeps builds the objects, reg.objects looks
620
732
  // up specified drops, floorEnv places them, state.lore feeds the theft
@@ -625,32 +737,92 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
625
737
  floorEnv,
626
738
  lore: state.lore,
627
739
  };
628
- const teleport = preds
629
- ? {
630
- isPlayerTrap: preds.isPlayerTrap,
631
- isWarded: preds.isWarded,
632
- isWebbed: preds.isWebbed,
633
- /* is_quest (player-quest.c L140): the real implementation behind the
634
- * force_descend / teleport-level guards (effect-general.ts,
635
- * effect-teleport.ts, effect-terrain.ts) - a quest level cannot be
636
- * skipped or recalled away from. */
637
- isQuest: (depth) => isQuest(state.actor.player, depth),
638
- /* dungeon_get_next_level (player-util.c:1147). This seam existed but
639
- * nothing ever wired it, so every consumer silently degraded to a
640
- * bare `depth + dir` - no stair_skip, no max_depth clamp and, worst,
641
- * no quest scan. */
642
- getNextLevel: (from, dir) => dungeonGetNextLevel(state.actor.player, from, dir, state.z),
643
- changeLevel: (targetDepth) => {
644
- state.targetDepth = targetDepth;
645
- state.generateLevel = true;
646
- },
647
- /* player_handle_post_move eval_trap half (player-util.c:1627-1629):
648
- * immediate traps at the landing grid after teleport / thrust. */
649
- onPlayerPostMove: (_byMonster) => {
650
- state.onPlayerMoved?.(state, state.actor.grid);
651
- },
652
- }
653
- : undefined;
740
+ /* The teleport-family environment (game/effect-teleport.ts TeleportEnv).
741
+ *
742
+ * This used to be `preds ? {...seven fields} : undefined` - so a game
743
+ * without the trap registry had NO teleport env at all, and nine of the
744
+ * sixteen fields had no producer anywhere even when it did. Each of those
745
+ * nine is a live upstream read whose subsystem has since been built, so the
746
+ * "inert default" they fell back on was a silently missing feature: the
747
+ * OF_NO_TELEPORT curse never blocked a teleport and its rune was never
748
+ * learned, a teleport could land the player in lava, nexus resistance did
749
+ * not foil a hostile teleport-level, and Dimension Door aborted every time.
750
+ * Only the trap predicates are conditional now.
751
+ *
752
+ * The three player reads are GETTERS, not values: this object is built once
753
+ * per game and every consumer holds the same reference, so a captured
754
+ * boolean would freeze the answer at wiring time - before the player ever
755
+ * picked up the cursed ring. */
756
+ const teleport = {
757
+ ...(preds
758
+ ? {
759
+ isPlayerTrap: preds.isPlayerTrap,
760
+ isWarded: preds.isWarded,
761
+ isWebbed: preds.isWebbed,
762
+ }
763
+ : {}),
764
+ /* square_isdamaging (cave-square.c): fiery terrain. Nothing supplied it,
765
+ * so has_teleport_destination_prereqs happily landed the player in lava. */
766
+ isDamaging: (grid) => state.chunk.isDamaging(grid),
767
+ /* is_quest (player-quest.c L140): the real implementation behind the
768
+ * force_descend / teleport-level guards (effect-general.ts,
769
+ * effect-teleport.ts, effect-terrain.ts) - a quest level cannot be
770
+ * skipped or recalled away from. */
771
+ isQuest: (depth) => isQuest(state.actor.player, depth),
772
+ /* dungeon_get_next_level (player-util.c:1147). This seam existed but
773
+ * nothing ever wired it, so every consumer silently degraded to a
774
+ * bare `depth + dir` - no stair_skip, no max_depth clamp and, worst,
775
+ * no quest scan. */
776
+ getNextLevel: (from, dir) => dungeonGetNextLevel(state.actor.player, from, dir, state.z),
777
+ changeLevel: (targetDepth) => {
778
+ state.targetDepth = targetDepth;
779
+ state.generateLevel = true;
780
+ },
781
+ /* player_handle_post_move eval_trap half (player-util.c:1627-1629):
782
+ * immediate traps at the landing grid after teleport / thrust. */
783
+ onPlayerPostMove: (_byMonster) => {
784
+ state.onPlayerMoved?.(state, state.actor.grid);
785
+ },
786
+ /* handle_stuff(player) after a monster teleports (PU_UPDATE_VIEW): the
787
+ * monster's own visibility and lighting are monsterSwap's, but the
788
+ * player's field of view is not, and a light-carrying monster arriving
789
+ * next door has to change what the player can see. */
790
+ onMonsterPostMove: (_midx) => {
791
+ state.updateFov?.(state);
792
+ },
793
+ /* player_of_has(OF_NO_TELEPORT) and its equip_learn_flag, the pair that
794
+ * makes a Teleportation-forbidding curse do anything at all. */
795
+ get hasNoTeleport() {
796
+ return playerOfHas(state, OF.NO_TELEPORT);
797
+ },
798
+ onLearnNoTeleport: () => {
799
+ equipLearnFlag(state.actor.player, state.runeEnv, OF.NO_TELEPORT);
800
+ },
801
+ /* player_resists(player, ELEM_NEXUS) (player-util.c): res_level > 0, off
802
+ * the LIVE derived state, so a swapped-in resist ring counts. */
803
+ get resistsNexus() {
804
+ return (state.playerState?.elInfo[ELEM.NEXUS]?.resLevel ?? 0) > 0;
805
+ },
806
+ /* player->max_depth: EF_TELEPORT_LEVEL's force_descend target is the
807
+ * deepest level REACHED, not the current one. Defaulting to the current
808
+ * depth made "deep descent"-style descent one level, every time. */
809
+ get maxPlayerDepth() {
810
+ return state.actor.player.maxDepth;
811
+ },
812
+ /* z_info->max_depth, from the bound constants rather than the hardcoded
813
+ * 128 the default assumed - a mod may ship a different dungeon bottom. */
814
+ maxDepth: reg.constants.maxDepth,
815
+ /* OPT(player, birth_force_descend) off the live option store. */
816
+ get forceDescend() {
817
+ return state.options?.get("birth_force_descend") ?? false;
818
+ },
819
+ /* get_aim_dir inside effect_handler_TELEPORT_TO (Dimension Door,
820
+ * effect-handler-general.c:2770-2778). The port cannot block mid-turn, so
821
+ * the shell asks first and rides the direction on the command; core does
822
+ * the resolution because `start` is the player's grid and the offset
823
+ * arithmetic belongs next to the handler, not in a front end. */
824
+ getAimTarget: () => resolveEffectAim(state),
825
+ };
654
826
  // Glyph / web creation needs the trap system; trapDeps joins below
655
827
  // once it is built (the mutual reference is deliberate). The stat
656
828
  // adjectives (desc_stat) come from the bound object properties, and
@@ -712,9 +884,28 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
712
884
  * PARSE_ERROR_INVALID_EFFECT, exactly as before. */
713
885
  lookupEffect: (name) => (effects.isRegistered(name) ? name : null),
714
886
  };
887
+ /* Published on the state so buildObjectEffectChain has these by default.
888
+ * The chain is rebuilt from raw records on every USE and every INSPECT, and
889
+ * a caller that omitted them could not resolve `SUMMON:ANY` - it threw
890
+ * mid-turn. See GameState.effectInject. */
891
+ state.effectInject = inject;
892
+ /* The three projection handler tables (game/projection-handlers.ts), seeded
893
+ * with core's 69. Built here, per game, for the same reason as the blow and
894
+ * store registries: a module-level table would carry one character's mod
895
+ * into the next character's game.
896
+ *
897
+ * Handed to the engine BY IDENTITY below - `worldEnv` and the playerHandlers
898
+ * dep hold these very Maps. A mod's register() runs after this wiring, so a
899
+ * snapshot would be a seam that ignored every mod. */
900
+ const projectionHandlers = new ProjectionHandlerRegistry();
901
+ state.projectionHandlers = projectionHandlers;
715
902
  // project_o / project_f world access; trapDeps joins it below once the
716
903
  // trap system is wired (the mutual reference is deliberate).
717
- const worldEnv = { makeDeps };
904
+ const worldEnv = {
905
+ makeDeps,
906
+ featHandlers: projectionHandlers.feat.table,
907
+ objHandlers: projectionHandlers.obj.table,
908
+ };
718
909
  /* The projection view reads the LIVE derived state, so worn resistance
719
910
  * gear reduces projection damage and equipment swaps take effect. */
720
911
  const playerActor = basicPlayerActor(state, {
@@ -751,20 +942,19 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
751
942
  monsterDeath(state, m, deathDeps);
752
943
  },
753
944
  /* add_monster_message: "the kobold dies", "wakes up", "catches
754
- * fire" - the MON_MSG grammar, routed to the game's message sink so
755
- * a shell shows ranged/spell/status monster messages the same way
756
- * melee shows "You hit/slay the X". The projection already gates on
945
+ * fire" - STACKED on the mon_msg[] queue, not emitted here, so one
946
+ * breath over a pit produces "8 kobolds die." and the deaths come
947
+ * last. noticeStuff drains it. The projection already gates on
757
948
  * visibility before calling this. */
758
- message: (m, msgCode, _delay, damage) => {
949
+ message: (m, msgCode, delay, damage) => {
759
950
  /* add_monster_message_show_damage (mon-msg.c:288) when the driver
760
951
  * passed a damage total, else add_monster_message (L252). */
761
- const text = damage === undefined
762
- ? formatMonsterMessage(m, msgCode)
763
- : formatMonsterMessageShowDamage(m, msgCode, damage);
764
- const type = monMessageSoundType(msgCode, m.race);
765
- if (text)
766
- state.msg?.(text, type);
767
- state.sound?.(type);
952
+ if (damage === undefined) {
953
+ addMonsterMessage(state, m, msgCode, delay);
954
+ }
955
+ else {
956
+ addMonsterMessageShowDamage(state, m, msgCode, delay, damage);
957
+ }
768
958
  },
769
959
  /* OPT(player, show_damage): project_m_player_attack's display_dam
770
960
  * (project-mon.c:1111) picks the *_show_damage message variants. */
@@ -772,19 +962,18 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
772
962
  /* message_pain: the graded "shrugs off the attack" / "cries out in
773
963
  * pain" line for a monster hurt but not killed. */
774
964
  messagePain: (m, dam, showDamage) => {
775
- const text = showDamage
776
- ? formatPainMessageShowDamage(m, dam)
777
- : formatPainMessage(m, dam);
778
- const type = monMessageSoundType(painMessageCode(m, dam), m.race);
779
- if (text)
780
- state.msg?.(text, type);
781
- state.sound?.(type);
965
+ if (showDamage)
966
+ messagePainShowDamage(state, m, dam);
967
+ else
968
+ messagePain(state, m, dam);
782
969
  },
783
- /* mon_set_timed's queued status messages (slowed, confused, held). */
970
+ /* mon_set_timed's queued status messages (slowed, confused, held),
971
+ * add_monster_message(mon, m_note, true) at mon-timed.c:215 - the
972
+ * delayed pass, so a status line follows the blow that caused it. */
784
973
  timedMessage: (m, note) => {
785
- const text = formatMonsterMessageByName(m, note);
786
- if (text)
787
- state.msg?.(text);
974
+ const code = monMessageCodeByName(note);
975
+ if (code !== null)
976
+ addMonsterMessage(state, m, code, true);
788
977
  },
789
978
  /* Lore learning when a projection's outcome is seen. */
790
979
  learnRaceFlag: (m, flag) => loreLearnFlagIfVisible(getLore(state.lore, m.race), m, flag),
@@ -793,8 +982,31 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
793
982
  revertShape: (m) => {
794
983
  monsterRevertShape(state, m);
795
984
  },
985
+ /* poly_race + the delete/place swap (project-mon.c:45, :1225-1229).
986
+ * Both halves are supplied, and separately, because upstream queues
987
+ * MON_MSG_CHANGE between them - naming the old monster while it still
988
+ * exists. Until this was wired the hook had NO supplier at all, so a
989
+ * Wand of Polymorph rolled its saving throw, spent the charge and then
990
+ * reported "maintains its shape" every single time.
991
+ *
992
+ * ambientPlaceDeps is declared later in this same function; the
993
+ * forward reference is safe for the same reason state.monsterMultiply
994
+ * (which uses it too) is - these arrows only run during play. Reusing
995
+ * it rather than a private table also means the polymorphed monster
996
+ * gets its held drops, as place_monster does for any origin != 0. */
997
+ polyRace: (race) => polyRace(state, race, state.chunk.depth, ambientPlaceDeps),
998
+ replaceMonster: (m, race) => polymorphMonster(state, m, race, ambientPlaceDeps),
796
999
  /* PROJ_AWAY_ALL teleports and PROJ_FORCE knockback for monsters. */
797
- teleport: (m, dist) => teleportMonster(state, m.midx, dist, teleport ?? {}),
1000
+ teleport: (m, dist) => teleportMonster(state, m.midx, dist, teleport),
1001
+ /* update_mon(mon, cave, false) on a monster that SURVIVED the
1002
+ * projection (project-mon.c:1262). Unsupplied, so a monster that was
1003
+ * polymorphed, knocked back, woken or revealed by a spell kept its
1004
+ * pre-projection visibility until something else happened to move it.
1005
+ * updateMon has been ported since the FOV work; this hook was simply
1006
+ * never given a producer. */
1007
+ onUpdate: (mon) => {
1008
+ updateMon(state, mon, false);
1009
+ },
798
1010
  /* project-mon.c:183-185 / 208-212 + player thrust landing. */
799
1011
  thrustAway: (centre, target, gridsAway) => thrustAway(state, centre, target, gridsAway, {
800
1012
  onPlayerPostMove: () => {
@@ -819,7 +1031,21 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
819
1031
  projections: reg.projections,
820
1032
  expDeps,
821
1033
  lifeDrainPercent: reg.constants.lifeDrainPercent,
822
- ...(teleport ? { teleport } : {}),
1034
+ playerHandlers: projectionHandlers.player.table,
1035
+ /* project_p's OWN messages (project-player.c): "You resist the
1036
+ * effect!", "The intense heat saps you.", "Your eyes fill with
1037
+ * smoke!" - thirty-odd lines across the 21 arms, plus every timed
1038
+ * effect's onMessage, which makePlayerSideEffects gates on this
1039
+ * same optional.
1040
+ *
1041
+ * UNSUPPLIED UNTIL 2026-08-09, so all of them were dropped in the
1042
+ * live game and only the outer "You are hit by fire!" survived.
1043
+ * The seam was optional, every harness that exercised the arms
1044
+ * supplied it, and the one caller that matters did not - found by
1045
+ * a mod handler calling ctx.msg and getting silence
1046
+ * (session/projection-registry-wiring.test.ts). */
1047
+ msg: (text) => state.msg?.(text),
1048
+ teleport,
823
1049
  }),
824
1050
  /* adjust_dam(actual=true) equip_learn_element (project-player.c
825
1051
  * L60-62, audit 01 P5): being hit by an element teaches the resist
@@ -853,6 +1079,14 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
853
1079
  * are gated. Absent, every increase was allowed. Same shape as
854
1080
  * buildFailRuneEnv / makePlayerSideEffects. */
855
1081
  const incQueries = makeIncCheckQueries(state);
1082
+ /* player_inc_check's equip-learn side effects on the INTERPRETER path -
1083
+ * traps, potions, wands, player spells. Only mon-cast.ts supplied incHooks
1084
+ * before, so a trap you were immune to taught nothing, where upstream's
1085
+ * non-lore branch always calls equip_learn_flag / equip_learn_element
1086
+ * (player-timed.c:945, :967, :985). No monster is passed: these sources are
1087
+ * not cave->mon_current, so update_smart_learn and "You resist the effect!"
1088
+ * correctly stay silent. */
1089
+ const incHooks = makeIncCheckHooks(state);
856
1090
  const envDeps = {
857
1091
  timedTable: players.timed,
858
1092
  // Effect status/damage messages ("You feel better", "You feel yourself
@@ -860,6 +1094,10 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
860
1094
  // them; absent, they would drop.
861
1095
  onMessage: (text, msgt) => state.msg?.(text, msgt),
862
1096
  incQueries,
1097
+ incHooks,
1098
+ /* player_set_timed's notify suppression: the obj_k reads that silence a
1099
+ * message duplicating known player state (player-timed.c:828-839). */
1100
+ notifyQueries: makeTimedNotifyQueries(state),
863
1101
  /* on_begin_effect / on_end_effect (audit 01 T2): the interpreter timed
864
1102
  * path (a SCRAMBLE / SPRINT potion or spell) must run the chain too, not
865
1103
  * just the world clock. The thunk reads runTimedTransition (assigned just
@@ -892,7 +1130,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
892
1130
  state,
893
1131
  cast,
894
1132
  takeHitHooks: sharedTakeHitHooks,
895
- ...(teleport ? { teleport } : {}),
1133
+ teleport,
896
1134
  general,
897
1135
  item,
898
1136
  summon,
@@ -913,7 +1151,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
913
1151
  cast,
914
1152
  envDeps,
915
1153
  inject,
916
- ...(teleport ? { teleport } : {}),
1154
+ teleport,
917
1155
  general,
918
1156
  item,
919
1157
  summon,
@@ -927,7 +1165,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
927
1165
  change: (m) => monsterChangeShape(state, m, {
928
1166
  summon,
929
1167
  spells: reg.monsters.spells,
930
- ...(teleport ? { teleport } : {}),
1168
+ teleport,
931
1169
  }),
932
1170
  revert: (m) => monsterRevertShape(state, m),
933
1171
  };
@@ -943,7 +1181,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
943
1181
  envDeps,
944
1182
  saveSkill: pstate.skills[SKILL.SAVE] ?? 0,
945
1183
  inject,
946
- ...(teleport ? { teleport } : {}),
1184
+ teleport,
947
1185
  general,
948
1186
  summon,
949
1187
  monShape,
@@ -971,10 +1209,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
971
1209
  * path - die/pain messages, loot via monster_death, fear rolls. The
972
1210
  * scheduler reads these for monster_take_terrain_damage; without them
973
1211
  * terrain damage was inert. */
974
- installNonplayerHitDeps(state, {
975
- ...deathDeps,
976
- message: (text) => state.msg?.(text),
977
- });
1212
+ installNonplayerHitDeps(state, deathDeps);
978
1213
  /* does_resist lore (mon-timed.c L107-110, gap 8.6): when a monster resists a
979
1214
  * timed effect via a race flag, learn that flag into visible lore. The
980
1215
  * timed layer has no deps param (get_lore is a global upstream), so register
@@ -985,6 +1220,17 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
985
1220
  * applies its full elemental / status / stat / theft / terrain
986
1221
  * consequences in upstream RNG order. EF_EARTHQUAKE (SHATTER) routes
987
1222
  * through the effect interpreter so its internal draws are shared. */
1223
+ /* The blow-effect handler table (combat/mon-melee.ts). Built here, per game,
1224
+ * and seeded with core's 30: a module-level singleton would carry one
1225
+ * character's mod-registered blow into the next character's game. */
1226
+ const blowEffects = new BlowEffectRegistry();
1227
+ registerCoreBlowEffects(blowEffects);
1228
+ state.blowEffects = blowEffects;
1229
+ /* Store behaviour (store/store.ts): what a shop will buy, and how many of a
1230
+ * thing it stocks. Per game for the same reason as the blow table. */
1231
+ const storeBehaviour = new StoreBehaviourRegistry();
1232
+ registerCoreStoreBehaviour(storeBehaviour);
1233
+ state.storeBehaviour = storeBehaviour;
988
1234
  state.monBlowEnv = makeMonBlowEnv(state, {
989
1235
  timed: players.timed,
990
1236
  actor: playerActor,
@@ -994,7 +1240,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
994
1240
  adjDexSafe: adj_dex_safe,
995
1241
  packSize: reg.constants.packSize,
996
1242
  makeDeps,
997
- ...(teleport ? { teleport } : {}),
1243
+ teleport,
998
1244
  earthquake: (mon, radius) => {
999
1245
  effects.effectSimple(EF.EARTHQUAKE, buildEffectContext(state, envDeps), {
1000
1246
  origin: sourceMonster(mon.midx),
@@ -1016,7 +1262,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1016
1262
  * is applied by applyAutoinscription. */
1017
1263
  autoNote: (kind, aware) => state.autoinscribe?.get(kind.kidx, aware) ?? null,
1018
1264
  inject,
1019
- ...(teleport ? { teleport } : {}),
1265
+ teleport,
1020
1266
  general,
1021
1267
  item,
1022
1268
  summon,
@@ -1064,7 +1310,10 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1064
1310
  radius: 10,
1065
1311
  });
1066
1312
  },
1067
- temp: buildTempBrandSlay(state.actor.player, pack.player.timed, reg.objects.brands, reg.objects.slays),
1313
+ /* The ONE bound instance (state.tempBrandSlay), not a private second copy.
1314
+ * This used to build its own, which is why nothing outside the melee path
1315
+ * could ask the question - PORT_TODO 3.20. */
1316
+ temp: state.tempBrandSlay,
1068
1317
  });
1069
1318
  // Player spellcasting (cast / study) for casting classes.
1070
1319
  installSpellCommands(registry, {
@@ -1073,7 +1322,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1073
1322
  cast,
1074
1323
  envDeps,
1075
1324
  inject,
1076
- ...(teleport ? { teleport } : {}),
1325
+ teleport,
1077
1326
  general,
1078
1327
  item,
1079
1328
  summon,
@@ -1098,7 +1347,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1098
1347
  // Player ranged attacks (fire launcher + ammo, throw an object). The hit
1099
1348
  // math is combat/ranged.ts; the front-end walks the missile's path and
1100
1349
  // routes hit / death messages through state.msg like the other commands.
1101
- installRangedCommands(registry);
1350
+ installRangedCommands(registry, floorEnv);
1102
1351
  // Traps: disarm + the step-onto-trap hook; a trapdoor drops a level.
1103
1352
  if (reg.traps) {
1104
1353
  trapDeps = {
@@ -1108,7 +1357,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1108
1357
  cast,
1109
1358
  envDeps,
1110
1359
  inject,
1111
- ...(teleport ? { teleport } : {}),
1360
+ teleport,
1112
1361
  general,
1113
1362
  item,
1114
1363
  summon,
@@ -1118,6 +1367,15 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1118
1367
  msg: (text) => state.msg?.(text),
1119
1368
  /* player_of_has for trap saves / OF_TRAP_IMMUNE (trap.c:515-539). */
1120
1369
  playerHasFlag: (flag) => state.playerState?.flags.has(flag) ?? false,
1370
+ /* is_quest(player->depth) (trap.c:310-311): no trap doors on a quest
1371
+ * level - the guardian's floor must not open under the player. The
1372
+ * generation path already supplied this (gen/util.ts, from dun.quest);
1373
+ * the RUNTIME path did not, so every trap laid after generation - by
1374
+ * EF_TOUCH:MAKE_TRAP, by square_add_trap - drew from a table that
1375
+ * still contained the trapdoor on depths 99 and 100. Dropping a kind
1376
+ * also shifts pick_trap's cumulative total, so the omission moved the
1377
+ * whole draw, not just the trapdoor. */
1378
+ isQuest: (depth) => isQuest(state.actor.player, depth),
1121
1379
  changeLevel: (s) => {
1122
1380
  /* trap.c:579-582: a TRF_DOWN trapdoor drops you through
1123
1381
  * dungeon_get_next_level, not blindly one level. */
@@ -1144,7 +1402,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1144
1402
  cast,
1145
1403
  envDeps,
1146
1404
  inject,
1147
- ...(teleport ? { teleport } : {}),
1405
+ teleport,
1148
1406
  general,
1149
1407
  item,
1150
1408
  summon,
@@ -1164,7 +1422,7 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1164
1422
  cast,
1165
1423
  envDeps,
1166
1424
  inject,
1167
- ...(teleport ? { teleport } : {}),
1425
+ teleport,
1168
1426
  general,
1169
1427
  item,
1170
1428
  summon,
@@ -1280,6 +1538,10 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1280
1538
  if (canDisturb)
1281
1539
  disturb(state);
1282
1540
  },
1541
+ /* player_set_timed's notify suppression on the WORLD CLOCK path too -
1542
+ * this is where a timed resist most often ticks down (player-timed.c:
1543
+ * 828-839). */
1544
+ notifyQueries: makeTimedNotifyQueries(state),
1283
1545
  incCheck: (idx) => {
1284
1546
  const eff = players.timed[idx];
1285
1547
  return eff ? playerIncCheck(eff, worldIncQueries) : true;
@@ -1294,8 +1556,13 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1294
1556
  * the {name}/{kind}/{s}/{is} tags in weapon-related timed messages (the
1295
1557
  * temporary-brand on-begin/on-end lines) resolve against the CURRENTLY
1296
1558
  * wielded weapon, so a getter keeps it live across wield/takeoff. name /
1297
- * kind use the ODESC_BASE approximation (kind name) already adopted across
1298
- * the port (obj-knowledge.ts). No weapon => the obj == NULL tag forms. */
1559
+ * PORT_TODO 3.23: {name} and {kind} are DIFFERENT upstream functions, and
1560
+ * neither is the raw kind name this used to pass. {name} is
1561
+ * object_desc(ODESC_PREFIX | ODESC_BASE) - so it carries the article - and
1562
+ * {kind} is object_kind_name(kind, easy_know=true), which with easy_know
1563
+ * set is exactly obj_desc_name_format(kind->name, NULL, false). Passing
1564
+ * `w.kind.name` printed the raw `&` and `~` markers to the player.
1565
+ * No weapon => the obj == NULL tag forms. */
1299
1566
  get weapon() {
1300
1567
  const w = state.actor.weapon;
1301
1568
  /* No weapon => the obj == NULL tag forms. A sentinel with name/kind
@@ -1305,7 +1572,11 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1305
1572
  * that returns undefined. */
1306
1573
  if (!w)
1307
1574
  return { name: "hands", kind: "hands", number: 2 };
1308
- return { name: w.kind.name, kind: w.kind.name, number: w.number };
1575
+ return {
1576
+ name: describeObject(state, w, ODESC.PREFIX | ODESC.BASE),
1577
+ kind: objDescNameFormat(w.kind.name, null, false),
1578
+ number: w.number,
1579
+ };
1309
1580
  },
1310
1581
  },
1311
1582
  /* The same shared consequences drive the world clock's DoT ticks, terrain
@@ -1333,6 +1604,15 @@ function wireGame(state, reg, players, pstate, seedFlavor, pack) {
1333
1604
  ...(wizardEffect ? { effect: wizardEffect } : {}),
1334
1605
  ...(trapDeps ? { trapDeps } : {}),
1335
1606
  };
1607
+ /* Seed p->known_state now that runeEnv and the flavour store are live. */
1608
+ refreshKnownCombat(state.actor.player, {
1609
+ equipment: state.actor.player.equipment.map((h) => h ? gearGet(state.gear, h) : null),
1610
+ timedEffects: players.timed,
1611
+ curses: reg.objects.curses,
1612
+ update: true,
1613
+ depth: state.chunk.depth,
1614
+ isDaytime: isDaytime(state.turn, state.z.dayLength),
1615
+ });
1336
1616
  return { registry, trapDeps, flavor, everseen, effects: effectRegistry, wizardBundles };
1337
1617
  }
1338
1618
  /**
@@ -1345,9 +1625,14 @@ function populateFromLevel(state, level, trapDeps) {
1345
1625
  const spot = level.playerSpot ?? loc(1, 1);
1346
1626
  state.actor.grid = spot;
1347
1627
  placePlayer(state, spot);
1348
- /* Track the deepest level reached (player->max_depth). */
1628
+ /* Track the deepest level reached: on_new_level (game-world.c:1023-1024)
1629
+ * sets `max_depth = recall_depth = depth`, BOTH of them. recall_depth is the
1630
+ * anchor Word of Recall drops you back to, and this assignment is its only
1631
+ * producer on the ordinary walk down - EF_RECALL re-anchors it only when the
1632
+ * scroll is read in the dungeon. Setting max_depth alone left recall_depth at
1633
+ * 0 forever for a character who never read one below the town. */
1349
1634
  if (level.depth > state.actor.player.maxDepth) {
1350
- state.actor.player.maxDepth = level.depth;
1635
+ state.actor.player.maxDepth = state.actor.player.recallDepth = level.depth;
1351
1636
  }
1352
1637
  /* A new level clears the decoy (glyph traps do not persist the swap). */
1353
1638
  state.decoy = null;
@@ -1487,10 +1772,16 @@ function sanitizePlayerLoc(state) {
1487
1772
  * object, whose chunk and entity stores are replaced).
1488
1773
  */
1489
1774
  function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1490
- /* The level stashed when entering an arena: leaving a level FOR an
1491
- * arena persists it even without birth_levels_persist (generate.c
1492
- * L1349), and the arena exit restores it. */
1493
- let arenaStash = null;
1775
+ /* The level stashed when entering an arena lives on GameState
1776
+ * (state.arenaStash), not in this closure: leaving a level FOR an arena
1777
+ * persists it even without birth_levels_persist (generate.c L1349), and
1778
+ * upstream keeps that copy in the chunk_list, which the savefile carries. A
1779
+ * save taken mid-fight used to lose the level behind it and exit onto a
1780
+ * fresh one; now it exits where it went in.
1781
+ *
1782
+ * `inArena` stays a separate flag rather than "the stash exists", because a
1783
+ * save written before the stash was persisted restores with arena_level set
1784
+ * and no stash, and still has to be let out of the arena. */
1494
1785
  let inArena = opts.inArena ?? false;
1495
1786
  return (depth) => {
1496
1787
  /* Consume the pending arrival-stair request (create_up_stair /
@@ -1504,7 +1795,7 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1504
1795
  if (state.arenaLevel && !inArena) {
1505
1796
  const mon = state.healthWho;
1506
1797
  if (mon) {
1507
- arenaStash = {
1798
+ state.arenaStash = {
1508
1799
  chunk: state.chunk,
1509
1800
  monsters: state.monsters,
1510
1801
  groups: state.groups,
@@ -1513,6 +1804,11 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1513
1804
  known: state.known,
1514
1805
  decoy: state.decoy ?? null,
1515
1806
  monMidx: mon.midx,
1807
+ /* StoredLevel's other two fields. The freeze turn is stamped as
1808
+ * cave_store does; `join` is empty because an arena exit restores
1809
+ * the level wholesale and never re-aligns stairs against it. */
1810
+ turn: state.turn,
1811
+ join: [],
1516
1812
  };
1517
1813
  /* arena_gen (gen-cave.c L3984): 6x6 floor bounded by perm rock,
1518
1814
  * the player in one corner and the opponent in the other. */
@@ -1559,6 +1855,17 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1559
1855
  placePlayer(state, loc(1, 4));
1560
1856
  inArena = true;
1561
1857
  delete state.targetDepth;
1858
+ /* on_new_level (game-world.c:1034-1035): PN_COMBINE then notice_stuff,
1859
+ * one line BEFORE update_stuff, so the combine lands first. */
1860
+ noticeNewLevel(state);
1861
+ /* cmd_disable_repeat_floor_item (game-world.c:1068). Upstream's call is
1862
+ * in on_leave_level, which this port has no single equivalent of -
1863
+ * `generateLevel = true` is set at fourteen sites. It rides the ENTRY
1864
+ * path instead, which is observationally identical: the change is
1865
+ * synchronous, so the repeat key cannot be pressed between leaving and
1866
+ * arriving. The old level's pile is gone either way, so a remembered
1867
+ * args.floor index must not be re-dispatched. */
1868
+ cmdDisableRepeatFloorItem(state.actor.player);
1562
1869
  state.updateBonuses?.(); /* on_new_level PU_BONUS -> calc_light */
1563
1870
  state.updateFov?.(state);
1564
1871
  return;
@@ -1570,8 +1877,8 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1570
1877
  if (inArena) {
1571
1878
  inArena = false;
1572
1879
  state.arenaLevel = false;
1573
- const stash = arenaStash;
1574
- arenaStash = null;
1880
+ const stash = state.arenaStash;
1881
+ delete state.arenaStash;
1575
1882
  if (stash) {
1576
1883
  /* Restore the level left behind (the player marker stayed). */
1577
1884
  state.chunk = stash.chunk;
@@ -1598,6 +1905,17 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1598
1905
  state.healthWho = null;
1599
1906
  targetSetMonster(state, null);
1600
1907
  delete state.targetDepth;
1908
+ /* on_new_level (game-world.c:1034-1035): PN_COMBINE then notice_stuff,
1909
+ * one line BEFORE update_stuff, so the combine lands first. */
1910
+ noticeNewLevel(state);
1911
+ /* cmd_disable_repeat_floor_item (game-world.c:1068). Upstream's call is
1912
+ * in on_leave_level, which this port has no single equivalent of -
1913
+ * `generateLevel = true` is set at fourteen sites. It rides the ENTRY
1914
+ * path instead, which is observationally identical: the change is
1915
+ * synchronous, so the repeat key cannot be pressed between leaving and
1916
+ * arriving. The old level's pile is gone either way, so a remembered
1917
+ * args.floor index must not be re-dispatched. */
1918
+ cmdDisableRepeatFloorItem(state.actor.player);
1601
1919
  state.updateBonuses?.(); /* on_new_level PU_BONUS -> calc_light */
1602
1920
  state.updateFov?.(state);
1603
1921
  return;
@@ -1606,9 +1924,12 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1606
1924
  * fresh level of the same depth (ledgered). */
1607
1925
  delete state.oldGrid;
1608
1926
  }
1609
- /* dungeon_change_level: track the deepest level reached. */
1927
+ /* dungeon_change_level: track the deepest level reached. The same
1928
+ * `max_depth = recall_depth = depth` pair as populateFromLevel, because the
1929
+ * port checks on both the request and the arrival where upstream checks
1930
+ * once in on_new_level - whichever fires first, the other is a no-op. */
1610
1931
  if (depth > state.actor.player.maxDepth) {
1611
- state.actor.player.maxDepth = depth;
1932
+ state.actor.player.maxDepth = state.actor.player.recallDepth = depth;
1612
1933
  }
1613
1934
  /* birth_levels_persist (#30, off by default): when on, the level being
1614
1935
  * left is frozen into a depth-keyed cache and a previously-frozen target
@@ -1703,10 +2024,25 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1703
2024
  installChunkFeatHook(state);
1704
2025
  refreshTownStores(state, reg);
1705
2026
  delete state.targetDepth;
2027
+ /* on_new_level (game-world.c:1034-1035): PN_COMBINE then notice_stuff,
2028
+ * one line BEFORE update_stuff, so the combine lands first. */
2029
+ noticeNewLevel(state);
2030
+ /* cmd_disable_repeat_floor_item (game-world.c:1068). Upstream's call is
2031
+ * in on_leave_level, which this port has no single equivalent of -
2032
+ * `generateLevel = true` is set at fourteen sites. It rides the ENTRY
2033
+ * path instead, which is observationally identical: the change is
2034
+ * synchronous, so the repeat key cannot be pressed between leaving and
2035
+ * arriving. The old level's pile is gone either way, so a remembered
2036
+ * args.floor index must not be re-dispatched. */
2037
+ cmdDisableRepeatFloorItem(state.actor.player);
1706
2038
  state.updateBonuses?.(); /* on_new_level PU_BONUS -> calc_light */
1707
2039
  /* only_partial during level-entry FOV (ui-display.c:2522 / cave-view.c:851). */
1708
2040
  state.chunk.onlyPartial = true;
1709
2041
  state.updateFov?.(state);
2042
+ /* on_new_level's own disturb (game-world.c:1016-1017), immediately before
2043
+ * the feeling and the search: arriving on a level cancels whatever was
2044
+ * still queued from the level you left. */
2045
+ disturb(state);
1710
2046
  announceFeeling(state, reg);
1711
2047
  search(state); /* on_new_level (game-world.c:1052). */
1712
2048
  state.chunk.onlyPartial = false;
@@ -1727,6 +2063,7 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1727
2063
  * and generateLevel builds exactly as before. */
1728
2064
  const persistCache = state.levelCache;
1729
2065
  let joinInfo;
2066
+ let minSize;
1730
2067
  if (persist && persistCache) {
1731
2068
  const above = persistCache.get(depth - 1)?.join;
1732
2069
  const twoAbove = persistCache.get(depth - 2)?.join;
@@ -1738,13 +2075,27 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1738
2075
  ...(below ? { below } : {}),
1739
2076
  ...(twoBelow ? { twoBelow } : {}),
1740
2077
  });
2078
+ /* get_min_level_size (prepare_next_level L1531-1546): the new level must
2079
+ * be big enough to hold the stairs its frozen neighbours already expect,
2080
+ * or build_staircase_rooms has nowhere to put them and quits. Only the
2081
+ * IMMEDIATE neighbours are measured - a one-off connector two levels away
2082
+ * seeds dun.one_off_* (an avoid list), never a staircase room, so it
2083
+ * imposes no minimum. Starts at 0/0 exactly as the C's locals do. */
2084
+ minSize = getMinLevelSize(below ?? [], false, getMinLevelSize(above ?? [], true));
1741
2085
  }
1742
2086
  /* choose_profile's NOSCORE_JUMPING request (generate.c L824-836): consumed
1743
2087
  * once, cleared as it is passed on, exactly as the C clears the bit. */
1744
2088
  const jumpProfileName = state.jumpProfileName;
1745
2089
  state.jumpProfileName = undefined;
1746
2090
  const g = generateLevel(state.rng, depth, {
1747
- ...genDeps(reg, true, state.artifacts, state.options?.get("birth_no_artifacts") ?? false,
2091
+ ...genDeps(reg, true,
2092
+ /* The player-dependent make_object / make_gold foils. These were
2093
+ * supplied to both STORE paths and to nothing else, so during level
2094
+ * generation - the path they exist for - every book was browsable, no
2095
+ * curse was ever foiled, and generated gold skipped the no-selling
2096
+ * inflation. Two of the three are RNG draws, so the generation stream
2097
+ * itself was off upstream's. */
2098
+ genFoilFields(state), state.artifacts, state.options?.get("birth_no_artifacts") ?? false,
1748
2099
  /* mon_create_drop's unique theft reduction reads the live lore store. */
1749
2100
  state.lore),
1750
2101
  /* The mod behaviour seam, read at build time (this call is synchronous,
@@ -1774,6 +2125,10 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1774
2125
  * unconditionally; only read under persist. */
1775
2126
  hasAdjacentAbove: persistCache?.has(depth - 1) ?? false,
1776
2127
  hasAdjacentBelow: persistCache?.has(depth + 1) ?? false,
2128
+ /* get_min_level_size's answer (prepare_next_level L1531-1546), the
2129
+ * only producer these two builder inputs have. Omitted entirely when
2130
+ * not persisting, so generateLevel keeps its own defaults. */
2131
+ ...(minSize ? { minHeight: minSize.height, minWidth: minSize.width } : {}),
1777
2132
  /* birth_connect_stairs (gen-util.c:427-433): lay the arrival stair the
1778
2133
  * stair command requested ("up" after a descent, "down" after an
1779
2134
  * ascent), unless the option is off. Recall/first-spawn leave
@@ -1846,6 +2201,12 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1846
2201
  g.lightLevel = false;
1847
2202
  }
1848
2203
  delete state.targetDepth;
2204
+ /* on_new_level (game-world.c:1034-1035): PN_COMBINE then notice_stuff, one
2205
+ * line BEFORE update_stuff, so the combine lands first. */
2206
+ noticeNewLevel(state);
2207
+ /* cmd_disable_repeat_floor_item (game-world.c:1068); see the note at the
2208
+ * other level-entry sites for why it rides entry rather than leave. */
2209
+ cmdDisableRepeatFloorItem(state.actor.player);
1849
2210
  /* on_new_level (game-world.c:1034-1037): PU_BONUS -> update_bonuses ->
1850
2211
  * calc_light recomputes cur_light for the NEW depth, then the view is
1851
2212
  * flooded. Without this the daytime-town cur_light (0) leaks into the
@@ -1855,6 +2216,10 @@ function makeChangeLevel(state, reg, trapDeps, opts = {}) {
1855
2216
  /* only_partial during level-entry FOV (ui-display.c:2522 / cave-view.c:851). */
1856
2217
  state.chunk.onlyPartial = true;
1857
2218
  state.updateFov?.(state);
2219
+ /* on_new_level's own disturb (game-world.c:1016-1017), immediately before
2220
+ * the feeling and the search: arriving on a level cancels whatever was
2221
+ * still queued from the level you left. */
2222
+ disturb(state);
1858
2223
  announceFeeling(state, reg);
1859
2224
  search(state); /* on_new_level (game-world.c:1052). */
1860
2225
  state.chunk.onlyPartial = false;
@@ -1959,6 +2324,11 @@ function refreshTownStores(state, reg) {
1959
2324
  deps: storeDeps,
1960
2325
  maxDepth: state.actor.player.maxDepth,
1961
2326
  stores: state.stores,
2327
+ /* history_lose_artifact (store.c:1091 / :1307): an artifact the player
2328
+ * sold into stock and the store then turned over or purged is gone. */
2329
+ onArtifactLost: (art) => state.onArtifactLost?.(art),
2330
+ /* Store behaviour a mod may have added to (StoreBehaviourRegistry). */
2331
+ ...(state.storeBehaviour ? { behaviour: state.storeBehaviour } : {}),
1962
2332
  /* OPT(cheat_xtra) (store.c:1424, :1444). */
1963
2333
  ...(state.options?.get("cheat_xtra")
1964
2334
  ? { cheatMsg: (text) => state.msg?.(text) }
@@ -2006,17 +2376,24 @@ export function startGame(pack, opts = {}) {
2006
2376
  const reg = bindCore(pack);
2007
2377
  const players = bindPlayer(pack.player);
2008
2378
  registerBookKinds(reg.objects, players.classes);
2009
- // The player option store (option.c options_init_defaults): seeded from
2010
- // OPTION_ENTRIES defaults, with the birth/interface choices applied. Built
2011
- // before level generation so birth_randarts can swap the artifact set first.
2012
- // Every Angband option (show_damage, center_player, auto_more, ...) ships in
2013
- // core with its faithful upstream default (OPTION_ENTRIES.normal); the player
2014
- // changes them in the options menu exactly as upstream. Only explicit birth /
2015
- // interface CHOICES (birth screen, point-buy) are applied here as overrides,
2016
- // so with none supplied the store is byte-identical to the table.
2379
+ // The player option store, options_init_defaults whole (option.c L186-205,
2380
+ // called from player_init at player.c:491). In upstream's order:
2381
+ //
2382
+ // 1. every option from its table `normal` (OPTION_ENTRIES),
2383
+ // 2. the player's customised BIRTH defaults from ANGBAND_DIR_USER,
2384
+ // 3. their customised INTERFACE defaults,
2385
+ // 4. delay_factor = 40 and hitpoint_warn = 3,
2386
+ //
2387
+ // and only then this call's explicit birth/interface CHOICES on top. Steps 2
2388
+ // and 3 read customized_<page>_options.txt through the installed host; with
2389
+ // no host, or no such file, they are exactly the table defaults, which is why
2390
+ // a test or an MCP session sees no change. Built before level generation so
2391
+ // birth_randarts can swap the artifact set first.
2392
+ const initial = optionsInitDefaults(host());
2017
2393
  const options = new OptionState({
2018
- ...(opts.optionOverrides ? { overrides: opts.optionOverrides } : {}),
2019
- ...(opts.hitpointWarn !== undefined ? { hitpointWarn: opts.hitpointWarn } : {}),
2394
+ overrides: { ...initial.opts, ...(opts.optionOverrides ?? {}) },
2395
+ hitpointWarn: opts.hitpointWarn ?? initial.hitpointWarn,
2396
+ delayFactor: initial.delayFactor,
2020
2397
  });
2021
2398
  // options_init_cheat (player-birth.c:1234, gap 1.12): at character accept,
2022
2399
  // clear every cheat option and its score twin, THEN re-apply the port-only
@@ -2160,6 +2537,10 @@ export function startGame(pack, opts = {}) {
2160
2537
  speed: pstate.speed,
2161
2538
  totalEnergy: 0,
2162
2539
  combat,
2540
+ /* p->known_state, seeded with the real state and replaced with the true
2541
+ * known derive at the end of wireGame - the flavour store and the rune env
2542
+ * it needs do not exist yet at this line. */
2543
+ knownCombat: combat,
2163
2544
  defense: toDefenderState(pstate),
2164
2545
  weapon,
2165
2546
  stealth: combat.skills[SKILL.STEALTH] ?? 0,
@@ -2194,6 +2575,12 @@ export function startGame(pack, opts = {}) {
2194
2575
  /* birth_levels_persist (#30) frozen-level cache; empty until a level is
2195
2576
  * left with the option on (the whole persist path is option-gated). */
2196
2577
  levelCache: new Map(),
2578
+ /* The STARTING level's stair connectors, on the same option gate
2579
+ * changeLevel uses. Without this the first level a character ever stands on
2580
+ * freezes with an empty join list and its neighbour cannot align to it. */
2581
+ ...((options.get("birth_levels_persist") ?? false)
2582
+ ? { currentJoins: [...booted.joins] }
2583
+ : {}),
2197
2584
  turn: 0,
2198
2585
  z: {
2199
2586
  ...DEFAULT_GAME_CONSTANTS,
@@ -2211,6 +2598,17 @@ export function startGame(pack, opts = {}) {
2211
2598
  },
2212
2599
  brands: reg.objects.brands,
2213
2600
  slays: reg.objects.slays,
2601
+ curses: reg.objects.curses,
2602
+ /* player_has_temporary_brand / _slay (player-util.c), bound ONCE here from the
2603
+ * pack's timed records. The melee hooks used to build a private copy and
2604
+ * nothing else could reach one, so obj-info's brand/slay gathering carried a
2605
+ * "DEFERRED" note beside a predicate that already existed - PORT_TODO 3.20. */
2606
+ tempBrandSlay: buildTempBrandSlay(birth.player,
2607
+ /* The RAW pack records, not players.timed - bindPlayer's TimedEffect has no
2608
+ * brand/slay code arrays, so passing the bound table silently mapped every
2609
+ * index to -1 and hasBrand answered false for everything. Measured: the
2610
+ * first draft of this did exactly that and the test caught it. */
2611
+ pack.player.timed, reg.objects.brands, reg.objects.slays),
2214
2612
  /* Placeholder; wireGame installs the full registry-backed env. */
2215
2613
  runeEnv: makeRuneEnv(() => null, () => false),
2216
2614
  playing: true,
@@ -2223,7 +2621,7 @@ export function startGame(pack, opts = {}) {
2223
2621
  // seed_flavor already drawn above; flavor_init runs inside wireGame.
2224
2622
  if (opts.updateFov)
2225
2623
  state.updateFov = opts.updateFov;
2226
- const wired = wireGame(state, reg, players, pstate, seedFlavor, pack);
2624
+ const wired = wireGame(state, reg, players, pstate, seedFlavor);
2227
2625
  /* PU_INVEN from the starting kit's inven_carry (player-birth.c). */
2228
2626
  buildGearViews(state, reg, pstate.ammoTval);
2229
2627
  // kind->everseen = true for each bought start item (player-birth.c L658). At
@@ -2240,6 +2638,13 @@ export function startGame(pack, opts = {}) {
2240
2638
  for (const kind of startKinds) {
2241
2639
  wired.flavor.objectFlavorAware(kind, NOOP_FLAVOR_AWARE_DEPS);
2242
2640
  }
2641
+ // obj->known->effect = obj->effect per start item (player-birth.c:650). The
2642
+ // objectFlavorAware pass above already covers every kind the outfit can
2643
+ // contain, so this is the C's line rather than a behaviour change - but it is
2644
+ // the line, and the field now exists to hold it.
2645
+ for (const obj of state.gear.store.values()) {
2646
+ obj.knownEffect = obj.effect;
2647
+ }
2243
2648
  // birth_know_runes (player-birth.c L1261-1262): a birth_know_runes character
2244
2649
  // knows every rune for ID-on-walkover (gap 1.5). No RNG. Before
2245
2650
  // player_learn_innate, matching the C acceptance order.
@@ -2301,6 +2706,10 @@ export function startGame(pack, opts = {}) {
2301
2706
  state.chunk.onlyPartial = true;
2302
2707
  if (state.updateFov) {
2303
2708
  state.updateFov(state);
2709
+ /* on_new_level's own disturb (game-world.c:1016-1017), immediately before
2710
+ * the feeling and the search: arriving on a level cancels whatever was
2711
+ * still queued from the level you left. */
2712
+ disturb(state);
2304
2713
  announceFeeling(state, reg);
2305
2714
  search(state); /* on_new_level (game-world.c:1052). */
2306
2715
  state.chunk.onlyPartial = false;
@@ -2348,6 +2757,10 @@ function makeStoreApi(state, reg, flavor, options) {
2348
2757
  },
2349
2758
  maxDepth: state.actor.player.maxDepth,
2350
2759
  stores: state.stores ?? [],
2760
+ /* The sell path decides through storeWillBuy too, so it reads the same
2761
+ * registry the maintenance path does - a seam supplied to every path but
2762
+ * one is the shape that lets a mod work in town and not in the shop. */
2763
+ ...(state.storeBehaviour ? { behaviour: state.storeBehaviour } : {}),
2351
2764
  });
2352
2765
  const noSelling = () => options.get("birth_no_selling") ?? false;
2353
2766
  /* update_stuff's PU_INVEN after a gear change (obj-gear.c: inven_carry /
@@ -2369,11 +2782,25 @@ function makeStoreApi(state, reg, flavor, options) {
2369
2782
  msg: (text) => state.msg?.(text),
2370
2783
  });
2371
2784
  };
2785
+ /**
2786
+ * object_flag_is_known(player, obj, flag) bound to one object, for
2787
+ * store_will_buy's buy-list branch (store.c:551). PORT_TODO 2.10 / 5.8.
2788
+ *
2789
+ * The shadow is synthesised ONCE per object rather than per flag: a buy list
2790
+ * can name several flags for one tval, and objectKnownShadow walks the whole
2791
+ * rune set each time it is called.
2792
+ */
2793
+ const flagKnownFor = (obj) => {
2794
+ const p = state.actor.player;
2795
+ const shadow = objectKnownShadow(obj, p, state.runeEnv, knownDescOf(state));
2796
+ return (flag) => objectFlagIsKnown(obj, shadow, p, state.runeEnv, flag);
2797
+ };
2372
2798
  const txnKnow = (obj) => ({
2373
2799
  flavor,
2374
2800
  flavorDeps: flavorAwareDeps(state),
2375
2801
  aware: flavor.isAware(obj.kind),
2376
2802
  noSelling: noSelling(),
2803
+ flagKnown: flagKnownFor(obj),
2377
2804
  /* do_cmd_sell L1946-1951: selling teaches the runes as well as the flavour.
2378
2805
  * buildRuneList is rebuilt per call for the same reason effect-item.ts does
2379
2806
  * it (L930): a mod can add runes mid-session, so a cached list would go
@@ -2385,7 +2812,7 @@ function makeStoreApi(state, reg, flavor, options) {
2385
2812
  /* do_cmd_retrieve (store.c:1783): Home Take is free - no price, no
2386
2813
  * ORIGIN_STORE, no shuffle/maint RNG. Reuse the existing homeRetrieve. */
2387
2814
  if (store.feat === FEAT.HOME) {
2388
- const r = homeRetrieve(store, obj, amt, state.gear, reg.constants);
2815
+ const r = homeRetrieve(store, obj, amt, state.actor.player, state.gear, reg.constants);
2389
2816
  if (!r.ok) {
2390
2817
  return {
2391
2818
  ok: false,
@@ -2425,7 +2852,15 @@ function makeStoreApi(state, reg, flavor, options) {
2425
2852
  const obj = state.gear.store.get(handle);
2426
2853
  const know = obj
2427
2854
  ? txnKnow(obj)
2428
- : { flavor, flavorDeps: flavorAwareDeps(state), aware: false, noSelling: noSelling() };
2855
+ : {
2856
+ flavor,
2857
+ flavorDeps: flavorAwareDeps(state),
2858
+ aware: false,
2859
+ noSelling: noSelling(),
2860
+ /* No object behind the handle, so the sale is refused before the
2861
+ * buy list is consulted; nothing can be known about a flag on it. */
2862
+ flagKnown: () => false,
2863
+ };
2429
2864
  const result = storeSell(storeCtx(), store, handle, amt, state.actor.player, state.gear, know);
2430
2865
  /* do_cmd_sell: selling an artifact reveals it (history_find_artifact,
2431
2866
  * store.c L1928); if the store then discards it, it is lost (L1992). */
@@ -2481,7 +2916,7 @@ function makeStoreApi(state, reg, flavor, options) {
2481
2916
  price: (store, obj, storeBuying, qty) => priceItem(reg.objects, store, store.owner, obj, storeBuying, qty, flavor.isAware(obj.kind), noSelling()),
2482
2917
  /* runesKnown matches storeSell's know (txnKnow never sets it, so the
2483
2918
  * transaction treats it as false); keep the filter aligned with the sale. */
2484
- willBuy: (store, obj) => storeWillBuy(reg.objects, store, obj, flavor.isAware(obj.kind), noSelling(), false),
2919
+ willBuy: (store, obj) => storeWillBuy(reg.objects, store, obj, flavor.isAware(obj.kind), noSelling(), false, flagKnownFor(obj), state.storeBehaviour),
2485
2920
  };
2486
2921
  }
2487
2922
  /**
@@ -2533,7 +2968,7 @@ function swapRandartSet(reg, seed, timedFoil, activationSummarize) {
2533
2968
  * (gap 3.8, effect_summarize_properties) is threaded via activationSummarize
2534
2969
  * so remove_contradictory_activation can drop a redundant activation
2535
2970
  * (obj-randart.c L2420); absent, it stays a conservative no-op. */
2536
- const randarts = doRandart(reg.objects, seed, reg.nameSections.get(RANDNAME_TOLKIEN), timedFoil || activationSummarize
2971
+ const randarts = doRandart(reg.objects, reg.constants, seed, true, reg.nameSections.get(RANDNAME_TOLKIEN), timedFoil || activationSummarize
2537
2972
  ? { timedFoil, activationSummarize }
2538
2973
  : undefined);
2539
2974
  reg.objects.artifacts.length = 0;
@@ -2635,8 +3070,16 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2635
3070
  if (!save.chunk && save.dungeonDepth !== undefined) {
2636
3071
  chunk.depth = save.dungeonDepth;
2637
3072
  }
2638
- const player = deserializePlayer(save.player, players, reg.objects, ids);
3073
+ const player = deserializePlayer(save.player, players, reg.objects, ids, reg.quests);
2639
3074
  const gear = deserializeGear(save.gear, reg.objects, ids);
3075
+ /* rd_gear (load.c L1179-1185): the carried-weight total is RE-SUMMED from the
3076
+ * restored gear, not trusted from the file. Upstream does this because its
3077
+ * gear list and the total are written separately; the port needs it for a
3078
+ * second reason - a character saved before this accounting existed carries a
3079
+ * stored total of 0, and reading that back would leave them weightless for
3080
+ * the rest of the game. It has to run before calcBonuses below, which reads
3081
+ * the total for the carrying-capacity speed penalty. */
3082
+ player.upkeep.totalWeight = gearTotalWeight(gear);
2640
3083
  const equipment = player.equipment.map((h) => (h ? gearGet(gear, h) : null));
2641
3084
  const weaponSlot = player.body.slots.findIndex((s) => s.type === "WEAPON");
2642
3085
  const weapon = weaponSlot >= 0 ? (equipment[weaponSlot] ?? null) : null;
@@ -2662,6 +3105,10 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2662
3105
  speed: pstate.speed,
2663
3106
  totalEnergy: save.actor.totalEnergy,
2664
3107
  combat,
3108
+ /* p->known_state, seeded with the real state and replaced with the true
3109
+ * known derive at the end of wireGame - the flavour store and the rune env
3110
+ * it needs do not exist yet at this line. */
3111
+ knownCombat: combat,
2665
3112
  defense: toDefenderState(pstate),
2666
3113
  weapon,
2667
3114
  stealth: combat.skills[SKILL.STEALTH] ?? 0,
@@ -2675,6 +3122,11 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2675
3122
  decoy = grid;
2676
3123
  })
2677
3124
  : new Map();
3125
+ /* Hoisted: the remembered pile is saved as locators into these very piles,
3126
+ * so deserializeKnown must be handed the same map the state runs on. */
3127
+ const loadedFloor = !save.isDead && save.floor
3128
+ ? deserializeFloor(save.floor, reg.objects, chunk.width, ids)
3129
+ : new Map();
2678
3130
  const state = {
2679
3131
  rng,
2680
3132
  chunk,
@@ -2683,13 +3135,11 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2683
3135
  decoy,
2684
3136
  monsters: (save.isDead ? [null] : (save.monsters ?? [null])).map((m) => m ? deserializeMonster(m, reg.monsters, reg.objects, ids) : null),
2685
3137
  groups: (save.isDead ? [null] : (save.groups ?? [null])).map((g) => g ? { index: g.index, leader: g.leader, members: [...g.members] } : null),
2686
- floor: !save.isDead && save.floor
2687
- ? deserializeFloor(save.floor, reg.objects, chunk.width, ids)
2688
- : new Map(),
3138
+ floor: loadedFloor,
2689
3139
  traps: loadedTraps,
2690
3140
  known: save.isDead
2691
3141
  ? newKnownMap(chunk.width, chunk.height)
2692
- : deserializeKnown(save.known, chunk.width, chunk.height, featRemap),
3142
+ : deserializeKnown(save.known, chunk.width, chunk.height, featRemap, ids, loadedFloor, reg.objects),
2693
3143
  /* birth_levels_persist (#30) frozen-level cache; empty in saves written
2694
3144
  * before the field or with the option off (back-compat). */
2695
3145
  levelCache: deserializeLevelCache(save.isDead ? undefined : save.levelCache, reg.features, reg.monsters, reg.objects, reg.traps, ids),
@@ -2770,6 +3220,17 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2770
3220
  },
2771
3221
  brands: reg.objects.brands,
2772
3222
  slays: reg.objects.slays,
3223
+ curses: reg.objects.curses,
3224
+ /* player_has_temporary_brand / _slay (player-util.c), bound ONCE here from the
3225
+ * pack's timed records. The melee hooks used to build a private copy and
3226
+ * nothing else could reach one, so obj-info's brand/slay gathering carried a
3227
+ * "DEFERRED" note beside a predicate that already existed - PORT_TODO 3.20. */
3228
+ tempBrandSlay: buildTempBrandSlay(player,
3229
+ /* The RAW pack records, not players.timed - bindPlayer's TimedEffect has no
3230
+ * brand/slay code arrays, so passing the bound table silently mapped every
3231
+ * index to -1 and hasBrand answered false for everything. Measured: the
3232
+ * first draft of this did exactly that and the test caught it. */
3233
+ pack.player.timed, reg.objects.brands, reg.objects.slays),
2773
3234
  /* Placeholder; wireGame installs the full registry-backed env. */
2774
3235
  runeEnv: makeRuneEnv(() => null, () => false),
2775
3236
  playing: save.playing,
@@ -2805,16 +3266,27 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2805
3266
  player.noscore = markNoscore(player.noscore, NOSCORE.WIZARD);
2806
3267
  state.isDead = false;
2807
3268
  }
2808
- /* A save taken in single combat resumes it (the stashed pre-arena
2809
- * level is gone: winning exits to a fresh level of the same depth). */
3269
+ /* A save taken in single combat resumes it, INCLUDING the level it was
3270
+ * entered from - upstream keeps that in the chunk_list and the savefile
3271
+ * carries it. Rebuilt through the frozen-level cache deserializer, which is
3272
+ * the same code path the live level and the persistent cache use. A save
3273
+ * written before the stash was persisted has no `stash` key and reloads with
3274
+ * the old behaviour: winning exits to a fresh level of the same depth. */
2810
3275
  if (save.arena) {
2811
3276
  state.arenaLevel = true;
2812
3277
  state.oldGrid = loc(save.arena.oldGrid.x, save.arena.oldGrid.y);
3278
+ if (save.arena.stash) {
3279
+ const rebuilt = deserializeLevelCache([save.arena.stash], reg.features, reg.monsters, reg.objects, reg.traps, ids);
3280
+ const level = rebuilt.get(save.arena.stash.depth);
3281
+ if (level) {
3282
+ state.arenaStash = { ...level, monMidx: save.arena.monMidx ?? 0 };
3283
+ }
3284
+ }
2813
3285
  }
2814
3286
  /* seed_flavor from the save (load.c L960). Older saves predate it; fall
2815
3287
  * back to 0 so flavor_init still produces a stable per-load assignment. */
2816
3288
  const seedFlavor = save.seedFlavor ?? 0;
2817
- const wired = wireGame(state, reg, players, pstate, seedFlavor, pack);
3289
+ const wired = wireGame(state, reg, players, pstate, seedFlavor);
2818
3290
  /* rd_gear's tail (load.c:1187). */
2819
3291
  buildGearViews(state, reg, pstate.ammoTval);
2820
3292
  /* restore() replaces the aware/tried sets, so it must run AFTER flavor_init's
@@ -2851,6 +3323,10 @@ export function loadGame(pack, saveDocument, present = new Set(["core"]), opts =
2851
3323
  const booted = {
2852
3324
  chunk,
2853
3325
  depth: chunk.depth,
3326
+ /* The restored level's connectors live on state.currentJoins, which the
3327
+ * savefile already carries; this view describes a level nothing
3328
+ * generated, so it reports none. */
3329
+ joins: [],
2854
3330
  playerSpot: actor.grid,
2855
3331
  monsters: [],
2856
3332
  objects: [],