@alpaca-software/40kdc-data 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/dist/abilities-resolver/index.d.ts +9 -0
  2. package/dist/abilities-resolver/index.d.ts.map +1 -0
  3. package/dist/abilities-resolver/index.js +9 -0
  4. package/dist/abilities-resolver/index.js.map +1 -0
  5. package/dist/abilities-resolver/resolver.d.ts +64 -0
  6. package/dist/abilities-resolver/resolver.d.ts.map +1 -0
  7. package/dist/abilities-resolver/resolver.js +135 -0
  8. package/dist/abilities-resolver/resolver.js.map +1 -0
  9. package/dist/bundle-schemas.d.ts +1 -0
  10. package/dist/bundle-schemas.d.ts.map +1 -0
  11. package/dist/bundle-schemas.js +12 -0
  12. package/dist/bundle-schemas.js.map +1 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +10 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/codegen-data.d.ts +1 -0
  18. package/dist/codegen-data.d.ts.map +1 -0
  19. package/dist/codegen-data.js +2 -0
  20. package/dist/codegen-data.js.map +1 -0
  21. package/dist/commands/import.d.ts +7 -0
  22. package/dist/commands/import.d.ts.map +1 -0
  23. package/dist/commands/import.js +103 -0
  24. package/dist/commands/import.js.map +1 -0
  25. package/dist/commands/translate.d.ts +1 -0
  26. package/dist/commands/translate.d.ts.map +1 -0
  27. package/dist/commands/translate.js +1 -0
  28. package/dist/commands/translate.js.map +1 -0
  29. package/dist/commands/validate-all.d.ts +1 -0
  30. package/dist/commands/validate-all.d.ts.map +1 -0
  31. package/dist/commands/validate-all.js +1 -0
  32. package/dist/commands/validate-all.js.map +1 -0
  33. package/dist/commands/validate-core.d.ts +1 -0
  34. package/dist/commands/validate-core.d.ts.map +1 -0
  35. package/dist/commands/validate-core.js +1 -0
  36. package/dist/commands/validate-core.js.map +1 -0
  37. package/dist/commands/validate-enrichment.d.ts +1 -0
  38. package/dist/commands/validate-enrichment.d.ts.map +1 -0
  39. package/dist/commands/validate-enrichment.js +1 -0
  40. package/dist/commands/validate-enrichment.js.map +1 -0
  41. package/dist/convert-faction.d.ts +1 -0
  42. package/dist/convert-faction.d.ts.map +1 -0
  43. package/dist/convert-faction.js +1 -0
  44. package/dist/convert-faction.js.map +1 -0
  45. package/dist/converters/configs/adepta-sororitas.d.ts +1 -0
  46. package/dist/converters/configs/adepta-sororitas.d.ts.map +1 -0
  47. package/dist/converters/configs/adepta-sororitas.js +1 -0
  48. package/dist/converters/configs/adepta-sororitas.js.map +1 -0
  49. package/dist/converters/configs/adeptus-astartes.d.ts +1 -0
  50. package/dist/converters/configs/adeptus-astartes.d.ts.map +1 -0
  51. package/dist/converters/configs/adeptus-astartes.js +1 -0
  52. package/dist/converters/configs/adeptus-astartes.js.map +1 -0
  53. package/dist/converters/configs/adeptus-custodes.d.ts +1 -0
  54. package/dist/converters/configs/adeptus-custodes.d.ts.map +1 -0
  55. package/dist/converters/configs/adeptus-custodes.js +1 -0
  56. package/dist/converters/configs/adeptus-custodes.js.map +1 -0
  57. package/dist/converters/configs/adeptus-mechanicus.d.ts +1 -0
  58. package/dist/converters/configs/adeptus-mechanicus.d.ts.map +1 -0
  59. package/dist/converters/configs/adeptus-mechanicus.js +1 -0
  60. package/dist/converters/configs/adeptus-mechanicus.js.map +1 -0
  61. package/dist/converters/configs/aeldari.d.ts +1 -0
  62. package/dist/converters/configs/aeldari.d.ts.map +1 -0
  63. package/dist/converters/configs/aeldari.js +1 -0
  64. package/dist/converters/configs/aeldari.js.map +1 -0
  65. package/dist/converters/configs/agents-of-the-imperium.d.ts +1 -0
  66. package/dist/converters/configs/agents-of-the-imperium.d.ts.map +1 -0
  67. package/dist/converters/configs/agents-of-the-imperium.js +1 -0
  68. package/dist/converters/configs/agents-of-the-imperium.js.map +1 -0
  69. package/dist/converters/configs/astra-militarum.d.ts +1 -0
  70. package/dist/converters/configs/astra-militarum.d.ts.map +1 -0
  71. package/dist/converters/configs/astra-militarum.js +1 -0
  72. package/dist/converters/configs/astra-militarum.js.map +1 -0
  73. package/dist/converters/configs/black-templars.d.ts +1 -0
  74. package/dist/converters/configs/black-templars.d.ts.map +1 -0
  75. package/dist/converters/configs/black-templars.js +1 -0
  76. package/dist/converters/configs/black-templars.js.map +1 -0
  77. package/dist/converters/configs/blood-angels.d.ts +1 -0
  78. package/dist/converters/configs/blood-angels.d.ts.map +1 -0
  79. package/dist/converters/configs/blood-angels.js +1 -0
  80. package/dist/converters/configs/blood-angels.js.map +1 -0
  81. package/dist/converters/configs/chaos-daemons.d.ts +1 -0
  82. package/dist/converters/configs/chaos-daemons.d.ts.map +1 -0
  83. package/dist/converters/configs/chaos-daemons.js +1 -0
  84. package/dist/converters/configs/chaos-daemons.js.map +1 -0
  85. package/dist/converters/configs/chaos-knights.d.ts +1 -0
  86. package/dist/converters/configs/chaos-knights.d.ts.map +1 -0
  87. package/dist/converters/configs/chaos-knights.js +1 -0
  88. package/dist/converters/configs/chaos-knights.js.map +1 -0
  89. package/dist/converters/configs/chaos-space-marines.d.ts +1 -0
  90. package/dist/converters/configs/chaos-space-marines.d.ts.map +1 -0
  91. package/dist/converters/configs/chaos-space-marines.js +1 -0
  92. package/dist/converters/configs/chaos-space-marines.js.map +1 -0
  93. package/dist/converters/configs/crimson-fists.d.ts +1 -0
  94. package/dist/converters/configs/crimson-fists.d.ts.map +1 -0
  95. package/dist/converters/configs/crimson-fists.js +1 -0
  96. package/dist/converters/configs/crimson-fists.js.map +1 -0
  97. package/dist/converters/configs/dark-angels.d.ts +1 -0
  98. package/dist/converters/configs/dark-angels.d.ts.map +1 -0
  99. package/dist/converters/configs/dark-angels.js +1 -0
  100. package/dist/converters/configs/dark-angels.js.map +1 -0
  101. package/dist/converters/configs/death-guard.d.ts +1 -0
  102. package/dist/converters/configs/death-guard.d.ts.map +1 -0
  103. package/dist/converters/configs/death-guard.js +1 -0
  104. package/dist/converters/configs/death-guard.js.map +1 -0
  105. package/dist/converters/configs/deathwatch.d.ts +1 -0
  106. package/dist/converters/configs/deathwatch.d.ts.map +1 -0
  107. package/dist/converters/configs/deathwatch.js +1 -0
  108. package/dist/converters/configs/deathwatch.js.map +1 -0
  109. package/dist/converters/configs/drukhari.d.ts +1 -0
  110. package/dist/converters/configs/drukhari.d.ts.map +1 -0
  111. package/dist/converters/configs/drukhari.js +1 -0
  112. package/dist/converters/configs/drukhari.js.map +1 -0
  113. package/dist/converters/configs/emperors-children.d.ts +1 -0
  114. package/dist/converters/configs/emperors-children.d.ts.map +1 -0
  115. package/dist/converters/configs/emperors-children.js +1 -0
  116. package/dist/converters/configs/emperors-children.js.map +1 -0
  117. package/dist/converters/configs/genestealer-cults.d.ts +1 -0
  118. package/dist/converters/configs/genestealer-cults.d.ts.map +1 -0
  119. package/dist/converters/configs/genestealer-cults.js +1 -0
  120. package/dist/converters/configs/genestealer-cults.js.map +1 -0
  121. package/dist/converters/configs/grey-knights.d.ts +1 -0
  122. package/dist/converters/configs/grey-knights.d.ts.map +1 -0
  123. package/dist/converters/configs/grey-knights.js +1 -0
  124. package/dist/converters/configs/grey-knights.js.map +1 -0
  125. package/dist/converters/configs/imperial-fists.d.ts +1 -0
  126. package/dist/converters/configs/imperial-fists.d.ts.map +1 -0
  127. package/dist/converters/configs/imperial-fists.js +1 -0
  128. package/dist/converters/configs/imperial-fists.js.map +1 -0
  129. package/dist/converters/configs/imperial-knights.d.ts +1 -0
  130. package/dist/converters/configs/imperial-knights.d.ts.map +1 -0
  131. package/dist/converters/configs/imperial-knights.js +1 -0
  132. package/dist/converters/configs/imperial-knights.js.map +1 -0
  133. package/dist/converters/configs/iron-hands.d.ts +1 -0
  134. package/dist/converters/configs/iron-hands.d.ts.map +1 -0
  135. package/dist/converters/configs/iron-hands.js +1 -0
  136. package/dist/converters/configs/iron-hands.js.map +1 -0
  137. package/dist/converters/configs/leagues-of-votann.d.ts +1 -0
  138. package/dist/converters/configs/leagues-of-votann.d.ts.map +1 -0
  139. package/dist/converters/configs/leagues-of-votann.js +1 -0
  140. package/dist/converters/configs/leagues-of-votann.js.map +1 -0
  141. package/dist/converters/configs/necrons.d.ts +1 -0
  142. package/dist/converters/configs/necrons.d.ts.map +1 -0
  143. package/dist/converters/configs/necrons.js +1 -0
  144. package/dist/converters/configs/necrons.js.map +1 -0
  145. package/dist/converters/configs/orks.d.ts +1 -0
  146. package/dist/converters/configs/orks.d.ts.map +1 -0
  147. package/dist/converters/configs/orks.js +1 -0
  148. package/dist/converters/configs/orks.js.map +1 -0
  149. package/dist/converters/configs/raven-guard.d.ts +1 -0
  150. package/dist/converters/configs/raven-guard.d.ts.map +1 -0
  151. package/dist/converters/configs/raven-guard.js +1 -0
  152. package/dist/converters/configs/raven-guard.js.map +1 -0
  153. package/dist/converters/configs/salamanders.d.ts +1 -0
  154. package/dist/converters/configs/salamanders.d.ts.map +1 -0
  155. package/dist/converters/configs/salamanders.js +1 -0
  156. package/dist/converters/configs/salamanders.js.map +1 -0
  157. package/dist/converters/configs/space-wolves.d.ts +1 -0
  158. package/dist/converters/configs/space-wolves.d.ts.map +1 -0
  159. package/dist/converters/configs/space-wolves.js +1 -0
  160. package/dist/converters/configs/space-wolves.js.map +1 -0
  161. package/dist/converters/configs/tau-empire.d.ts +1 -0
  162. package/dist/converters/configs/tau-empire.d.ts.map +1 -0
  163. package/dist/converters/configs/tau-empire.js +1 -0
  164. package/dist/converters/configs/tau-empire.js.map +1 -0
  165. package/dist/converters/configs/thousand-sons.d.ts +1 -0
  166. package/dist/converters/configs/thousand-sons.d.ts.map +1 -0
  167. package/dist/converters/configs/thousand-sons.js +1 -0
  168. package/dist/converters/configs/thousand-sons.js.map +1 -0
  169. package/dist/converters/configs/tyranids.d.ts +1 -0
  170. package/dist/converters/configs/tyranids.d.ts.map +1 -0
  171. package/dist/converters/configs/tyranids.js +1 -0
  172. package/dist/converters/configs/tyranids.js.map +1 -0
  173. package/dist/converters/configs/ultramarines.d.ts +1 -0
  174. package/dist/converters/configs/ultramarines.d.ts.map +1 -0
  175. package/dist/converters/configs/ultramarines.js +1 -0
  176. package/dist/converters/configs/ultramarines.js.map +1 -0
  177. package/dist/converters/configs/white-scars.d.ts +1 -0
  178. package/dist/converters/configs/white-scars.d.ts.map +1 -0
  179. package/dist/converters/configs/white-scars.js +1 -0
  180. package/dist/converters/configs/white-scars.js.map +1 -0
  181. package/dist/converters/configs/world-eaters.d.ts +1 -0
  182. package/dist/converters/configs/world-eaters.d.ts.map +1 -0
  183. package/dist/converters/configs/world-eaters.js +1 -0
  184. package/dist/converters/configs/world-eaters.js.map +1 -0
  185. package/dist/converters/faction-config.d.ts +1 -0
  186. package/dist/converters/faction-config.d.ts.map +1 -0
  187. package/dist/converters/faction-config.js +1 -0
  188. package/dist/converters/faction-config.js.map +1 -0
  189. package/dist/converters/id-generator.d.ts +1 -0
  190. package/dist/converters/id-generator.d.ts.map +1 -0
  191. package/dist/converters/id-generator.js +1 -0
  192. package/dist/converters/id-generator.js.map +1 -0
  193. package/dist/converters/keyword-filter.d.ts +1 -0
  194. package/dist/converters/keyword-filter.d.ts.map +1 -0
  195. package/dist/converters/keyword-filter.js +1 -0
  196. package/dist/converters/keyword-filter.js.map +1 -0
  197. package/dist/converters/stat-parser.d.ts +1 -0
  198. package/dist/converters/stat-parser.d.ts.map +1 -0
  199. package/dist/converters/stat-parser.js +1 -0
  200. package/dist/converters/stat-parser.js.map +1 -0
  201. package/dist/converters/view-selector.d.ts +1 -0
  202. package/dist/converters/view-selector.d.ts.map +1 -0
  203. package/dist/converters/view-selector.js +1 -0
  204. package/dist/converters/view-selector.js.map +1 -0
  205. package/dist/converters/weapon-dedup.d.ts +1 -0
  206. package/dist/converters/weapon-dedup.d.ts.map +1 -0
  207. package/dist/converters/weapon-dedup.js +1 -0
  208. package/dist/converters/weapon-dedup.js.map +1 -0
  209. package/dist/cruncher/buffs.d.ts +184 -0
  210. package/dist/cruncher/buffs.d.ts.map +1 -0
  211. package/dist/cruncher/buffs.js +150 -0
  212. package/dist/cruncher/buffs.js.map +1 -0
  213. package/dist/cruncher/engine.d.ts +50 -0
  214. package/dist/cruncher/engine.d.ts.map +1 -0
  215. package/dist/cruncher/engine.js +312 -0
  216. package/dist/cruncher/engine.js.map +1 -0
  217. package/dist/cruncher/from-dsl.d.ts +69 -0
  218. package/dist/cruncher/from-dsl.d.ts.map +1 -0
  219. package/dist/cruncher/from-dsl.js +523 -0
  220. package/dist/cruncher/from-dsl.js.map +1 -0
  221. package/dist/cruncher/from-keyword.d.ts +35 -0
  222. package/dist/cruncher/from-keyword.d.ts.map +1 -0
  223. package/dist/cruncher/from-keyword.js +159 -0
  224. package/dist/cruncher/from-keyword.js.map +1 -0
  225. package/dist/cruncher/get-buffs.d.ts +12 -0
  226. package/dist/cruncher/get-buffs.d.ts.map +1 -0
  227. package/dist/cruncher/get-buffs.js +7 -0
  228. package/dist/cruncher/get-buffs.js.map +1 -0
  229. package/dist/cruncher/index.d.ts +11 -0
  230. package/dist/cruncher/index.d.ts.map +1 -0
  231. package/dist/cruncher/index.js +11 -0
  232. package/dist/cruncher/index.js.map +1 -0
  233. package/dist/data/bundle.generated.d.ts +1 -0
  234. package/dist/data/bundle.generated.d.ts.map +1 -0
  235. package/dist/data/bundle.generated.js +2 -1
  236. package/dist/data/bundle.generated.js.map +1 -0
  237. package/dist/data/collection.d.ts +1 -0
  238. package/dist/data/collection.d.ts.map +1 -0
  239. package/dist/data/collection.js +1 -0
  240. package/dist/data/collection.js.map +1 -0
  241. package/dist/data/dataset.d.ts +54 -2
  242. package/dist/data/dataset.d.ts.map +1 -0
  243. package/dist/data/dataset.js +111 -1
  244. package/dist/data/dataset.js.map +1 -0
  245. package/dist/data/entities.d.ts +70 -2
  246. package/dist/data/entities.d.ts.map +1 -0
  247. package/dist/data/entities.js +122 -0
  248. package/dist/data/entities.js.map +1 -0
  249. package/dist/data/index.d.ts +9 -1
  250. package/dist/data/index.d.ts.map +1 -0
  251. package/dist/data/index.js +14 -1
  252. package/dist/data/index.js.map +1 -0
  253. package/dist/data/normalize.d.ts +1 -0
  254. package/dist/data/normalize.d.ts.map +1 -0
  255. package/dist/data/normalize.js +1 -0
  256. package/dist/data/normalize.js.map +1 -0
  257. package/dist/data/roster-resolve.d.ts +33 -0
  258. package/dist/data/roster-resolve.d.ts.map +1 -0
  259. package/dist/data/roster-resolve.js +36 -0
  260. package/dist/data/roster-resolve.js.map +1 -0
  261. package/dist/data/types.d.ts +4 -1
  262. package/dist/data/types.d.ts.map +1 -0
  263. package/dist/data/types.js +2 -0
  264. package/dist/data/types.js.map +1 -0
  265. package/dist/export/helpers.d.ts +33 -0
  266. package/dist/export/helpers.d.ts.map +1 -0
  267. package/dist/export/helpers.js +57 -0
  268. package/dist/export/helpers.js.map +1 -0
  269. package/dist/export/index.d.ts +21 -0
  270. package/dist/export/index.d.ts.map +1 -0
  271. package/dist/export/index.js +25 -0
  272. package/dist/export/index.js.map +1 -0
  273. package/dist/export/newrecruit-json.d.ts +3 -0
  274. package/dist/export/newrecruit-json.d.ts.map +1 -0
  275. package/dist/export/newrecruit-json.js +140 -0
  276. package/dist/export/newrecruit-json.js.map +1 -0
  277. package/dist/export/newrecruit-simple.d.ts +3 -0
  278. package/dist/export/newrecruit-simple.d.ts.map +1 -0
  279. package/dist/export/newrecruit-simple.js +76 -0
  280. package/dist/export/newrecruit-simple.js.map +1 -0
  281. package/dist/export/newrecruit-wtc.d.ts +4 -0
  282. package/dist/export/newrecruit-wtc.d.ts.map +1 -0
  283. package/dist/export/newrecruit-wtc.js +142 -0
  284. package/dist/export/newrecruit-wtc.js.map +1 -0
  285. package/dist/export/roster-json.d.ts +3 -0
  286. package/dist/export/roster-json.d.ts.map +1 -0
  287. package/dist/export/roster-json.js +8 -0
  288. package/dist/export/roster-json.js.map +1 -0
  289. package/dist/export/serializer.d.ts +27 -0
  290. package/dist/export/serializer.d.ts.map +1 -0
  291. package/dist/export/serializer.js +2 -0
  292. package/dist/export/serializer.js.map +1 -0
  293. package/dist/gen-conformance.d.ts +2 -0
  294. package/dist/gen-conformance.d.ts.map +1 -0
  295. package/dist/gen-conformance.js +131 -0
  296. package/dist/gen-conformance.js.map +1 -0
  297. package/dist/generated.d.ts +194 -118
  298. package/dist/generated.d.ts.map +1 -0
  299. package/dist/generated.js +1 -0
  300. package/dist/generated.js.map +1 -0
  301. package/dist/import/adapter.d.ts +27 -0
  302. package/dist/import/adapter.d.ts.map +1 -0
  303. package/dist/import/adapter.js +10 -0
  304. package/dist/import/adapter.js.map +1 -0
  305. package/dist/import/decode.d.ts +7 -0
  306. package/dist/import/decode.d.ts.map +1 -0
  307. package/dist/import/decode.js +73 -0
  308. package/dist/import/decode.js.map +1 -0
  309. package/dist/import/import-roster.d.ts +35 -0
  310. package/dist/import/import-roster.d.ts.map +1 -0
  311. package/dist/import/import-roster.js +97 -0
  312. package/dist/import/import-roster.js.map +1 -0
  313. package/dist/import/index.d.ts +22 -0
  314. package/dist/import/index.d.ts.map +1 -0
  315. package/dist/import/index.js +19 -0
  316. package/dist/import/index.js.map +1 -0
  317. package/dist/import/listforge.d.ts +24 -0
  318. package/dist/import/listforge.d.ts.map +1 -0
  319. package/dist/import/listforge.js +201 -0
  320. package/dist/import/listforge.js.map +1 -0
  321. package/dist/import/newrecruit-json.d.ts +31 -0
  322. package/dist/import/newrecruit-json.d.ts.map +1 -0
  323. package/dist/import/newrecruit-json.js +224 -0
  324. package/dist/import/newrecruit-json.js.map +1 -0
  325. package/dist/import/newrecruit-simple.d.ts +29 -0
  326. package/dist/import/newrecruit-simple.d.ts.map +1 -0
  327. package/dist/import/newrecruit-simple.js +200 -0
  328. package/dist/import/newrecruit-simple.js.map +1 -0
  329. package/dist/import/newrecruit-text.d.ts +48 -0
  330. package/dist/import/newrecruit-text.d.ts.map +1 -0
  331. package/dist/import/newrecruit-text.js +96 -0
  332. package/dist/import/newrecruit-text.js.map +1 -0
  333. package/dist/import/newrecruit-wtc.d.ts +36 -0
  334. package/dist/import/newrecruit-wtc.d.ts.map +1 -0
  335. package/dist/import/newrecruit-wtc.js +334 -0
  336. package/dist/import/newrecruit-wtc.js.map +1 -0
  337. package/dist/import/resolve.d.ts +20 -0
  338. package/dist/import/resolve.d.ts.map +1 -0
  339. package/dist/import/resolve.js +190 -0
  340. package/dist/import/resolve.js.map +1 -0
  341. package/dist/import/types.d.ts +153 -0
  342. package/dist/import/types.d.ts.map +1 -0
  343. package/dist/import/types.js +20 -0
  344. package/dist/import/types.js.map +1 -0
  345. package/dist/index.d.ts +6 -0
  346. package/dist/index.d.ts.map +1 -0
  347. package/dist/index.js +7 -0
  348. package/dist/index.js.map +1 -0
  349. package/dist/known-support-10e.d.ts +1 -0
  350. package/dist/known-support-10e.d.ts.map +1 -0
  351. package/dist/known-support-10e.js +1 -0
  352. package/dist/known-support-10e.js.map +1 -0
  353. package/dist/link-abilities.d.ts +41 -0
  354. package/dist/link-abilities.d.ts.map +1 -0
  355. package/dist/link-abilities.js +159 -0
  356. package/dist/link-abilities.js.map +1 -0
  357. package/dist/migrations/2026-weapon-keywords.d.ts +2 -0
  358. package/dist/migrations/2026-weapon-keywords.d.ts.map +1 -0
  359. package/dist/migrations/2026-weapon-keywords.js +243 -0
  360. package/dist/migrations/2026-weapon-keywords.js.map +1 -0
  361. package/dist/port-10e-faction.d.ts +1 -0
  362. package/dist/port-10e-faction.d.ts.map +1 -0
  363. package/dist/port-10e-faction.js +1 -0
  364. package/dist/port-10e-faction.js.map +1 -0
  365. package/dist/report.d.ts +1 -0
  366. package/dist/report.d.ts.map +1 -0
  367. package/dist/report.js +1 -0
  368. package/dist/report.js.map +1 -0
  369. package/dist/rube-goldberg.d.ts +3 -0
  370. package/dist/rube-goldberg.d.ts.map +1 -0
  371. package/dist/rube-goldberg.js +109 -0
  372. package/dist/rube-goldberg.js.map +1 -0
  373. package/dist/schema-loader.d.ts +1 -0
  374. package/dist/schema-loader.d.ts.map +1 -0
  375. package/dist/schema-loader.js +1 -0
  376. package/dist/schema-loader.js.map +1 -0
  377. package/dist/validate.d.ts +1 -0
  378. package/dist/validate.d.ts.map +1 -0
  379. package/dist/validate.js +2 -0
  380. package/dist/validate.js.map +1 -0
  381. package/package.json +8 -2
  382. package/schemas/core/roster.schema.json +17 -4
  383. package/schemas/core/weapon-keyword.schema.json +31 -0
  384. package/schemas/core/weapon.schema.json +22 -1
  385. package/schemas/enrichment/ability-dsl/effect.schema.json +23 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-dsl.js","sourceRoot":"","sources":["../../src/cruncher/from-dsl.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translate an Ability DSL `effect` tree into the {@link Buff} stack it\n * contributes (for an attacker-perspective crunch) along with a list of\n * effect fragments the translator could not auto-apply.\n *\n * The buff layer is intentionally a subset of the DSL: it covers the math the\n * cruncher's expected-value engine reads (rerolls, die-roll modifiers, S/A/T\n * stat shifts, FNP, granted weapon keywords, cover) and reports everything\n * else — choice nodes (player decisions), dice-gated effects (stochastic),\n * defender-side bs-modifier, attack-restrictions, ability grants, mortal\n * wound triggers — as `unsupported` so the SPA can surface \"this ability has\n * effects we can't auto-apply\" rather than silently dropping them.\n *\n * The walker classifies an effect's `target` against the attacker\n * perspective: `self`, `bearer`, `unit`, `attached-unit`, `attacker`, and\n * `friendly-within-aura` are all treated as \"applies to my unit\". `defender`,\n * `enemy-within-aura`, and `all-enemy` are dropped without being marked\n * unsupported — those are defender-side mods and would surface from the\n * target's perspective (M3 work), not the attacker's.\n *\n * @packageDocumentation\n */\nimport type { Buff, BuffSource, EngineContext, WeaponKeywordRef } from \"./buffs.js\";\n\n/** A fragment we couldn't translate. The SPA can render these as warnings. */\nexport type UnsupportedFragment = {\n reason: string;\n effectFragment: unknown;\n};\n\nexport type EffectTranslation = {\n applied: Buff[];\n unsupported: UnsupportedFragment[];\n};\n\n/**\n * Whose perspective the translation runs from.\n *\n * - `\"attacker\"`: the buffed unit is *firing*. `target: \"unit\"/\"self\"` etc.\n * become attacker-side mods (re-rolls, hit/wound mods, A/S shifts, granted\n * keywords). `target: \"defender\"` is silently dropped — that's incoming\n * penalty math relevant when the buffed unit is the *target*, surfaced via\n * the `\"target\"` perspective instead.\n *\n * - `\"target\"`: the buffed unit is *being shot at*. Defensive mods on the\n * buffed unit (`stat-modifier T`, `stat-modifier Sv`, `feel-no-pain`,\n * `roll-modifier save`) become defender-side buffs. Conversely, attacker-\n * only mods (re-rolls, hit/wound mods, A/S shifts) drop silently because\n * they describe what the buffed unit does when *attacking*.\n *\n * The bs-modifier effect (a -1 to incoming hit rolls, e.g. Benefit of Cover)\n * becomes a `hit-mod` buff under target perspective so it stacks correctly\n * with attacker-side modifiers in the resolver's ±1 cap.\n */\nexport type TranslationPerspective = \"attacker\" | \"target\";\n\n/** Targets that resolve to the buffed unit itself. */\nconst SELF_TARGETS = new Set([\n \"self\",\n \"bearer\",\n \"unit\",\n \"attached-unit\",\n \"friendly-within-aura\",\n \"all-friendly\",\n]);\n\n/** Aliases the DSL uses when a node specifically calls out \"the attacker\". */\nconst ATTACKER_TARGET = \"attacker\";\n/** Aliases the DSL uses when a node specifically calls out \"the defender\". */\nconst DEFENDER_TARGETS = new Set([\"defender\", \"enemy-within-aura\", \"all-enemy\"]);\n\n/**\n * Walk an ability DSL `effect` tree and produce the buff stack it contributes\n * against `context` from the given `perspective`, plus an `unsupported` list\n * naming any branches the buff layer can't express today.\n */\nexport function effectToBuffs(\n effect: unknown,\n source: BuffSource,\n context: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n): EffectTranslation {\n const out: EffectTranslation = { applied: [], unsupported: [] };\n walk(effect, source, { context, perspective }, out);\n return out;\n}\n\ntype WalkOpts = { context: EngineContext; perspective: TranslationPerspective };\n\nfunction walk(\n node: unknown,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n if (!isObject(node)) return;\n const type = node.type;\n switch (type) {\n case \"re-roll\":\n translateReroll(node, source, opts, out);\n return;\n case \"roll-modifier\":\n translateRollModifier(node, source, opts, out);\n return;\n case \"stat-modifier\":\n translateStatModifier(node, source, opts, out);\n return;\n case \"feel-no-pain\":\n translateFeelNoPain(node, source, opts, out);\n return;\n case \"keyword-grant\":\n translateKeywordGrant(node, source, opts, out);\n return;\n case \"bs-modifier\":\n translateBsModifier(node, source, opts, out);\n return;\n case \"conditional\":\n translateConditional(node, source, opts, out);\n return;\n case \"sequence\":\n for (const step of (node.steps as unknown[]) ?? []) walk(step, source, opts, out);\n return;\n case \"choice\":\n // Player decision — auto-applying every branch would double-count.\n out.unsupported.push({\n reason: \"choice: player picks one option; the buff layer can't choose\",\n effectFragment: node,\n });\n return;\n case \"dice-gated\":\n // Probabilistic; the buff layer is deterministic.\n out.unsupported.push({\n reason: \"dice-gated effect: stochastic; not expressible as a buff\",\n effectFragment: node,\n });\n return;\n case \"dice-pool-allocation\":\n out.unsupported.push({\n reason: \"dice-pool-allocation: player allocates dice at runtime\",\n effectFragment: node,\n });\n return;\n default:\n // Unknown effect — record it. Covers ability-grant, deep-strike,\n // mortal-wounds, cp-gain, movement-modifier, etc.; the buff layer\n // doesn't model these as deterministic mods to a single shot.\n out.unsupported.push({\n reason: `effect type \"${String(type)}\" is not modelled by the buff layer`,\n effectFragment: node,\n });\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Leaf translators\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a node's `target` field against the perspective we're translating\n * for. Returns:\n * - `\"self\"`: the node targets the buffed unit (apply attacker-side or\n * defender-side translation, depending on perspective + stat).\n * - `\"attacker\"` / `\"defender\"`: the node targets the other party explicitly.\n * - `\"unknown\"`: missing/malformed target.\n */\nfunction classifyTarget(\n node: Record<string, unknown>,\n): \"self\" | \"attacker\" | \"defender\" | \"unknown\" {\n const target = node.target;\n if (typeof target !== \"string\") return \"unknown\";\n if (target === ATTACKER_TARGET) return \"attacker\";\n if (DEFENDER_TARGETS.has(target)) return \"defender\";\n if (SELF_TARGETS.has(target)) return \"self\";\n return \"unknown\";\n}\n\n/**\n * Does this node's target match the buffed unit under the current\n * perspective? Used for symmetric roll/keyword translations where the same\n * effect is \"self\" in either direction.\n */\nfunction appliesToBuffedUnit(\n node: Record<string, unknown>,\n perspective: TranslationPerspective,\n): boolean {\n const cls = classifyTarget(node);\n if (cls === \"self\") return true;\n if (cls === \"attacker\") return perspective === \"attacker\";\n if (cls === \"defender\") return perspective === \"target\";\n return false;\n}\n\nfunction translateReroll(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // Rerolls are inherently attacker-side (you re-roll your own hit/wound/\n // damage; save rerolls fire when *you* are the target). Apply only under\n // the matching perspective so a target-perspective walk doesn't grab the\n // attacker's reroll-failed-hits buff.\n if (opts.perspective === \"attacker\" && !appliesToBuffedUnit(node, \"attacker\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({ reason: \"re-roll: missing modifier object\", effectFragment: node });\n return;\n }\n const roll = modifier.roll;\n const subset = modifier.subset;\n // Under target perspective, only \"save\" rerolls fire on the buffed unit.\n if (opts.perspective === \"target\" && roll !== \"save\") return;\n if (\n (roll === \"hit\" || roll === \"wound\" || roll === \"save\" || roll === \"damage\") &&\n (subset === \"ones\" || subset === \"all-failures\")\n ) {\n out.applied.push({ source, contribution: { type: \"reroll\", roll, subset } });\n return;\n }\n out.unsupported.push({\n reason: `re-roll on \"${String(roll)}\" (subset \"${String(subset)}\") is outside the damage path`,\n effectFragment: node,\n });\n}\n\nfunction translateRollModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"roll-modifier: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const value = signedValue(modifier);\n if (value === null) {\n out.unsupported.push({\n reason: `roll-modifier: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n const roll = modifier.roll;\n // Each roll type is intrinsically on one side. Hit / wound / damage are\n // attacker-side; save is defender-side. The perspective decides whether the\n // buffed unit's `target` is the right party for that roll type.\n if (opts.perspective === \"attacker\") {\n if (!appliesToBuffedUnit(node, \"attacker\")) return;\n if (roll === \"save\") return; // saves apply to the defender, not the attacker.\n } else {\n // target perspective: only `save` rolls on the buffed unit fire here.\n if (roll !== \"save\") return;\n if (!appliesToBuffedUnit(node, \"target\")) return;\n }\n switch (roll) {\n case \"hit\":\n out.applied.push({ source, contribution: { type: \"hit-mod\", value } });\n return;\n case \"wound\":\n out.applied.push({ source, contribution: { type: \"wound-mod\", value } });\n return;\n case \"save\":\n out.applied.push({ source, contribution: { type: \"save-mod\", value } });\n return;\n case \"damage\":\n out.applied.push({ source, contribution: { type: \"damage-mod\", value } });\n return;\n default:\n out.unsupported.push({\n reason: `roll-modifier on \"${String(roll)}\" is outside the damage path`,\n effectFragment: node,\n });\n }\n}\n\nfunction translateStatModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"stat-modifier: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const value = signedValue(modifier);\n if (value === null) {\n out.unsupported.push({\n reason: `stat-modifier: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n const stat = modifier.stat;\n const isOnBuffedUnit = appliesToBuffedUnit(node, opts.perspective);\n switch (stat) {\n case \"A\":\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n out.applied.push({ source, contribution: { type: \"attacks-mod\", value } });\n return;\n case \"S\":\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n out.applied.push({ source, contribution: { type: \"strength-mod\", value } });\n return;\n case \"T\":\n // Defender stat. Only relevant under target perspective.\n if (opts.perspective !== \"target\") {\n out.unsupported.push({\n reason: \"stat-modifier T: defender-side stat; applies when the buffed unit is the target\",\n effectFragment: node,\n });\n return;\n }\n if (!isOnBuffedUnit) return;\n out.applied.push({ source, contribution: { type: \"toughness-mod\", value } });\n return;\n case \"Sv\":\n // Saves improve when the *defender* gets +Sv. A +1 to Sv in printed\n // rules means \"improve the save by 1\", which maps to a `save-mod` of\n // `-value` since save-mod is signed against the *needed roll*.\n // (Equivalent: a -1 Sv penalty is a +1 save-mod.) We translate\n // \"Sv add 1\" → save-mod -1 to keep the resolver's sign convention.\n if (opts.perspective !== \"target\") {\n out.unsupported.push({\n reason: \"stat-modifier Sv: defender-side stat; applies when the buffed unit is the target\",\n effectFragment: node,\n });\n return;\n }\n if (!isOnBuffedUnit) return;\n out.applied.push({ source, contribution: { type: \"save-mod\", value: -value } });\n return;\n case \"AP\":\n // AP rides on the attacker's weapon profile and is stored as a negative\n // number in the data (e.g. AP -1). The data's `{operation:\"add\", value:-1}`\n // form means \"AP becomes one more negative\" → more piercing. `signedValue`\n // already returns that negative number directly, so pass it through.\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n out.applied.push({ source, contribution: { type: \"ap-mod\", value } });\n return;\n default:\n out.unsupported.push({\n reason: `stat-modifier on \"${String(stat)}\" is outside the damage path`,\n effectFragment: node,\n });\n }\n}\n\nfunction translateFeelNoPain(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // FNP applies when the buffed unit is the *target* — it ablates incoming\n // damage. Under attacker perspective the FNP is irrelevant (the unit is\n // firing, not taking damage). Drop silently rather than as `unsupported`\n // so attacker-perspective walks don't surface a spurious diagnostic for\n // every unit that happens to have a FNP rule.\n if (opts.perspective !== \"target\") return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"feel-no-pain: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const threshold = Number(modifier.threshold);\n if (!Number.isFinite(threshold)) {\n out.unsupported.push({\n reason: \"feel-no-pain: threshold not numeric\",\n effectFragment: node,\n });\n return;\n }\n out.applied.push({ source, contribution: { type: \"feel-no-pain\", threshold } });\n}\n\nfunction translateKeywordGrant(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // Weapon-keyword grants ride with the attacker's profile (e.g. \"your\n // weapons gain [Sustained Hits 1]\"). Defender-perspective walks ignore\n // them — the keyword applies when the buffed unit fires, not when it's\n // shot at.\n if (opts.perspective !== \"attacker\") return;\n if (!appliesToBuffedUnit(node, \"attacker\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) return;\n const keywords = modifier.keywords;\n if (!Array.isArray(keywords)) return;\n for (const raw of keywords) {\n if (typeof raw !== \"string\") continue;\n const ref = parseKeywordGrant(raw);\n if (!ref) {\n out.unsupported.push({\n reason: `keyword-grant: cannot parse \"${raw}\" to a catalog keyword`,\n effectFragment: { keyword: raw },\n });\n continue;\n }\n out.applied.push({ source, contribution: { type: \"extra-keyword\", keywordRef: ref } });\n }\n}\n\nfunction translateBsModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // A bs-modifier on `target: \"attacker\"` is a defender-side rule: it\n // penalises *incoming* hit rolls (e.g. Benefit of Cover). Translate it\n // as a `hit-mod` buff under target perspective so the resolver's ±1 cap\n // composes with attacker-side mods.\n if (opts.perspective !== \"target\") return;\n const cls = classifyTarget(node);\n if (cls !== \"attacker\") return; // a bs-modifier on self wouldn't make sense.\n const modifier = node.modifier;\n if (!isObject(modifier)) return;\n const value = signedValue(modifier);\n if (value === null) return;\n out.applied.push({ source, contribution: { type: \"hit-mod\", value } });\n}\n\nfunction translateConditional(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const condition = node.condition;\n const effect = node.effect;\n if (!isObject(condition)) return;\n const negated = condition.negated === true;\n const verdict = evaluateCondition(condition, opts.context);\n if (verdict === \"unknown\") {\n out.unsupported.push({\n reason: `conditional: cannot evaluate condition \"${String(condition.type)}\" against current context`,\n effectFragment: node,\n });\n return;\n }\n const active = negated ? !verdict : verdict;\n if (!active) return;\n walk(effect, source, opts, out);\n}\n\n// ---------------------------------------------------------------------------\n// Condition evaluator\n// ---------------------------------------------------------------------------\n\nfunction evaluateCondition(\n condition: Record<string, unknown>,\n ctx: EngineContext,\n): boolean | \"unknown\" {\n // Compound conditions use {operator, operands} rather than {type, parameters}.\n // The schema's `condition-node` oneOf doesn't guarantee discrimination by a\n // single field, so dispatch on shape: presence of `operator` + `operands`\n // wins over the simple-condition switch below.\n if (\n typeof condition.operator === \"string\" &&\n Array.isArray(condition.operands)\n ) {\n return evaluateCompound(condition.operator, condition.operands, ctx);\n }\n switch (condition.type) {\n case \"phase-is\": {\n const wanted = (condition.parameters as Record<string, unknown> | undefined)?.phase;\n if (typeof wanted !== \"string\") return \"unknown\";\n return ctx.phase === wanted;\n }\n case \"timing-is\": {\n const wanted = (condition.parameters as Record<string, unknown> | undefined)?.timing;\n if (typeof wanted !== \"string\") return \"unknown\";\n if (ctx.timing === undefined) return \"unknown\";\n return ctx.timing === wanted;\n }\n case \"remained-stationary\":\n return ctx.attackerStationary === true;\n case \"target-has-keyword\": {\n const kw = (condition.parameters as Record<string, unknown> | undefined)?.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.targetKeywords ?? []).includes(kw.toLowerCase());\n }\n case \"unit-has-keyword\": {\n const kw = (condition.parameters as Record<string, unknown> | undefined)?.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.attackerKeywords ?? []).includes(kw.toLowerCase());\n }\n case \"is-attached\":\n // The resolver knows whether a leader is attached; absent that signal\n // here, treat as unknown so the SPA can surface the gap.\n return \"unknown\";\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Kleene three-valued evaluator for compound conditions. `and` short-circuits\n * to `false` as soon as any operand is false (an unknown operand is then\n * irrelevant); `or` short-circuits to `true` symmetrically. `not` flips its\n * single operand and leaves `\"unknown\"` as `\"unknown\"`. Unknown operands that\n * don't get short-circuited propagate as `\"unknown\"` so the SPA can surface\n * the gap rather than collapsing it into a misleading false.\n */\nfunction evaluateCompound(\n operator: string,\n operands: unknown[],\n ctx: EngineContext,\n): boolean | \"unknown\" {\n if (operator === \"not\") {\n const first = operands[0];\n if (!isObject(first)) return \"unknown\";\n const v = evaluateCondition(first, ctx);\n if (v === \"unknown\") return \"unknown\";\n return !v;\n }\n if (operator !== \"and\" && operator !== \"or\") return \"unknown\";\n let sawUnknown = false;\n for (const operand of operands) {\n if (!isObject(operand)) {\n sawUnknown = true;\n continue;\n }\n const v = evaluateCondition(operand, ctx);\n if (v === \"unknown\") {\n sawUnknown = true;\n continue;\n }\n if (operator === \"and\" && v === false) return false;\n if (operator === \"or\" && v === true) return true;\n }\n if (sawUnknown) return \"unknown\";\n return operator === \"and\"; // all true for AND, all false for OR\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read a signed numeric value out of a modifier `{operation, value}` pair.\n * \"add\"/\"subtract\" become the matching sign; \"set\" / \"multiply\" / etc. return\n * `null` (translator surfaces them as unsupported).\n */\nfunction signedValue(modifier: Record<string, unknown>): number | null {\n const value = Number(modifier.value);\n if (!Number.isFinite(value)) return null;\n switch (modifier.operation) {\n case \"add\":\n return value;\n case \"subtract\":\n return -value;\n default:\n return null;\n }\n}\n\n/**\n * Parse a printed weapon-keyword string (e.g. `\"Sustained Hits 1\"`,\n * `\"Anti-INFANTRY 4+\"`, `\"Lethal Hits\"`) into a `{keyword_id, parameters?}`\n * catalog reference, or `null` if the form is unrecognised.\n *\n * Reverses the conventions baked into the M0 catalog: kebab-case ids,\n * trailing number → `value`, embedded keyword + threshold → `target_keyword`\n * + `threshold`.\n */\nexport function parseKeywordGrant(raw: string): WeaponKeywordRef | null {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n\n // Anti-X N+ → { anti, target_keyword: X, threshold: N }\n const antiMatch = /^anti-([A-Z][A-Z\\s-]*)\\s+(\\d+)\\+?$/i.exec(trimmed);\n if (antiMatch) {\n return {\n keyword_id: \"anti\",\n parameters: { target_keyword: antiMatch[1].trim(), threshold: Number(antiMatch[2]) },\n };\n }\n\n // \"Lethal Hits\", \"Twin-linked\", \"Heavy\" → kebab-case lookup, no params.\n // \"Sustained Hits 1\", \"Rapid Fire 2\", \"Melta 2\" → kebab-case + value.\n const valueMatch = /^(.+?)\\s+(\\d+)$/.exec(trimmed);\n if (valueMatch) {\n return {\n keyword_id: toKebabCase(valueMatch[1]),\n parameters: { value: Number(valueMatch[2]) },\n };\n }\n return { keyword_id: toKebabCase(trimmed) };\n}\n\nfunction toKebabCase(s: string): string {\n return s\n .toLowerCase()\n .replace(/[\\s_]+/g, \"-\")\n .replace(/[^a-z0-9-]/g, \"\");\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Translate a weapon-keyword catalog entry into the Buff stack it contributes
3
+ * for a given reference-site parameter set and engine context.
4
+ *
5
+ * Two paths converge here:
6
+ *
7
+ * 1. **DSL walk**, for keywords whose catalog `effect` is non-null
8
+ * (`twin-linked`, `heavy`). The walker handles a deliberately small subset
9
+ * of nodes — `re-roll`, `roll-modifier`, `conditional`-on-the-conditions
10
+ * the engine knows about — and produces `Buff`s with
11
+ * `source.kind = "weapon-keyword"`.
12
+ *
13
+ * 2. **Id dispatch**, for the eight rules whose catalog `effect` is null
14
+ * because the DSL has no primitive for them yet — `lethal-hits`,
15
+ * `sustained-hits`, `devastating-wounds`, `anti`, `melta`, `rapid-fire`,
16
+ * `torrent`, `ignores-cover`. These are surfaced as `extra-keyword` buffs
17
+ * so the engine can read them out of `ResolvedModifiers.extraKeywords`
18
+ * and dispatch its math directly.
19
+ *
20
+ * Unrecognised nodes drop silently in M1 — diagnostic surfacing belongs to
21
+ * M2's broader ability translator.
22
+ */
23
+ import type { Buff, EngineContext } from "./buffs.js";
24
+ /**
25
+ * Convert a single weapon-keyword reference (catalog effect + reference-site
26
+ * parameters) into the buff contributions it makes against `context`.
27
+ */
28
+ export declare function buffsFromKeyword(args: {
29
+ keywordId: string;
30
+ weaponId: string;
31
+ effect: unknown;
32
+ parameters?: Record<string, unknown>;
33
+ context: EngineContext;
34
+ }): Buff[];
35
+ //# sourceMappingURL=from-keyword.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-keyword.d.ts","sourceRoot":"","sources":["../../src/cruncher/from-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAc,aAAa,EAAoB,MAAM,YAAY,CAAC;AAcpF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,EAAE,aAAa,CAAC;CACxB,GAAG,IAAI,EAAE,CAiBT"}
@@ -0,0 +1,159 @@
1
+ /** Keywords whose math the engine encodes directly (catalog `effect` is null). */
2
+ const ENGINE_DISPATCH_KEYWORDS = new Set([
3
+ "lethal-hits",
4
+ "sustained-hits",
5
+ "devastating-wounds",
6
+ "anti",
7
+ "melta",
8
+ "rapid-fire",
9
+ "torrent",
10
+ "ignores-cover",
11
+ ]);
12
+ /**
13
+ * Convert a single weapon-keyword reference (catalog effect + reference-site
14
+ * parameters) into the buff contributions it makes against `context`.
15
+ */
16
+ export function buffsFromKeyword(args) {
17
+ const source = {
18
+ kind: "weapon-keyword",
19
+ weaponId: args.weaponId,
20
+ keywordId: args.keywordId,
21
+ };
22
+ if (ENGINE_DISPATCH_KEYWORDS.has(args.keywordId)) {
23
+ const ref = {
24
+ keyword_id: args.keywordId,
25
+ ...(args.parameters !== undefined ? { parameters: args.parameters } : {}),
26
+ };
27
+ return [{ source, contribution: { type: "extra-keyword", keywordRef: ref } }];
28
+ }
29
+ if (args.effect === null || args.effect === undefined)
30
+ return [];
31
+ return walk(args.effect, source, args.context);
32
+ }
33
+ function walk(node, source, ctx) {
34
+ if (!isObject(node))
35
+ return [];
36
+ const type = node.type;
37
+ switch (type) {
38
+ case "re-roll":
39
+ return rerollBuffs(node, source);
40
+ case "roll-modifier":
41
+ return rollModifierBuffs(node, source);
42
+ case "feel-no-pain":
43
+ return feelNoPainBuffs(node, source);
44
+ case "keyword-grant":
45
+ return keywordGrantBuffs(node, source);
46
+ case "conditional":
47
+ return conditionalBuffs(node, source, ctx);
48
+ case "sequence":
49
+ return walkChildren(node.steps, source, ctx);
50
+ default:
51
+ return [];
52
+ }
53
+ }
54
+ function walkChildren(children, source, ctx) {
55
+ if (!Array.isArray(children))
56
+ return [];
57
+ const out = [];
58
+ for (const child of children)
59
+ out.push(...walk(child, source, ctx));
60
+ return out;
61
+ }
62
+ function rerollBuffs(node, source) {
63
+ const modifier = node.modifier;
64
+ if (!isObject(modifier))
65
+ return [];
66
+ const roll = modifier.roll;
67
+ const subset = modifier.subset;
68
+ if ((roll === "hit" || roll === "wound" || roll === "save" || roll === "damage") &&
69
+ (subset === "ones" || subset === "all-failures")) {
70
+ return [{ source, contribution: { type: "reroll", roll, subset } }];
71
+ }
72
+ return [];
73
+ }
74
+ function rollModifierBuffs(node, source) {
75
+ const modifier = node.modifier;
76
+ if (!isObject(modifier))
77
+ return [];
78
+ const operation = modifier.operation;
79
+ if (operation !== "add")
80
+ return []; // M1 supports additive only; multiplicative effects are out of scope.
81
+ const value = typeof modifier.value === "number" ? modifier.value : NaN;
82
+ if (!Number.isFinite(value))
83
+ return [];
84
+ const roll = modifier.roll;
85
+ switch (roll) {
86
+ case "hit":
87
+ return [{ source, contribution: { type: "hit-mod", value } }];
88
+ case "wound":
89
+ return [{ source, contribution: { type: "wound-mod", value } }];
90
+ case "save":
91
+ return [{ source, contribution: { type: "save-mod", value } }];
92
+ case "damage":
93
+ return [{ source, contribution: { type: "damage-mod", value } }];
94
+ default:
95
+ return [];
96
+ }
97
+ }
98
+ function feelNoPainBuffs(node, source) {
99
+ const modifier = node.modifier;
100
+ if (!isObject(modifier))
101
+ return [];
102
+ const threshold = typeof modifier.threshold === "number" ? modifier.threshold : NaN;
103
+ if (!Number.isFinite(threshold))
104
+ return [];
105
+ return [{ source, contribution: { type: "feel-no-pain", threshold } }];
106
+ }
107
+ function keywordGrantBuffs(node, source) {
108
+ const modifier = node.modifier;
109
+ if (!isObject(modifier))
110
+ return [];
111
+ const id = modifier.keyword_id ?? modifier.id;
112
+ if (typeof id !== "string" || id === "")
113
+ return [];
114
+ const params = isObject(modifier.parameters) ? modifier.parameters : undefined;
115
+ const ref = {
116
+ keyword_id: id,
117
+ ...(params !== undefined ? { parameters: params } : {}),
118
+ };
119
+ return [{ source, contribution: { type: "extra-keyword", keywordRef: ref } }];
120
+ }
121
+ function conditionalBuffs(node, source, ctx) {
122
+ const condition = node.condition;
123
+ const effect = node.effect;
124
+ if (!isObject(condition))
125
+ return [];
126
+ const negated = condition.negated === true;
127
+ const verdict = evaluateCondition(condition, ctx);
128
+ if (verdict === "unknown")
129
+ return [];
130
+ const active = negated ? !verdict : verdict;
131
+ if (!active)
132
+ return [];
133
+ return walk(effect, source, ctx);
134
+ }
135
+ /**
136
+ * Returns true/false when the engine can evaluate the condition against
137
+ * `ctx`; "unknown" when the condition references state the M1 engine has no
138
+ * channel for (the buff is then dropped — M2's diagnostic surface owns the
139
+ * "cannot auto-apply" reporting).
140
+ */
141
+ function evaluateCondition(condition, ctx) {
142
+ switch (condition.type) {
143
+ case "remained-stationary":
144
+ return ctx.attackerStationary === true;
145
+ case "target-has-keyword": {
146
+ const parameters = isObject(condition.parameters) ? condition.parameters : {};
147
+ const kw = parameters.keyword;
148
+ if (typeof kw !== "string")
149
+ return "unknown";
150
+ return (ctx.targetKeywords ?? []).includes(kw.toLowerCase());
151
+ }
152
+ default:
153
+ return "unknown";
154
+ }
155
+ }
156
+ function isObject(value) {
157
+ return typeof value === "object" && value !== null && !Array.isArray(value);
158
+ }
159
+ //# sourceMappingURL=from-keyword.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-keyword.js","sourceRoot":"","sources":["../../src/cruncher/from-keyword.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translate a weapon-keyword catalog entry into the Buff stack it contributes\n * for a given reference-site parameter set and engine context.\n *\n * Two paths converge here:\n *\n * 1. **DSL walk**, for keywords whose catalog `effect` is non-null\n * (`twin-linked`, `heavy`). The walker handles a deliberately small subset\n * of nodes — `re-roll`, `roll-modifier`, `conditional`-on-the-conditions\n * the engine knows about — and produces `Buff`s with\n * `source.kind = \"weapon-keyword\"`.\n *\n * 2. **Id dispatch**, for the eight rules whose catalog `effect` is null\n * because the DSL has no primitive for them yet — `lethal-hits`,\n * `sustained-hits`, `devastating-wounds`, `anti`, `melta`, `rapid-fire`,\n * `torrent`, `ignores-cover`. These are surfaced as `extra-keyword` buffs\n * so the engine can read them out of `ResolvedModifiers.extraKeywords`\n * and dispatch its math directly.\n *\n * Unrecognised nodes drop silently in M1 — diagnostic surfacing belongs to\n * M2's broader ability translator.\n */\nimport type { Buff, BuffSource, EngineContext, WeaponKeywordRef } from \"./buffs.js\";\n\n/** Keywords whose math the engine encodes directly (catalog `effect` is null). */\nconst ENGINE_DISPATCH_KEYWORDS = new Set([\n \"lethal-hits\",\n \"sustained-hits\",\n \"devastating-wounds\",\n \"anti\",\n \"melta\",\n \"rapid-fire\",\n \"torrent\",\n \"ignores-cover\",\n]);\n\n/**\n * Convert a single weapon-keyword reference (catalog effect + reference-site\n * parameters) into the buff contributions it makes against `context`.\n */\nexport function buffsFromKeyword(args: {\n keywordId: string;\n weaponId: string;\n effect: unknown;\n parameters?: Record<string, unknown>;\n context: EngineContext;\n}): Buff[] {\n const source: BuffSource = {\n kind: \"weapon-keyword\",\n weaponId: args.weaponId,\n keywordId: args.keywordId,\n };\n\n if (ENGINE_DISPATCH_KEYWORDS.has(args.keywordId)) {\n const ref: WeaponKeywordRef = {\n keyword_id: args.keywordId,\n ...(args.parameters !== undefined ? { parameters: args.parameters } : {}),\n };\n return [{ source, contribution: { type: \"extra-keyword\", keywordRef: ref } }];\n }\n\n if (args.effect === null || args.effect === undefined) return [];\n return walk(args.effect, source, args.context);\n}\n\nfunction walk(node: unknown, source: BuffSource, ctx: EngineContext): Buff[] {\n if (!isObject(node)) return [];\n const type = node.type;\n\n switch (type) {\n case \"re-roll\":\n return rerollBuffs(node, source);\n case \"roll-modifier\":\n return rollModifierBuffs(node, source);\n case \"feel-no-pain\":\n return feelNoPainBuffs(node, source);\n case \"keyword-grant\":\n return keywordGrantBuffs(node, source);\n case \"conditional\":\n return conditionalBuffs(node, source, ctx);\n case \"sequence\":\n return walkChildren((node as { steps?: unknown[] }).steps, source, ctx);\n default:\n return [];\n }\n}\n\nfunction walkChildren(children: unknown[] | undefined, source: BuffSource, ctx: EngineContext): Buff[] {\n if (!Array.isArray(children)) return [];\n const out: Buff[] = [];\n for (const child of children) out.push(...walk(child, source, ctx));\n return out;\n}\n\nfunction rerollBuffs(node: Record<string, unknown>, source: BuffSource): Buff[] {\n const modifier = node.modifier;\n if (!isObject(modifier)) return [];\n const roll = modifier.roll;\n const subset = modifier.subset;\n if (\n (roll === \"hit\" || roll === \"wound\" || roll === \"save\" || roll === \"damage\") &&\n (subset === \"ones\" || subset === \"all-failures\")\n ) {\n return [{ source, contribution: { type: \"reroll\", roll, subset } }];\n }\n return [];\n}\n\nfunction rollModifierBuffs(node: Record<string, unknown>, source: BuffSource): Buff[] {\n const modifier = node.modifier;\n if (!isObject(modifier)) return [];\n const operation = modifier.operation;\n if (operation !== \"add\") return []; // M1 supports additive only; multiplicative effects are out of scope.\n const value = typeof modifier.value === \"number\" ? modifier.value : NaN;\n if (!Number.isFinite(value)) return [];\n const roll = modifier.roll;\n switch (roll) {\n case \"hit\":\n return [{ source, contribution: { type: \"hit-mod\", value } }];\n case \"wound\":\n return [{ source, contribution: { type: \"wound-mod\", value } }];\n case \"save\":\n return [{ source, contribution: { type: \"save-mod\", value } }];\n case \"damage\":\n return [{ source, contribution: { type: \"damage-mod\", value } }];\n default:\n return [];\n }\n}\n\nfunction feelNoPainBuffs(node: Record<string, unknown>, source: BuffSource): Buff[] {\n const modifier = node.modifier;\n if (!isObject(modifier)) return [];\n const threshold = typeof modifier.threshold === \"number\" ? modifier.threshold : NaN;\n if (!Number.isFinite(threshold)) return [];\n return [{ source, contribution: { type: \"feel-no-pain\", threshold } }];\n}\n\nfunction keywordGrantBuffs(node: Record<string, unknown>, source: BuffSource): Buff[] {\n const modifier = node.modifier;\n if (!isObject(modifier)) return [];\n const id = modifier.keyword_id ?? modifier.id;\n if (typeof id !== \"string\" || id === \"\") return [];\n const params = isObject(modifier.parameters) ? modifier.parameters : undefined;\n const ref: WeaponKeywordRef = {\n keyword_id: id,\n ...(params !== undefined ? { parameters: params } : {}),\n };\n return [{ source, contribution: { type: \"extra-keyword\", keywordRef: ref } }];\n}\n\nfunction conditionalBuffs(\n node: Record<string, unknown>,\n source: BuffSource,\n ctx: EngineContext,\n): Buff[] {\n const condition = node.condition;\n const effect = node.effect;\n if (!isObject(condition)) return [];\n const negated = condition.negated === true;\n const verdict = evaluateCondition(condition, ctx);\n if (verdict === \"unknown\") return [];\n const active = negated ? !verdict : verdict;\n if (!active) return [];\n return walk(effect, source, ctx);\n}\n\n/**\n * Returns true/false when the engine can evaluate the condition against\n * `ctx`; \"unknown\" when the condition references state the M1 engine has no\n * channel for (the buff is then dropped — M2's diagnostic surface owns the\n * \"cannot auto-apply\" reporting).\n */\nfunction evaluateCondition(\n condition: Record<string, unknown>,\n ctx: EngineContext,\n): boolean | \"unknown\" {\n switch (condition.type) {\n case \"remained-stationary\":\n return ctx.attackerStationary === true;\n case \"target-has-keyword\": {\n const parameters = isObject(condition.parameters) ? condition.parameters : {};\n const kw = parameters.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.targetKeywords ?? []).includes(kw.toLowerCase());\n }\n default:\n return \"unknown\";\n }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Concatenate the buff contributions from every item in a list of "has-buffs"
3
+ * objects. Lower-level utility behind {@link Dataset.buffsFor}; surfaced as
4
+ * its own export so callers who want to wire their stack differently can
5
+ * still keep the one-liner ergonomics.
6
+ */
7
+ import type { Buff } from "./buffs.js";
8
+ export interface HasBuffs {
9
+ getBuffs(...args: unknown[]): Buff[];
10
+ }
11
+ export declare function getBuffs(items: HasBuffs[], ...args: unknown[]): Buff[];
12
+ //# sourceMappingURL=get-buffs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-buffs.d.ts","sourceRoot":"","sources":["../../src/cruncher/get-buffs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,QAAQ;IAIvB,QAAQ,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;CACtC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAItE"}
@@ -0,0 +1,7 @@
1
+ export function getBuffs(items, ...args) {
2
+ const out = [];
3
+ for (const item of items)
4
+ out.push(...item.getBuffs(...args));
5
+ return out;
6
+ }
7
+ //# sourceMappingURL=get-buffs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-buffs.js","sourceRoot":"","sources":["../../src/cruncher/get-buffs.ts"],"names":[],"mappings":"AAeA,MAAM,UAAU,QAAQ,CAAC,KAAiB,EAAE,GAAG,IAAe;IAC5D,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Concatenate the buff contributions from every item in a list of \"has-buffs\"\n * objects. Lower-level utility behind {@link Dataset.buffsFor}; surfaced as\n * its own export so callers who want to wire their stack differently can\n * still keep the one-liner ergonomics.\n */\nimport type { Buff } from \"./buffs.js\";\n\nexport interface HasBuffs {\n // The runtime contract is loose on purpose: any of the package's view\n // classes — WeaponView, WeaponKeywordView, AbilityView (M2), or anything\n // a caller hand-rolls — can satisfy it.\n getBuffs(...args: unknown[]): Buff[];\n}\n\nexport function getBuffs(items: HasBuffs[], ...args: unknown[]): Buff[] {\n const out: Buff[] = [];\n for (const item of items) out.push(...item.getBuffs(...args));\n return out;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The damage-projection engine: pure-function math over schema profiles and
3
+ * a flat {@link Buff} stack.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ export { resolveBuffs, type Buff, type BuffApplicability, type BuffContribution, type BuffSource, type EngineContext, type ResolveContext, type ResolvedModifiers, type WeaponKeywordRef, } from "./buffs.js";
8
+ export { buffsFromKeyword } from "./from-keyword.js";
9
+ export { getBuffs, type HasBuffs } from "./get-buffs.js";
10
+ export { crunch, type AttackProfileRef, type EngineInput, type EngineOutput, type Stage, type TargetProfileRef, } from "./engine.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cruncher/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,YAAY,EACZ,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,MAAM,EACN,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The damage-projection engine: pure-function math over schema profiles and
3
+ * a flat {@link Buff} stack.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ export { resolveBuffs, } from "./buffs.js";
8
+ export { buffsFromKeyword } from "./from-keyword.js";
9
+ export { getBuffs } from "./get-buffs.js";
10
+ export { crunch, } from "./engine.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cruncher/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,YAAY,GASb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAiB,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,MAAM,GAMP,MAAM,aAAa,CAAC","sourcesContent":["/**\n * The damage-projection engine: pure-function math over schema profiles and\n * a flat {@link Buff} stack.\n *\n * @packageDocumentation\n */\nexport {\n resolveBuffs,\n type Buff,\n type BuffApplicability,\n type BuffContribution,\n type BuffSource,\n type EngineContext,\n type ResolveContext,\n type ResolvedModifiers,\n type WeaponKeywordRef,\n} from \"./buffs.js\";\nexport { buffsFromKeyword } from \"./from-keyword.js\";\nexport { getBuffs, type HasBuffs } from \"./get-buffs.js\";\nexport {\n crunch,\n type AttackProfileRef,\n type EngineInput,\n type EngineOutput,\n type Stage,\n type TargetProfileRef,\n} from \"./engine.js\";\n"]}
@@ -1,3 +1,4 @@
1
1
  import type { RawData } from "./types.js";
2
2
  /** The full 40kdc dataset, embedded at build time and parsed once at load. */
3
3
  export declare const RAW_DATA: RawData;
4
+ //# sourceMappingURL=bundle.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.generated.d.ts","sourceRoot":"","sources":["../../src/data/bundle.generated.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,EAAE,OAA0C,CAAC"}