@archivist-gg/dnd5e 0.3.0 → 0.3.4

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 (260) hide show
  1. package/LICENSES/SRD.md +25 -6
  2. package/dist/armor/armor.codec.js +4 -3
  3. package/dist/armor/armor.parser.js +3 -2
  4. package/dist/armor/armor.schema.d.ts +4 -0
  5. package/dist/armor/armor.schema.js +2 -1
  6. package/dist/armor/armor.types.d.ts +8 -0
  7. package/dist/background/background.codec.js +7 -6
  8. package/dist/background/background.parser.js +6 -5
  9. package/dist/background/background.schema.d.ts +34 -0
  10. package/dist/background/background.schema.js +9 -6
  11. package/dist/background/background.types.d.ts +22 -0
  12. package/dist/{chunk-4TIJKN6O.js → chunk-23LZLBJK.js} +1 -1
  13. package/dist/{chunk-AFKTJXRA.js → chunk-2DFENQIJ.js} +18 -0
  14. package/dist/chunk-2FRUSVZS.js +489 -0
  15. package/dist/chunk-4FWNK4FD.js +159 -0
  16. package/dist/chunk-4U55VNC7.js +22 -0
  17. package/dist/{chunk-4JHOHWBI.js → chunk-4XTNPNQB.js} +1 -1
  18. package/dist/{chunk-HCP3GMHS.js → chunk-5N2GW72F.js} +15 -2
  19. package/dist/{chunk-42Y5IJKT.js → chunk-5U5FFFA3.js} +5 -1
  20. package/dist/{chunk-4F6AIZ36.js → chunk-5XMQPTHD.js} +1 -1
  21. package/dist/{chunk-GJOI2GHP.js → chunk-6GNXYAYP.js} +1 -1
  22. package/dist/chunk-6PKV5V2H.js +178 -0
  23. package/dist/{chunk-66XXW2Z2.js → chunk-6PXTNCBP.js} +2 -2
  24. package/dist/chunk-6X2NV665.js +33 -0
  25. package/dist/{chunk-LV4SNWZQ.js → chunk-6X4KUDMU.js} +8 -1
  26. package/dist/{chunk-ZMR77DDL.js → chunk-AW3EHF3L.js} +1 -1
  27. package/dist/chunk-CJYCVNW2.js +7 -0
  28. package/dist/chunk-COEOOQX2.js +133 -0
  29. package/dist/{chunk-XTIQWSFM.js → chunk-D5U7GMPX.js} +1 -1
  30. package/dist/{chunk-RAWJTA45.js → chunk-DEEHHLAP.js} +1 -1
  31. package/dist/chunk-DPUJDOKV.js +365 -0
  32. package/dist/{chunk-73HOFQQ5.js → chunk-ESS46GR4.js} +1 -1
  33. package/dist/chunk-EVMUJYUD.js +195 -0
  34. package/dist/{chunk-2BWAID3Y.js → chunk-FDBWYMZK.js} +29 -3
  35. package/dist/{chunk-K6Q5OUGK.js → chunk-GFZ7PITJ.js} +86 -10
  36. package/dist/chunk-H7KBLR4X.js +81 -0
  37. package/dist/{chunk-L7E2UT5A.js → chunk-HB7XZXDK.js} +5 -1
  38. package/dist/{chunk-VTYUBINW.js → chunk-HJ5EEZ47.js} +1 -1
  39. package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
  40. package/dist/{chunk-A4WENOUQ.js → chunk-I2NDWFDD.js} +52 -8
  41. package/dist/{chunk-RSDRMH54.js → chunk-IMZFOOVL.js} +1 -1
  42. package/dist/{chunk-3DZ74AKK.js → chunk-IN4M4X7P.js} +8 -2
  43. package/dist/chunk-IUAAXXNO.js +58 -0
  44. package/dist/{chunk-XJNECUPT.js → chunk-JC5UNIVM.js} +19 -0
  45. package/dist/chunk-JJ46T5XX.js +297 -0
  46. package/dist/{chunk-TUIFSVLI.js → chunk-JRDPUDT5.js} +30 -3
  47. package/dist/{chunk-24GFQU7T.js → chunk-JSZ4H6AQ.js} +1 -1
  48. package/dist/chunk-KU3YLF6D.js +106 -0
  49. package/dist/{chunk-5INR4NSL.js → chunk-LDDCNPHD.js} +1 -1
  50. package/dist/{chunk-DZXSWSBP.js → chunk-LMBAEIP6.js} +1 -1
  51. package/dist/chunk-LVQLWRCU.js +15 -0
  52. package/dist/chunk-MI7P3NX3.js +161 -0
  53. package/dist/chunk-O4SRD3J5.js +165 -0
  54. package/dist/{chunk-EMOORSQ7.js → chunk-OSQFPGLM.js} +45 -6
  55. package/dist/chunk-OZP6JFGT.js +162 -0
  56. package/dist/{chunk-NAEPSZAM.js → chunk-PDJSUH24.js} +24 -2
  57. package/dist/chunk-PP2UHLBA.js +50 -0
  58. package/dist/chunk-PVCEA4HZ.js +6 -0
  59. package/dist/{chunk-XLSVNUTK.js → chunk-R3VAKWKW.js} +1 -1
  60. package/dist/chunk-RIXI5D3R.js +89 -0
  61. package/dist/chunk-RLGQ5PZZ.js +16 -0
  62. package/dist/chunk-S7REMWW4.js +21 -0
  63. package/dist/{chunk-7VJ6BOA5.js → chunk-SMLXZUBH.js} +9 -1
  64. package/dist/{chunk-NXSWH3AE.js → chunk-T26ZVCL5.js} +1 -1
  65. package/dist/chunk-TOM66P2P.js +38 -0
  66. package/dist/chunk-UA5KS2PL.js +26 -0
  67. package/dist/{chunk-N6N74K6X.js → chunk-UBBKSYBY.js} +1 -1
  68. package/dist/{chunk-353WXS6S.js → chunk-UIRMA5KM.js} +1 -1
  69. package/dist/{chunk-GE4TLPWM.js → chunk-US5IKYCW.js} +1 -1
  70. package/dist/chunk-UXPGW4TI.js +101 -0
  71. package/dist/chunk-VQAXTIBW.js +100 -0
  72. package/dist/{chunk-WP2CX2JO.js → chunk-WELIKLZF.js} +22 -1
  73. package/dist/chunk-WPWOHGPI.js +172 -0
  74. package/dist/{chunk-GZ46OVGZ.js → chunk-WSVMLNTO.js} +1 -1
  75. package/dist/{chunk-4LZJXJJS.js → chunk-WXWPK3LI.js} +6 -2
  76. package/dist/{chunk-ZZGEVJKX.js → chunk-XBQMCEI2.js} +1 -1
  77. package/dist/{chunk-PHQRGDRQ.js → chunk-XGOFP7AQ.js} +5 -1
  78. package/dist/chunk-XJ2F2WO6.js +8 -0
  79. package/dist/chunk-XQCSGNXE.js +1580 -0
  80. package/dist/chunk-YBLDE5CG.js +42 -0
  81. package/dist/{chunk-JEYQU3JT.js → chunk-YHYTW5QA.js} +2 -18
  82. package/dist/{chunk-TLYDBT5F.js → chunk-YJJTISBN.js} +64 -7
  83. package/dist/{chunk-PZOWBI3B.js → chunk-YLL2QXY2.js} +7 -2
  84. package/dist/{chunk-MDEJHMKK.js → chunk-ZONJ66NE.js} +26 -5
  85. package/dist/class/class.codec.js +12 -9
  86. package/dist/class/class.parser.js +11 -8
  87. package/dist/class/class.schema.d.ts +55 -3
  88. package/dist/class/class.schema.js +9 -7
  89. package/dist/class/class.types.d.ts +48 -3
  90. package/dist/condition/condition.codec.d.ts +9 -0
  91. package/dist/condition/condition.entity-type.d.ts +2 -0
  92. package/dist/condition/condition.parser.d.ts +13 -0
  93. package/dist/condition/condition.parser.js +7 -0
  94. package/dist/condition/condition.schema.d.ts +29 -0
  95. package/dist/condition/condition.types.d.ts +15 -0
  96. package/dist/condition/condition.types.js +0 -0
  97. package/dist/dnd/constants.d.ts +22 -0
  98. package/dist/dnd/constants.js +9 -3
  99. package/dist/dnd/formula-tags.js +2 -2
  100. package/dist/dnd/math.d.ts +26 -0
  101. package/dist/dnd/math.js +12 -2
  102. package/dist/dnd/prose-tags.js +1 -1
  103. package/dist/dnd/resource-formula.d.ts +36 -10
  104. package/dist/dnd/resource-formula.js +10 -155
  105. package/dist/dnd/srd-tag-converter.js +3 -3
  106. package/dist/dnd/tag-grammar.js +1 -1
  107. package/dist/dnd/warn-once.d.ts +2 -0
  108. package/dist/dnd/warn-once.js +8 -0
  109. package/dist/entities/base-item-resolver.js +2 -2
  110. package/dist/entities/slug.d.ts +13 -0
  111. package/dist/entities/slug.js +5 -3
  112. package/dist/entities/wikilink-display.d.ts +3 -0
  113. package/dist/entities/wikilink-display.js +10 -0
  114. package/dist/feat/feat.category-codes.d.ts +32 -0
  115. package/dist/feat/feat.category-codes.js +14 -0
  116. package/dist/feat/feat.codec.js +7 -5
  117. package/dist/feat/feat.parser.js +6 -4
  118. package/dist/feat/feat.schema.d.ts +230 -0
  119. package/dist/feat/feat.schema.js +5 -3
  120. package/dist/feat/feat.types.d.ts +37 -1
  121. package/dist/index.js +2120 -6923
  122. package/dist/item/item.ai-schema.d.ts +1 -1
  123. package/dist/item/item.codec.js +4 -3
  124. package/dist/item/item.enrichment.js +4 -4
  125. package/dist/item/item.parser.js +3 -2
  126. package/dist/item/item.schema.d.ts +69 -3
  127. package/dist/item/item.schema.js +2 -1
  128. package/dist/item/item.types.d.ts +69 -3
  129. package/dist/monster/monster.codec.d.ts +7 -6
  130. package/dist/monster/monster.enrichment.js +4 -4
  131. package/dist/monster/monster.format.d.ts +135 -0
  132. package/dist/monster/monster.format.js +78 -0
  133. package/dist/monster/monster.parser.d.ts +6 -1
  134. package/dist/monster/monster.parser.js +10 -1
  135. package/dist/monster/monster.schema.d.ts +369 -0
  136. package/dist/monster/monster.schema.js +34 -0
  137. package/dist/monster/monster.types.d.ts +200 -14
  138. package/dist/optional-feature/optional-feature.codec.js +6 -5
  139. package/dist/optional-feature/optional-feature.parser.js +5 -4
  140. package/dist/optional-feature/optional-feature.schema.d.ts +176 -0
  141. package/dist/optional-feature/optional-feature.schema.js +4 -3
  142. package/dist/pack.d.ts +1 -1
  143. package/dist/pc/conditions.constants.d.ts +0 -1
  144. package/dist/pc/conditions.constants.js +1 -3
  145. package/dist/pc/decision-recognizer.js +1 -1
  146. package/dist/pc/pc.additional-spells.d.ts +47 -0
  147. package/dist/pc/pc.additional-spells.js +23 -0
  148. package/dist/pc/pc.asi-flatten.d.ts +18 -0
  149. package/dist/pc/pc.decision-engine.d.ts +106 -5
  150. package/dist/pc/pc.decision-engine.js +28 -10
  151. package/dist/pc/pc.defense-normalize.d.ts +11 -0
  152. package/dist/pc/pc.defense-normalize.js +6 -0
  153. package/dist/pc/pc.equipment.d.ts +30 -1
  154. package/dist/pc/pc.equipment.js +12 -7
  155. package/dist/pc/pc.feature-effects.d.ts +225 -11
  156. package/dist/pc/pc.feature-effects.js +21 -5
  157. package/dist/pc/pc.parser.js +2 -2
  158. package/dist/pc/pc.pools.d.ts +11 -3
  159. package/dist/pc/pc.pools.js +9 -13
  160. package/dist/pc/pc.proficiencies.d.ts +60 -4
  161. package/dist/pc/pc.proficiencies.js +89 -37
  162. package/dist/pc/pc.proficiency-grants.d.ts +58 -0
  163. package/dist/pc/pc.proficiency-normalize.d.ts +29 -0
  164. package/dist/pc/pc.proficiency-normalize.js +12 -0
  165. package/dist/pc/pc.proficiency-query.d.ts +13 -0
  166. package/dist/pc/pc.proficiency-query.js +5 -1
  167. package/dist/pc/pc.recalc.d.ts +69 -14
  168. package/dist/pc/pc.recalc.js +291 -142
  169. package/dist/pc/pc.resolver.d.ts +34 -8
  170. package/dist/pc/pc.resolver.js +27 -12
  171. package/dist/pc/pc.resource-seed.d.ts +3 -2
  172. package/dist/pc/pc.resource-seed.js +19 -17
  173. package/dist/pc/pc.resources.d.ts +87 -0
  174. package/dist/pc/pc.resources.js +21 -0
  175. package/dist/pc/pc.rest.d.ts +10 -1
  176. package/dist/pc/pc.rest.js +66 -12
  177. package/dist/pc/pc.schema.d.ts +147 -7
  178. package/dist/pc/pc.schema.js +4 -2
  179. package/dist/pc/pc.slotting.js +3 -3
  180. package/dist/pc/pc.spellcasting.d.ts +80 -7
  181. package/dist/pc/pc.spellcasting.js +18 -6
  182. package/dist/pc/pc.table-column.d.ts +23 -0
  183. package/dist/pc/pc.table-column.js +13 -3
  184. package/dist/pc/pc.types.d.ts +253 -16
  185. package/dist/pc/pool-layout.d.ts +47 -0
  186. package/dist/pc/pool-layout.js +16 -0
  187. package/dist/pc/roll-scope.d.ts +2 -0
  188. package/dist/pc/roll-scope.js +8 -0
  189. package/dist/pc/roll-tag-labels.d.ts +40 -0
  190. package/dist/pc/roll-tag-labels.js +17 -0
  191. package/dist/pc/spell-source.d.ts +21 -0
  192. package/dist/pc/spell-source.js +13 -0
  193. package/dist/race/race.codec.js +9 -7
  194. package/dist/race/race.parser.js +8 -6
  195. package/dist/race/race.schema.d.ts +51 -1
  196. package/dist/race/race.schema.js +8 -5
  197. package/dist/race/race.structural.d.ts +7 -0
  198. package/dist/race/race.structural.js +6 -0
  199. package/dist/race/race.types.d.ts +20 -0
  200. package/dist/schemas/attack-schema.d.ts +5 -0
  201. package/dist/schemas/caster-type-schema.d.ts +14 -0
  202. package/dist/schemas/caster-type-schema.js +6 -0
  203. package/dist/schemas/choice-schema.js +2 -2
  204. package/dist/schemas/entity-extras-schema.d.ts +53 -0
  205. package/dist/schemas/entity-extras-schema.js +10 -0
  206. package/dist/schemas/equipment-grant-schema.d.ts +2 -0
  207. package/dist/schemas/equipment-grant-schema.js +1 -1
  208. package/dist/schemas/feature-alias.d.ts +34 -0
  209. package/dist/schemas/feature-effect-schema.d.ts +124 -0
  210. package/dist/schemas/feature-effect-schema.js +1 -1
  211. package/dist/schemas/feature-schema.d.ts +8 -0
  212. package/dist/schemas/feature-schema.js +6 -4
  213. package/dist/schemas/resource-schema.d.ts +43 -0
  214. package/dist/schemas/resource-schema.js +1 -1
  215. package/dist/schemas/selection-pool-schema.d.ts +2 -0
  216. package/dist/schemas/selection-pool-schema.js +1 -1
  217. package/dist/spell/spell.access.js +23 -3
  218. package/dist/spell/spell.codec.js +4 -3
  219. package/dist/spell/spell.enrichment.js +4 -4
  220. package/dist/spell/spell.parser.d.ts +30 -0
  221. package/dist/spell/spell.parser.js +9 -2
  222. package/dist/spell/spell.scaling.d.ts +27 -6
  223. package/dist/spell/spell.scaling.js +25 -6
  224. package/dist/spell/spell.schema.d.ts +44 -2
  225. package/dist/spell/spell.schema.js +2 -1
  226. package/dist/spell/spell.types.d.ts +14 -0
  227. package/dist/subclass/subclass.codec.d.ts +1 -1
  228. package/dist/subclass/subclass.codec.js +11 -8
  229. package/dist/subclass/subclass.parser.js +10 -7
  230. package/dist/subclass/subclass.schema.d.ts +54 -7
  231. package/dist/subclass/subclass.schema.js +8 -6
  232. package/dist/subclass/subclass.types.d.ts +20 -2
  233. package/dist/types/choice.d.ts +56 -0
  234. package/dist/types/choice.js +21 -3
  235. package/dist/types/equipment-grant.d.ts +8 -1
  236. package/dist/types/feature-effect.d.ts +78 -23
  237. package/dist/types/feature.d.ts +70 -1
  238. package/dist/types/optional-feature.types.d.ts +28 -0
  239. package/dist/types/resource.d.ts +21 -1
  240. package/dist/types/selection-pool.d.ts +17 -2
  241. package/dist/weapon/weapon.classify.js +2 -2
  242. package/dist/weapon/weapon.codec.js +4 -3
  243. package/dist/weapon/weapon.parser.js +3 -2
  244. package/dist/weapon/weapon.schema.d.ts +4 -0
  245. package/dist/weapon/weapon.schema.js +2 -1
  246. package/dist/weapon/weapon.types.d.ts +8 -0
  247. package/package.json +2 -2
  248. package/dist/chunk-5MZSB6OM.js +0 -1089
  249. package/dist/chunk-5XUXXAQJ.js +0 -30
  250. package/dist/chunk-7P65Q426.js +0 -158
  251. package/dist/chunk-AIRFC5NQ.js +0 -143
  252. package/dist/chunk-BH6AMZVY.js +0 -25
  253. package/dist/chunk-CZPMTQOY.js +0 -68
  254. package/dist/chunk-M2EMFD6S.js +0 -269
  255. package/dist/chunk-N2JE7BWL.js +0 -81
  256. package/dist/chunk-NXR3BD2V.js +0 -90
  257. package/dist/chunk-Q5K6LWBZ.js +0 -45
  258. package/dist/chunk-TZK36BU7.js +0 -38
  259. package/dist/chunk-W7HWMMBF.js +0 -46
  260. package/dist/chunk-Y4I74YEG.js +0 -33
@@ -0,0 +1,1580 @@
1
+ import {
2
+ resolveFeatureResources,
3
+ resolveResourceIndex
4
+ } from "./chunk-UXPGW4TI.js";
5
+ import {
6
+ attributeUnclassedSpell,
7
+ effectiveSpellcastingAbility,
8
+ normalizeKnownSpell,
9
+ resolveSpellcasting
10
+ } from "./chunk-DPUJDOKV.js";
11
+ import {
12
+ resolveAllPools,
13
+ strandedPicks
14
+ } from "./chunk-O4SRD3J5.js";
15
+ import {
16
+ countColumnsFor,
17
+ readTableColumn
18
+ } from "./chunk-IUAAXXNO.js";
19
+ import {
20
+ recognizeDecision
21
+ } from "./chunk-YHYTW5QA.js";
22
+ import {
23
+ collectAdditionalSpells
24
+ } from "./chunk-MI7P3NX3.js";
25
+ import {
26
+ isItemEntity,
27
+ resolveEntityForEntry
28
+ } from "./chunk-UBBKSYBY.js";
29
+ import {
30
+ assembleEffectFeatures,
31
+ collectProficiencyEffectGrants
32
+ } from "./chunk-JJ46T5XX.js";
33
+ import {
34
+ ALL_LANGUAGES,
35
+ ALL_SKILL_SLUGS,
36
+ ALL_TOOLS
37
+ } from "./chunk-KU3YLF6D.js";
38
+ import {
39
+ humanizeProficiency,
40
+ proficiencyLabel,
41
+ toProfSlug
42
+ } from "./chunk-RLGQ5PZZ.js";
43
+ import {
44
+ warnOnce
45
+ } from "./chunk-LVQLWRCU.js";
46
+ import {
47
+ bareEntitySlug,
48
+ wikilinkTailSlug
49
+ } from "./chunk-6X4KUDMU.js";
50
+ import {
51
+ FEATURE_TYPE_FOR_CATEGORY,
52
+ featProgressionCategory
53
+ } from "./chunk-YBLDE5CG.js";
54
+ import {
55
+ mirrorSpellShapes
56
+ } from "./chunk-OZP6JFGT.js";
57
+ import {
58
+ ABILITY_KEYS
59
+ } from "./chunk-WELIKLZF.js";
60
+ import {
61
+ withResolvedActionCost
62
+ } from "./chunk-4U55VNC7.js";
63
+
64
+ // src/pc/pc.proficiency-grants.ts
65
+ var UNKNOWN_SOURCE = "Unknown";
66
+ function collectProficiencyGrants(resolved) {
67
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
68
+ const classArmor = [];
69
+ const classWeaponFixed = [];
70
+ const classWeaponCategories = [];
71
+ const classToolFixed = [];
72
+ for (const c of resolved.classes) {
73
+ const prof = (_a = c.entity) == null ? void 0 : _a.proficiencies;
74
+ if (!prof) continue;
75
+ const source = (_c = (_b = c.entity) == null ? void 0 : _b.name) != null ? _c : UNKNOWN_SOURCE;
76
+ for (const a of (_d = prof.armor) != null ? _d : []) classArmor.push({ value: a, source });
77
+ for (const w of (_f = (_e = prof.weapons) == null ? void 0 : _e.fixed) != null ? _f : []) classWeaponFixed.push({ value: w, source });
78
+ for (const w of (_h = (_g = prof.weapons) == null ? void 0 : _g.categories) != null ? _h : []) classWeaponCategories.push({ value: w, source });
79
+ for (const t of (_j = (_i = prof.tools) == null ? void 0 : _i.fixed) != null ? _j : []) classToolFixed.push({ value: t, source });
80
+ }
81
+ const raceSource = (_l = (_k = resolved.race) == null ? void 0 : _k.name) != null ? _l : UNKNOWN_SOURCE;
82
+ const raceLangFixed = ((_o = (_n = (_m = resolved.race) == null ? void 0 : _m.languages) == null ? void 0 : _n.fixed) != null ? _o : []).map(
83
+ (l) => ({ value: l, source: raceSource })
84
+ );
85
+ const bgToolFixed = [];
86
+ const bgLangFixed = [];
87
+ const bg = resolved.background;
88
+ if (bg) {
89
+ const source = (_p = bg.name) != null ? _p : UNKNOWN_SOURCE;
90
+ for (const entry of (_q = bg.tool_proficiencies) != null ? _q : []) {
91
+ if (entry.kind === "fixed") {
92
+ for (const t of entry.items) bgToolFixed.push({ value: t, source });
93
+ }
94
+ }
95
+ for (const entry of (_r = bg.language_proficiencies) != null ? _r : []) {
96
+ if (entry.kind === "fixed") {
97
+ for (const l of entry.languages) bgLangFixed.push({ value: l, source });
98
+ }
99
+ }
100
+ }
101
+ const featArmor = [];
102
+ const featWeapons = [];
103
+ const featTools = [];
104
+ const featLanguages = [];
105
+ for (const f of (_s = resolved.feats) != null ? _s : []) {
106
+ const featView = f;
107
+ const grants = featView.proficiencies;
108
+ if (!grants) continue;
109
+ const source = (_t = f.name) != null ? _t : UNKNOWN_SOURCE;
110
+ for (const a of (_u = grants.armor) != null ? _u : []) featArmor.push({ value: a, source });
111
+ for (const w of (_v = grants.weapons) != null ? _v : []) featWeapons.push({ value: w, source });
112
+ for (const t of (_w = grants.tools) != null ? _w : []) featTools.push({ value: t, source });
113
+ for (const l of (_x = grants.languages) != null ? _x : []) featLanguages.push({ value: l, source });
114
+ }
115
+ const { features: effectFeatures, activeBuffs } = assembleEffectFeatures(resolved);
116
+ const fx = collectProficiencyEffectGrants(effectFeatures, activeBuffs);
117
+ const nameFor = (kind, slug) => {
118
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2, _o2;
119
+ switch (kind) {
120
+ case "class":
121
+ return (_d2 = (_c2 = (_b2 = (_a2 = resolved.classes) == null ? void 0 : _a2.find((c) => {
122
+ var _a3;
123
+ return ((_a3 = c.entity) == null ? void 0 : _a3.slug) === slug;
124
+ })) == null ? void 0 : _b2.entity) == null ? void 0 : _c2.name) != null ? _d2 : UNKNOWN_SOURCE;
125
+ case "subclass":
126
+ return (_h2 = (_g2 = (_f2 = (_e2 = resolved.classes) == null ? void 0 : _e2.find((c) => {
127
+ var _a3;
128
+ return ((_a3 = c.subclass) == null ? void 0 : _a3.slug) === slug;
129
+ })) == null ? void 0 : _f2.subclass) == null ? void 0 : _g2.name) != null ? _h2 : UNKNOWN_SOURCE;
130
+ case "race":
131
+ return (_j2 = (_i2 = resolved.race) == null ? void 0 : _i2.name) != null ? _j2 : UNKNOWN_SOURCE;
132
+ case "background":
133
+ return (_l2 = (_k2 = resolved.background) == null ? void 0 : _k2.name) != null ? _l2 : UNKNOWN_SOURCE;
134
+ case "feat":
135
+ return (_o2 = (_n2 = (_m2 = resolved.feats) == null ? void 0 : _m2.find((f) => f.slug === slug)) == null ? void 0 : _n2.name) != null ? _o2 : UNKNOWN_SOURCE;
136
+ default:
137
+ return UNKNOWN_SOURCE;
138
+ }
139
+ };
140
+ const toGrants = (list, useRaw) => list.map((g) => ({ value: useRaw ? g.raw : g.value, source: nameFor(g.sourceKind, g.sourceSlug), ...g.expertise ? { expertise: true } : {} }));
141
+ const effectArmor = toGrants(fx.armor, true);
142
+ const effectWeapons = toGrants(fx.weapons, true);
143
+ const effectTools = toGrants(fx.tools, false);
144
+ const effectLanguages = toGrants(fx.languages, false);
145
+ return {
146
+ classArmor,
147
+ classWeaponFixed,
148
+ classWeaponCategories,
149
+ classToolFixed,
150
+ raceLangFixed,
151
+ bgToolFixed,
152
+ bgLangFixed,
153
+ featArmor,
154
+ featWeapons,
155
+ featTools,
156
+ featLanguages,
157
+ effectArmor,
158
+ effectWeapons,
159
+ effectTools,
160
+ effectLanguages
161
+ };
162
+ }
163
+
164
+ // src/pc/pc.asi-flatten.ts
165
+ function flattenAsiOrFeat(choice) {
166
+ var _a;
167
+ if (choice.kind !== "select-inline" || choice.id !== "asi-or-feat") return choice;
168
+ const featBranches = choice.options.filter((o) => {
169
+ var _a2;
170
+ return ((_a2 = o.choices) != null ? _a2 : []).some((c) => c.kind === "select-entity" && c.entity_type === "feat");
171
+ });
172
+ if (featBranches.length !== 1) return choice;
173
+ const inner = (_a = featBranches[0].choices) != null ? _a : [];
174
+ if (inner.length !== 1) return choice;
175
+ const only = inner[0];
176
+ if (only.kind !== "select-entity" || only.entity_type !== "feat") return choice;
177
+ return only;
178
+ }
179
+
180
+ // src/pc/pc.decision-engine.ts
181
+ var warnedDegradedEquipment = /* @__PURE__ */ new Set();
182
+ var warnedAmbiguousBare = /* @__PURE__ */ new Set();
183
+ function matchesFilter(e, where, ownerBare) {
184
+ var _a, _b, _c, _d, _e;
185
+ const d = e.data;
186
+ if (where.feature_type && d.feature_type !== where.feature_type) return false;
187
+ if (where.category && d.category !== where.category) return false;
188
+ if (where.list && !((_a = d.classes) != null ? _a : []).includes(where.list)) return false;
189
+ if (where.level !== void 0 && ((_b = d.level) != null ? _b : 0) !== where.level) return false;
190
+ if (where.edition && d.edition !== where.edition) return false;
191
+ if (where.weapon_category) {
192
+ const cat = ((_c = d.category) != null ? _c : "").toLowerCase();
193
+ if (!cat.startsWith(`${where.weapon_category}-`) && cat !== where.weapon_category) return false;
194
+ }
195
+ if (where.armor_category) {
196
+ if (((_d = d.category) != null ? _d : "").toLowerCase() !== where.armor_category) return false;
197
+ }
198
+ if (where.parent_class === "self") {
199
+ if (!d.parent_class || wikilinkTailSlug(d.parent_class) !== ownerBare) return false;
200
+ }
201
+ if (where.available_to === "self") {
202
+ const list = (_e = d.available_to) != null ? _e : [];
203
+ if (!list.some((l) => wikilinkTailSlug(l) === ownerBare)) return false;
204
+ }
205
+ return true;
206
+ }
207
+ var __matchesFilterForTest = matchesFilter;
208
+ function categoryToEntitySelect(category, id) {
209
+ const c = category.toLowerCase();
210
+ if (c === "shield") {
211
+ return { kind: "select-entity", id, label: "Choose a shield", count: 1, entity_type: "armor", where: { armor_category: "shield" } };
212
+ }
213
+ if (c.includes("armor")) {
214
+ const klass = c.includes("light") ? "light" : c.includes("medium") ? "medium" : c.includes("heavy") ? "heavy" : null;
215
+ return klass ? { kind: "select-entity", id, label: `Choose ${klass} armor`, count: 1, entity_type: "armor", where: { armor_category: klass } } : { kind: "select-entity", id, label: "Choose armor", count: 1, entity_type: "armor" };
216
+ }
217
+ const wc = c.includes("martial") ? "martial" : "simple";
218
+ return { kind: "select-entity", id, label: `Choose a ${c.replace(/-/g, " ")}`, count: 1, entity_type: "weapon", where: { weapon_category: wc } };
219
+ }
220
+ function enumerateOptions(choice, ctx, ownerBare) {
221
+ var _a, _b;
222
+ switch (choice.kind) {
223
+ case "select-inline":
224
+ return choice.options.map((o) => ({ value: o.value, label: o.label, description: o.description }));
225
+ case "select-entity": {
226
+ if (choice.from) {
227
+ let byBare;
228
+ const bareIndex = () => {
229
+ var _a2, _b2, _c, _d;
230
+ if (byBare) return byBare;
231
+ const built = /* @__PURE__ */ new Map();
232
+ const pool = ctx.registry.search("", choice.entity_type, Number.POSITIVE_INFINITY).slice().sort((a, b) => a.name.localeCompare(b.name) || a.slug.localeCompare(b.slug));
233
+ const seed = (e) => {
234
+ const bare = bareEntitySlug(e.slug);
235
+ if (built.has(bare)) {
236
+ if (!warnedAmbiguousBare.has(bare)) {
237
+ warnedAmbiguousBare.add(bare);
238
+ console.warn(`Archivist: bare slug "${bare}" matches multiple ${choice.entity_type} entities; using "${built.get(bare).slug}"`);
239
+ }
240
+ return;
241
+ }
242
+ built.set(bare, e);
243
+ };
244
+ for (const e of pool) if ((_b2 = (_a2 = ctx.isEntityVisible) == null ? void 0 : _a2.call(ctx, e)) != null ? _b2 : true) seed(e);
245
+ for (const e of pool) if (!((_d = (_c = ctx.isEntityVisible) == null ? void 0 : _c.call(ctx, e)) != null ? _d : true)) seed(e);
246
+ byBare = built;
247
+ return built;
248
+ };
249
+ return choice.from.map((slug) => {
250
+ var _a2;
251
+ const e = (_a2 = ctx.registry.getByTypeAndSlug(choice.entity_type, slug)) != null ? _a2 : bareIndex().get(slug);
252
+ return e ? { value: e.slug, label: e.name, entity: e } : { value: slug, label: slug, missing: true };
253
+ });
254
+ }
255
+ const all = ctx.registry.search("", choice.entity_type, Number.POSITIVE_INFINITY);
256
+ const filtered = choice.where ? all.filter((e) => matchesFilter(e, choice.where, ownerBare)) : all;
257
+ return filtered.map((e) => ({ value: e.slug, label: e.name, entity: e }));
258
+ }
259
+ case "select-proficiency": {
260
+ const pool = (_a = choice.from) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : choice.domain === "language" ? [...ALL_LANGUAGES] : choice.domain === "tool" ? [...ALL_TOOLS] : [];
261
+ return pool.map((v) => ({ value: v, label: humanizeProficiency(v) }));
262
+ }
263
+ case "ability-points": {
264
+ const pool = (_b = choice.pool) != null ? _b : ["str", "dex", "con", "int", "wis", "cha"];
265
+ return pool.map((a) => ({ value: a, label: a.toUpperCase() }));
266
+ }
267
+ }
268
+ }
269
+ function selectionCount(choice, selected) {
270
+ if (selected === void 0) return 0;
271
+ if (choice.kind === "ability-points") {
272
+ if (typeof selected !== "object" || Array.isArray(selected)) return 0;
273
+ return Object.values(selected).reduce((s, v) => s + (typeof v === "number" ? v : 0), 0);
274
+ }
275
+ if (Array.isArray(selected)) return selected.length;
276
+ return typeof selected === "string" && selected.length > 0 ? 1 : 0;
277
+ }
278
+ function requiredCount(choice) {
279
+ var _a;
280
+ if (choice.kind === "ability-points") return choice.points;
281
+ if (choice.kind === "select-proficiency") return choice.count;
282
+ return (_a = choice.count) != null ? _a : 1;
283
+ }
284
+ function statusOf(choice, selected, satisfied) {
285
+ const n = selectionCount(choice, selected);
286
+ const need = requiredCount(choice);
287
+ if (satisfied) return "resolved";
288
+ if (n === 0) return "unresolved";
289
+ return n >= need ? "resolved" : "partial";
290
+ }
291
+ var matchPool = (v, pool) => pool.find((p) => toProfSlug(p) === toProfSlug(v));
292
+ var canonicalizeSelection = (value, pool) => {
293
+ var _a;
294
+ if (typeof value === "string") return (_a = matchPool(value, pool)) != null ? _a : value;
295
+ if (Array.isArray(value)) return value.map((v) => {
296
+ var _a2;
297
+ return (_a2 = matchPool(v, pool)) != null ? _a2 : v;
298
+ });
299
+ return value;
300
+ };
301
+ function resolveEntityRef(ctx, entityType, value) {
302
+ if (typeof value !== "string" || value.length === 0) return void 0;
303
+ const raw = value.replace(/^\[\[/, "").replace(/\]\]$/, "");
304
+ const direct = ctx.registry.getByTypeAndSlug(entityType, raw);
305
+ if (direct) return direct;
306
+ for (const e of ctx.registry.search("", entityType, Number.POSITIVE_INFINITY)) {
307
+ if (e.slug === raw || bareEntitySlug(e.slug) === raw) return e;
308
+ }
309
+ return void 0;
310
+ }
311
+ function buildItem(inputChoice, source, level, featureName, readValue, ctx, ownerBare, effective, opts) {
312
+ var _a, _b, _c, _d;
313
+ const choice = flattenAsiOrFeat(inputChoice);
314
+ const keyPrefix = (_a = opts == null ? void 0 : opts.keyPrefix) != null ? _a : "";
315
+ const expandFeatChildren = (_b = opts == null ? void 0 : opts.expandFeatChildren) != null ? _b : true;
316
+ const key = keyPrefix + choice.id;
317
+ const raw = readValue(key);
318
+ const preExclusionOptions = enumerateOptions(choice, ctx, ownerBare);
319
+ let options = preExclusionOptions;
320
+ const mine = new Set(
321
+ (Array.isArray(raw) ? raw : raw === void 0 ? [] : [raw]).map((v) => toProfSlug(String(v)))
322
+ );
323
+ if (choice.kind === "select-proficiency" && choice.domain === "skill" && choice.from_proficient) {
324
+ options = options.filter((o) => effective.skill.has(toProfSlug(o.value)) || mine.has(toProfSlug(o.value)));
325
+ }
326
+ const postInclusionOptions = options;
327
+ const excludes = choice.kind === "select-proficiency" && (choice.domain === "language" || choice.domain === "tool" || choice.domain === "skill" && choice.expertise === true);
328
+ if (excludes && choice.kind === "select-proficiency") {
329
+ const known = choice.domain === "skill" ? effective.skillExpertise : effective[choice.domain];
330
+ options = options.filter((o) => !known.has(toProfSlug(o.value)) || mine.has(toProfSlug(o.value)));
331
+ }
332
+ const satisfied = excludes && postInclusionOptions.length > 0 && options.length === 0;
333
+ const selected = choice.kind === "select-proficiency" ? canonicalizeSelection(raw, options.map((o) => o.value)) : raw;
334
+ const item = {
335
+ key,
336
+ source,
337
+ level,
338
+ featureName,
339
+ choice,
340
+ description: opts == null ? void 0 : opts.description,
341
+ options,
342
+ selected,
343
+ status: statusOf(choice, selected, satisfied),
344
+ satisfied
345
+ };
346
+ if (choice.kind === "select-inline" && typeof selected === "string") {
347
+ const branch = choice.options.find((o) => o.value === selected);
348
+ if ((_c = branch == null ? void 0 : branch.choices) == null ? void 0 : _c.length) {
349
+ item.children = branch.choices.map((c) => buildItem(
350
+ c,
351
+ source,
352
+ level,
353
+ featureName,
354
+ readValue,
355
+ ctx,
356
+ ownerBare,
357
+ effective,
358
+ { keyPrefix, expandFeatChildren }
359
+ ));
360
+ if (item.status === "resolved" && item.children.some((c) => c.status !== "resolved")) {
361
+ item.status = "partial";
362
+ }
363
+ }
364
+ }
365
+ if (choice.kind === "select-entity" && choice.entity_type === "feat" && expandFeatChildren && typeof selected === "string") {
366
+ const entity = resolveEntityRef(ctx, "feat", selected);
367
+ const rawChoices = (_d = entity == null ? void 0 : entity.data) == null ? void 0 : _d.choices;
368
+ const featChoices = Array.isArray(rawChoices) ? rawChoices : [];
369
+ if (featChoices.length) {
370
+ const childPrefix = `${keyPrefix}feat:`;
371
+ item.children = featChoices.map((c) => buildItem(
372
+ c,
373
+ source,
374
+ level,
375
+ featureName,
376
+ readValue,
377
+ ctx,
378
+ ownerBare,
379
+ effective,
380
+ { keyPrefix: childPrefix, expandFeatChildren: false }
381
+ ));
382
+ if (item.status === "resolved" && item.children.some((c) => c.status !== "resolved")) {
383
+ item.status = "partial";
384
+ }
385
+ }
386
+ }
387
+ return item;
388
+ }
389
+ function buildSubclassItem(choice, source, level, featureName, c, ctx, ownerBare, description) {
390
+ const selected = c.subclass ? c.subclass.slug : void 0;
391
+ return {
392
+ key: "subclass",
393
+ source,
394
+ level,
395
+ featureName,
396
+ description,
397
+ choice,
398
+ options: enumerateOptions(choice, ctx, ownerBare),
399
+ selected,
400
+ status: selected ? "resolved" : "unresolved",
401
+ // A subclass pick is a select-entity, never a proficiency choice, so no
402
+ // exclusion can empty its pool.
403
+ satisfied: false
404
+ };
405
+ }
406
+ function visitProficiencyChoices(resolved, visit) {
407
+ var _a, _b, _c;
408
+ const walk = (choices, read) => {
409
+ for (const ch of choices != null ? choices : []) {
410
+ visit(ch, read(ch.id));
411
+ if (ch.kind === "select-inline") {
412
+ const sel = read(ch.id);
413
+ const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
414
+ if (branch == null ? void 0 : branch.choices) walk(branch.choices, read);
415
+ }
416
+ }
417
+ };
418
+ const oc = (_a = resolved.definition.origin_choices) != null ? _a : {};
419
+ const originRead = (ns) => (id) => oc[`${ns}:${id}`];
420
+ const stripRef = (ref) => ref.replace(/^\[\[/, "").replace(/\]\]$/, "");
421
+ const featBySlug = new Map(((_b = resolved.feats) != null ? _b : []).map((f) => [f.slug, f]));
422
+ resolved.classes.forEach((c, i) => {
423
+ var _a2, _b2, _c2, _d;
424
+ if (!c.entity) return;
425
+ const entity = c.entity;
426
+ const readAt = (lvl) => (id) => {
427
+ var _a3;
428
+ return (_a3 = c.choices[lvl]) == null ? void 0 : _a3[id];
429
+ };
430
+ if (i === 0 && ((_b2 = (_a2 = entity.skill_choices) == null ? void 0 : _a2.from) == null ? void 0 : _b2.length)) {
431
+ visit({
432
+ kind: "select-proficiency",
433
+ id: "skills",
434
+ count: entity.skill_choices.count,
435
+ domain: "skill",
436
+ from: entity.skill_choices.from
437
+ }, readAt(1)("skills"));
438
+ }
439
+ if (i === 0) walk(entity.choices, readAt(1));
440
+ for (const rf of resolved.features) {
441
+ if (rf.source.kind !== "class" && rf.source.kind !== "subclass") continue;
442
+ const belongs = rf.source.kind === "class" ? rf.source.slug === entity.slug : c.subclass != null && rf.source.slug === c.subclass.slug;
443
+ if (!belongs) continue;
444
+ for (const copy of (_c2 = rf.foldedFrom) != null ? _c2 : []) walk(copy.choices, readAt(copy.level));
445
+ walk(rf.feature.choices, readAt(rf.source.level));
446
+ }
447
+ for (const atLevel of Object.values((_d = c.choices) != null ? _d : {})) {
448
+ const block = atLevel;
449
+ const featRef = block == null ? void 0 : block.feat;
450
+ if (!block || typeof featRef !== "string") continue;
451
+ const feat = featBySlug.get(stripRef(featRef));
452
+ if (!feat) continue;
453
+ walk(feat.choices, (id) => block[`feat:${id}`]);
454
+ }
455
+ });
456
+ if (resolved.race) {
457
+ walk(resolved.race.choices, originRead("race"));
458
+ for (const t of (_c = resolved.race.traits) != null ? _c : []) walk(t.choices, originRead("race"));
459
+ }
460
+ if (resolved.background) {
461
+ walk(resolved.background.choices, originRead("background"));
462
+ if (resolved.background.feature) {
463
+ const feature = resolved.background.feature;
464
+ walk(feature.choices, originRead("background"));
465
+ }
466
+ }
467
+ if (resolved.originFeatSlug) {
468
+ const of = featBySlug.get(resolved.originFeatSlug);
469
+ if (of) walk(of.choices, (id) => oc[`background:feat:${id}`]);
470
+ }
471
+ }
472
+ var filterToPool = (vals, pool) => pool ? vals.map((v) => matchPool(v, pool)).filter((v) => !!v) : vals;
473
+ function collectChosenProficiencies(resolved) {
474
+ const out = { skills: [], expertise: [], languages: [], tools: [], weapons: [], saves: [] };
475
+ const isAbility = (v) => ABILITY_KEYS.includes(v);
476
+ visitProficiencyChoices(resolved, (choice, selected) => {
477
+ const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
478
+ if (choice.kind === "select-entity" && choice.entity_type === "weapon") {
479
+ if (choice.id === "weapon-mastery") return;
480
+ for (const v of vals) {
481
+ const bare = bareEntitySlug(String(v));
482
+ if (bare && !out.weapons.includes(bare)) out.weapons.push(bare);
483
+ }
484
+ return;
485
+ }
486
+ if (choice.kind !== "select-proficiency") return;
487
+ const valid = filterToPool(vals, choice.from);
488
+ if (choice.domain === "save") {
489
+ for (const v of valid) if (isAbility(v) && !out.saves.includes(v)) out.saves.push(v);
490
+ return;
491
+ }
492
+ const bucket = choice.domain === "skill" ? choice.expertise ? out.expertise : out.skills : choice.domain === "language" ? out.languages : choice.domain === "tool" ? out.tools : null;
493
+ if (bucket) {
494
+ for (const v of valid) if (!bucket.includes(v)) bucket.push(v);
495
+ }
496
+ });
497
+ return out;
498
+ }
499
+ function proficiencyEntryFor(raw, vocab, origin, sources, expertise) {
500
+ const hit = matchPool(raw, vocab);
501
+ const value = hit ? toProfSlug(hit) : raw;
502
+ const label = hit ? humanizeProficiency(value) : origin === "custom" ? raw : proficiencyLabel(raw);
503
+ return { value, label, sources, origin, ...expertise ? { expertise: true } : {} };
504
+ }
505
+ function computeEffectiveProficiencies(resolved) {
506
+ const grants = collectProficiencyGrants(resolved);
507
+ const chosen = collectChosenProficiencies(resolved);
508
+ const build = (domain) => {
509
+ var _a, _b, _c, _d, _e, _f, _g, _h;
510
+ const vocab = domain === "languages" ? ALL_LANGUAGES : ALL_TOOLS;
511
+ const ov = (_b = (_a = resolved.definition) == null ? void 0 : _a.overrides) == null ? void 0 : _b[domain];
512
+ const adds = (_c = ov == null ? void 0 : ov.add) != null ? _c : [];
513
+ const removes = (_d = ov == null ? void 0 : ov.remove) != null ? _d : [];
514
+ const byValue = /* @__PURE__ */ new Map();
515
+ const push = (raw, origin, source, expertise) => {
516
+ const probe = proficiencyEntryFor(raw, vocab, origin, source ? [source] : [], expertise);
517
+ const existing = byValue.get(probe.value);
518
+ if (!existing) {
519
+ byValue.set(probe.value, probe);
520
+ return;
521
+ }
522
+ if (source && !existing.sources.includes(source)) existing.sources.push(source);
523
+ if (expertise) existing.expertise = true;
524
+ const rank = { grant: 0, pick: 1, manual: 2, custom: 3 };
525
+ if (rank[origin] < rank[existing.origin]) existing.origin = origin;
526
+ };
527
+ const grantBuckets = domain === "languages" ? [grants.raceLangFixed, grants.bgLangFixed, grants.featLanguages, grants.effectLanguages] : [grants.classToolFixed, grants.bgToolFixed, grants.featTools, grants.effectTools];
528
+ for (const b of grantBuckets) for (const g of b) push(g.value, "grant", g.source, g.expertise);
529
+ for (const v of domain === "languages" ? chosen.languages : chosen.tools) push(v, "pick");
530
+ for (const v of adds) push(v, matchPool(v, vocab) ? "manual" : "custom");
531
+ const triSuppressed = /* @__PURE__ */ new Set();
532
+ if (domain === "tools") {
533
+ const tri = (_h = (_g = (_f = (_e = resolved.definition) == null ? void 0 : _e.overrides) == null ? void 0 : _f.tools) == null ? void 0 : _g.proficiency) != null ? _h : {};
534
+ for (const [tool, state] of Object.entries(tri)) {
535
+ const key = toProfSlug(tool);
536
+ if (state === "none") {
537
+ triSuppressed.add(key);
538
+ continue;
539
+ }
540
+ let entry = [...byValue.values()].find((e) => toProfSlug(e.value) === key);
541
+ if (!entry) {
542
+ push(tool, matchPool(tool, vocab) ? "manual" : "custom");
543
+ entry = [...byValue.values()].find((e) => toProfSlug(e.value) === key);
544
+ }
545
+ if (!entry) continue;
546
+ if (state === "expertise") entry.expertise = true;
547
+ else delete entry.expertise;
548
+ }
549
+ }
550
+ const suppressed = /* @__PURE__ */ new Set([...removes.map((r) => toProfSlug(r)), ...triSuppressed]);
551
+ return [...byValue.values()].filter((e) => !suppressed.has(toProfSlug(e.value))).sort((a, b) => a.label < b.label ? -1 : a.label > b.label ? 1 : 0);
552
+ };
553
+ return { languages: build("languages"), tools: build("tools") };
554
+ }
555
+ function collectChosenAbilityPoints(resolved) {
556
+ var _a, _b, _c;
557
+ const oc = (_a = resolved.definition.origin_choices) != null ? _a : {};
558
+ const out = { race: {}, background: {} };
559
+ const fold = (ns, choices) => {
560
+ var _a2;
561
+ for (const ch of choices != null ? choices : []) {
562
+ if (ch.kind !== "ability-points") continue;
563
+ const raw = oc[`${ns}:${ch.id}`];
564
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
565
+ let left = ch.points;
566
+ for (const ab of ABILITY_KEYS) {
567
+ if (ch.pool && !ch.pool.includes(ab)) continue;
568
+ const v = raw[ab];
569
+ if (typeof v !== "number" || v <= 0 || left <= 0) continue;
570
+ const take = Math.min(v, ch.max_per, left);
571
+ out[ns][ab] = ((_a2 = out[ns][ab]) != null ? _a2 : 0) + take;
572
+ left -= take;
573
+ }
574
+ }
575
+ };
576
+ const race = resolved.race;
577
+ if (race) {
578
+ fold("race", race.choices);
579
+ for (const t of (_b = race.traits) != null ? _b : []) fold("race", t.choices);
580
+ }
581
+ const bg = resolved.background;
582
+ if (bg) {
583
+ fold("background", bg.choices);
584
+ fold("background", (_c = bg.feature) == null ? void 0 : _c.choices);
585
+ }
586
+ return out;
587
+ }
588
+ function buildDecisionLedger(resolved, ctx) {
589
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
590
+ const eff = computeEffectiveProficiencies(resolved);
591
+ const chosen = collectChosenProficiencies(resolved);
592
+ const { features, activeBuffs } = assembleEffectFeatures(resolved);
593
+ const effectGrants = collectProficiencyEffectGrants(features, activeBuffs);
594
+ const effective = {
595
+ language: new Set(eff.languages.map((e) => toProfSlug(e.value))),
596
+ tool: new Set(eff.tools.map((e) => toProfSlug(e.value))),
597
+ // Same precedent as computeEffectiveProficiencies' `resolved.definition?.overrides?.[domain]`: the
598
+ // type says `skills` is non-optional, but consumers build ResolvedCharacter by cast and `tests/` is
599
+ // typechecked by nothing (the plugin's equipment-step fixtures pass a `definition` with no `skills`).
600
+ skill: new Set([
601
+ ...(_c = (_b = (_a = resolved.definition) == null ? void 0 : _a.skills) == null ? void 0 : _b.proficient) != null ? _c : [],
602
+ ...(_e = (_d = resolved.background) == null ? void 0 : _d.skill_proficiencies) != null ? _e : [],
603
+ ...chosen.skills,
604
+ ...effectGrants.skills.map((g) => g.value)
605
+ ].map(toProfSlug)),
606
+ skillExpertise: new Set([
607
+ ...(_h = (_g = (_f = resolved.definition) == null ? void 0 : _f.skills) == null ? void 0 : _g.expertise) != null ? _h : [],
608
+ ...chosen.expertise,
609
+ ...effectGrants.skills.filter((g) => g.expertise).map((g) => g.value)
610
+ ].map(toProfSlug))
611
+ };
612
+ const classes = [];
613
+ resolved.classes.forEach((c, classIndex) => {
614
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n, _o, _p;
615
+ if (!c.entity) return;
616
+ const entity = c.entity;
617
+ const ownerBare = bareEntitySlug(entity.slug);
618
+ const byLevel = /* @__PURE__ */ new Map();
619
+ const push = (lvl, item) => {
620
+ var _a3;
621
+ const arr = (_a3 = byLevel.get(lvl)) != null ? _a3 : [];
622
+ arr.push(item);
623
+ byLevel.set(lvl, arr);
624
+ };
625
+ const readAt = (lvl) => (id) => {
626
+ var _a3;
627
+ return (_a3 = c.choices[lvl]) == null ? void 0 : _a3[id];
628
+ };
629
+ const featProgressions = [...(_a2 = entity.feat_progression) != null ? _a2 : [], ...(_c2 = (_b2 = c.subclass) == null ? void 0 : _b2.feat_progression) != null ? _c2 : []];
630
+ const suppressedFeatureTypes = /* @__PURE__ */ new Set();
631
+ for (const decl of [...(_d2 = entity.selection_pools) != null ? _d2 : [], ...(_f2 = (_e2 = c.subclass) == null ? void 0 : _e2.selection_pools) != null ? _f2 : []]) {
632
+ const rp = resolved.pools.find((p) => p.classIndex === classIndex && p.id === decl.id);
633
+ if (rp && rp.count >= 1) suppressedFeatureTypes.add(decl.source.where.feature_type);
634
+ }
635
+ for (const entry of featProgressions) {
636
+ const category = featProgressionCategory(entry.category);
637
+ const ft = category ? FEATURE_TYPE_FOR_CATEGORY[category] : void 0;
638
+ if (ft) suppressedFeatureTypes.add(ft);
639
+ }
640
+ if (classIndex === 0 && ((_h2 = (_g2 = entity.skill_choices) == null ? void 0 : _g2.from) == null ? void 0 : _h2.length)) {
641
+ const skillChoice = {
642
+ kind: "select-proficiency",
643
+ id: "skills",
644
+ label: "Skill Proficiencies",
645
+ count: entity.skill_choices.count,
646
+ domain: "skill",
647
+ from: entity.skill_choices.from
648
+ };
649
+ push(1, buildItem(
650
+ skillChoice,
651
+ { kind: "class", slug: entity.slug, level: 1 },
652
+ 1,
653
+ "Proficiencies",
654
+ readAt(1),
655
+ ctx,
656
+ ownerBare,
657
+ effective
658
+ ));
659
+ }
660
+ if (classIndex === 0) {
661
+ for (const ch of (_i2 = entity.choices) != null ? _i2 : []) {
662
+ push(1, buildItem(
663
+ ch,
664
+ { kind: "class", slug: entity.slug, level: 1 },
665
+ 1,
666
+ "Proficiencies",
667
+ readAt(1),
668
+ ctx,
669
+ ownerBare,
670
+ effective
671
+ ));
672
+ }
673
+ }
674
+ if (classIndex === 0) {
675
+ ((_j2 = entity.starting_equipment) != null ? _j2 : []).forEach((eq, i) => {
676
+ var _a3;
677
+ if (eq.kind !== "choice") return;
678
+ const options = (_a3 = eq.options) != null ? _a3 : [];
679
+ let degraded = false;
680
+ const ch = {
681
+ kind: "select-inline",
682
+ id: `equipment-${i}`,
683
+ label: "Starting Equipment",
684
+ count: 1,
685
+ options: options.map((opt, j) => {
686
+ var _a4;
687
+ const label = typeof opt === "string" ? opt : (_a4 = opt == null ? void 0 : opt.label) != null ? _a4 : "";
688
+ const grants = opt && typeof opt === "object" && Array.isArray(opt.grants) ? opt.grants : [];
689
+ if (typeof opt === "string" || !(opt && typeof opt === "object" && Array.isArray(opt.grants))) {
690
+ degraded = true;
691
+ }
692
+ return {
693
+ value: `option-${j}`,
694
+ label,
695
+ choices: grants.flatMap((g, k) => g && typeof g === "object" && "category" in g && g.category ? [categoryToEntitySelect(g.category, `equipment-${i}-opt-${j}-cat-${k}`)] : [])
696
+ };
697
+ })
698
+ };
699
+ if (degraded && !warnedDegradedEquipment.has(entity.slug)) {
700
+ warnedDegradedEquipment.add(entity.slug);
701
+ console.warn(
702
+ `[archivist] Starting equipment for "${entity.slug}" is in an outdated/unstructured format; its nested picks and seeding are skipped. Re-sync the compendium (delete _compendium.md and reload) to fix.`
703
+ );
704
+ }
705
+ push(1, buildItem(
706
+ ch,
707
+ { kind: "class", slug: entity.slug, level: 1 },
708
+ 1,
709
+ "Starting Equipment",
710
+ readAt(1),
711
+ ctx,
712
+ ownerBare,
713
+ effective
714
+ ));
715
+ });
716
+ }
717
+ let sawAuthoredSubclass = false;
718
+ const staged = [];
719
+ const authoredRank = (kind, level, feature) => {
720
+ var _a3, _b3, _c3;
721
+ const at = (_c3 = (_b3 = (_a3 = kind === "class" ? entity : c.subclass) == null ? void 0 : _a3.features_by_level) == null ? void 0 : _b3[level]) != null ? _c3 : [];
722
+ const index = at.findIndex((f) => feature.id ? f.id === feature.id : f.name === feature.name);
723
+ return (kind === "class" ? 0 : 1) * 1e6 + (index < 0 ? 999999 : index);
724
+ };
725
+ for (const rf of resolved.features) {
726
+ const src = rf.source;
727
+ if (src.kind !== "class" && src.kind !== "subclass") continue;
728
+ const belongs = src.kind === "class" ? src.slug === entity.slug : c.subclass != null && src.slug === c.subclass.slug;
729
+ if (!belongs) continue;
730
+ const lvl = src.level;
731
+ const stage = (atLvl, item) => {
732
+ staged.push({ level: atLvl, item, rank: authoredRank(src.kind, atLvl, rf.feature), seq: staged.length });
733
+ };
734
+ const emitChoices = (list, atLvl, name, description) => {
735
+ var _a3;
736
+ for (const ch of list) {
737
+ if (ch.kind === "select-entity" && ch.entity_type === "subclass") {
738
+ sawAuthoredSubclass = true;
739
+ stage(atLvl, buildSubclassItem(ch, src, atLvl, name, c, ctx, ownerBare, description));
740
+ continue;
741
+ }
742
+ let emitted = ch;
743
+ if (ch.kind === "select-entity") {
744
+ const columns = countColumnsFor(ch.id);
745
+ const fromTable = columns ? readTableColumn(entity.table, c.level, columns) : null;
746
+ if (fromTable != null) emitted = { ...ch, count: Math.max((_a3 = ch.count) != null ? _a3 : 1, fromTable) };
747
+ }
748
+ stage(atLvl, buildItem(
749
+ emitted,
750
+ src,
751
+ atLvl,
752
+ name,
753
+ readAt(atLvl),
754
+ ctx,
755
+ ownerBare,
756
+ effective,
757
+ { description }
758
+ ));
759
+ }
760
+ };
761
+ const informationalItem = (atLvl, name, description) => {
762
+ var _a3, _b3;
763
+ return {
764
+ key: (_a3 = rf.feature.id) != null ? _a3 : name,
765
+ source: src,
766
+ level: atLvl,
767
+ featureName: name,
768
+ description,
769
+ choice: { kind: "select-inline", id: (_b3 = rf.feature.id) != null ? _b3 : name, options: [{ value: "_", label: "_" }] },
770
+ options: [],
771
+ selected: void 0,
772
+ status: "informational",
773
+ satisfied: false
774
+ };
775
+ };
776
+ const emitCopy = (copy) => {
777
+ let list = copy.choices;
778
+ if (!(list == null ? void 0 : list.length)) {
779
+ const recognized = recognizeDecision({ id: rf.feature.id, name: copy.name, description: copy.description });
780
+ if (recognized === "informational") {
781
+ stage(copy.level, informationalItem(copy.level, copy.name, copy.prose));
782
+ return;
783
+ }
784
+ list = recognized != null ? recognized : void 0;
785
+ if (list == null ? void 0 : list.length) {
786
+ list = list.filter((ch) => {
787
+ var _a3;
788
+ if (ch.kind !== "select-entity") return true;
789
+ const ft = (_a3 = ch.where) == null ? void 0 : _a3.feature_type;
790
+ return !(ft !== void 0 && suppressedFeatureTypes.has(ft));
791
+ });
792
+ }
793
+ }
794
+ if (!(list == null ? void 0 : list.length)) {
795
+ if (copy.prose) stage(copy.level, informationalItem(copy.level, copy.name, copy.prose));
796
+ return;
797
+ }
798
+ emitChoices(list, copy.level, copy.name, copy.description);
799
+ };
800
+ for (const copy of (_k2 = rf.foldedFrom) != null ? _k2 : []) emitCopy({ ...copy, prose: copy.description });
801
+ emitCopy({
802
+ level: lvl,
803
+ name: rf.feature.name,
804
+ description: rf.feature.description,
805
+ // The card prose: the description, else the entries joined. `||`, not `??`, so an EMPTY description
806
+ // falls through to the entries the way this branch's own guard always did.
807
+ prose: rf.feature.description || (((_l2 = rf.feature.entries) == null ? void 0 : _l2.length) ? rf.feature.entries.join("\n\n") : void 0),
808
+ choices: rf.feature.choices
809
+ });
810
+ }
811
+ staged.sort((a, b) => a.rank - b.rank || a.seq - b.seq);
812
+ for (const s of staged) push(s.level, s.item);
813
+ const subclassLevel = (_m2 = entity.subclass_level) != null ? _m2 : null;
814
+ if (subclassLevel != null && subclassLevel <= c.level && !sawAuthoredSubclass) {
815
+ const featureName = (_n = entity.subclass_feature_name) != null ? _n : "Subclass";
816
+ const synthChoice = {
817
+ kind: "select-entity",
818
+ id: "subclass",
819
+ label: featureName,
820
+ count: 1,
821
+ entity_type: "subclass",
822
+ where: { parent_class: "self" }
823
+ };
824
+ push(subclassLevel, buildSubclassItem(
825
+ synthChoice,
826
+ { kind: "class", slug: entity.slug, level: subclassLevel },
827
+ subclassLevel,
828
+ featureName,
829
+ c,
830
+ ctx,
831
+ ownerBare
832
+ ));
833
+ }
834
+ for (const pool of resolved.pools) {
835
+ if (pool.classIndex !== classIndex || pool.count < 1) continue;
836
+ const stranded = strandedPicks(pool);
837
+ const strandedSlugs = new Set(stranded.map((e) => e.slug));
838
+ const synth = {
839
+ kind: "select-entity",
840
+ id: pool.id,
841
+ label: pool.label,
842
+ count: pool.count,
843
+ entity_type: "optional-feature",
844
+ from: [...pool.available.map((e) => e.slug), ...stranded.map((e) => e.slug)]
845
+ };
846
+ const item = buildItem(
847
+ synth,
848
+ { kind: "class", slug: entity.slug, level: pool.anchorLevel },
849
+ pool.anchorLevel,
850
+ pool.label,
851
+ () => pool.selected.map((e) => e.slug),
852
+ ctx,
853
+ ownerBare,
854
+ effective
855
+ );
856
+ item.pool = { id: pool.id, anchorLevel: pool.anchorLevel };
857
+ for (const o of item.options) if (strandedSlugs.has(o.value)) o.stranded = true;
858
+ push(pool.anchorLevel, item);
859
+ }
860
+ for (const entry of featProgressions) {
861
+ const category = featProgressionCategory(entry.category);
862
+ if (!category) {
863
+ warnOnce(
864
+ `feat-progression-category:${entity.slug}:${entry.name}`,
865
+ `Archivist: feat_progression "${entry.name}" on "${entity.slug}" carries no category this engine maps (${((_o = entry.category) != null ? _o : []).join(", ")}); no decision is offered`
866
+ );
867
+ continue;
868
+ }
869
+ const progression = entry.progression;
870
+ if (Array.isArray(progression)) continue;
871
+ for (const key of Object.keys(progression)) {
872
+ const lvl = Number.parseInt(key, 10);
873
+ if (!Number.isInteger(lvl) || String(lvl) !== key) continue;
874
+ if (lvl > c.level) continue;
875
+ if (((_p = byLevel.get(lvl)) != null ? _p : []).some((i) => i.key === "feat")) {
876
+ warnOnce(
877
+ `feat-progression-collision:${entity.slug}:${lvl}`,
878
+ `Archivist: "${entry.name}" on "${entity.slug}" would add a second feat pick at level ${lvl}, where one already exists; skipping`
879
+ );
880
+ continue;
881
+ }
882
+ const progressionChoice = {
883
+ kind: "select-entity",
884
+ id: "feat",
885
+ label: entry.name,
886
+ count: 1,
887
+ entity_type: "feat",
888
+ where: { category }
889
+ };
890
+ push(lvl, buildItem(
891
+ progressionChoice,
892
+ { kind: "class", slug: entity.slug, level: lvl },
893
+ lvl,
894
+ entry.name,
895
+ readAt(lvl),
896
+ ctx,
897
+ ownerBare,
898
+ effective
899
+ ));
900
+ }
901
+ }
902
+ const levels = [...byLevel.entries()].sort(([a], [b]) => a - b).map(([level, items]) => ({ level, items }));
903
+ classes.push({ classIndex, levels });
904
+ });
905
+ const origin = [];
906
+ const oc = (_i = resolved.definition.origin_choices) != null ? _i : {};
907
+ const originRead = (ns) => (id) => oc[`${ns}:${id}`];
908
+ const pushOrigin = (choices, ns, source, featureName, ownerBare, description) => {
909
+ for (const ch of choices != null ? choices : []) {
910
+ origin.push(buildItem(
911
+ ch,
912
+ source,
913
+ 0,
914
+ featureName,
915
+ originRead(ns),
916
+ ctx,
917
+ ownerBare,
918
+ effective,
919
+ { description }
920
+ ));
921
+ }
922
+ };
923
+ if (resolved.race) {
924
+ const bare = bareEntitySlug(resolved.race.slug);
925
+ pushOrigin(
926
+ resolved.race.choices,
927
+ "race",
928
+ { kind: "race", slug: resolved.race.slug },
929
+ (_j = resolved.race.name) != null ? _j : "Race",
930
+ bare,
931
+ resolved.race.description
932
+ );
933
+ for (const t of (_k = resolved.race.traits) != null ? _k : []) {
934
+ pushOrigin(
935
+ t.choices,
936
+ "race",
937
+ { kind: "race", slug: resolved.race.slug },
938
+ t.name,
939
+ bare,
940
+ t.description
941
+ );
942
+ }
943
+ }
944
+ if (resolved.background) {
945
+ const bare = bareEntitySlug(resolved.background.slug);
946
+ pushOrigin(
947
+ resolved.background.choices,
948
+ "background",
949
+ { kind: "background", slug: resolved.background.slug },
950
+ (_l = resolved.background.name) != null ? _l : "Background",
951
+ bare,
952
+ resolved.background.description
953
+ );
954
+ if (resolved.background.feature) {
955
+ pushOrigin(
956
+ resolved.background.feature.choices,
957
+ "background",
958
+ { kind: "background", slug: resolved.background.slug },
959
+ resolved.background.feature.name,
960
+ bare,
961
+ resolved.background.feature.description
962
+ );
963
+ }
964
+ if (resolved.background.origin_feat) {
965
+ const originFeat = resolveOriginFeat(
966
+ ctx.registry,
967
+ resolved.background.origin_feat,
968
+ resolved.background.slug
969
+ );
970
+ if (originFeat) {
971
+ const featSource = { kind: "feat", slug: originFeat.feat.slug };
972
+ const featBare = bareEntitySlug(originFeat.feat.slug);
973
+ for (const ch of (_m = originFeat.feat.choices) != null ? _m : []) {
974
+ origin.push(buildItem(
975
+ ch,
976
+ featSource,
977
+ 0,
978
+ originFeat.display,
979
+ originRead("background"),
980
+ ctx,
981
+ featBare,
982
+ effective,
983
+ { keyPrefix: "feat:" }
984
+ ));
985
+ }
986
+ }
987
+ }
988
+ }
989
+ return { classes, origin };
990
+ }
991
+
992
+ // src/pc/pc.resolver.ts
993
+ var SLUG_RE = /^\[\[(.+?)\]\]$/;
994
+ function stripSlug(ref) {
995
+ if (!ref) return null;
996
+ const m = ref.match(SLUG_RE);
997
+ return m ? m[1] : ref;
998
+ }
999
+ function resolveOriginFeat(entities, originFeatRef, ownerSlug) {
1000
+ var _a, _b, _c;
1001
+ if (!originFeatRef) return null;
1002
+ const slug = wikilinkTailSlug(originFeatRef);
1003
+ const inner = originFeatRef.replace(/^\[\[/, "").replace(/\]\]$/, "").trim();
1004
+ const rawTail = (_b = (_a = inner.split("/").pop()) == null ? void 0 : _a.trim()) != null ? _b : "";
1005
+ const base = rawTail.replace(/\s*\([^()]*\)\s*$/, "").trim();
1006
+ const baseSlug = base && base !== rawTail ? wikilinkTailSlug(`[[${base}]]`) : null;
1007
+ const baseInner = baseSlug ? inner.slice(0, inner.length - rawTail.length) + base : null;
1008
+ const prefix = (ownerSlug == null ? void 0 : ownerSlug.includes("_")) ? ownerSlug.slice(0, ownerSlug.indexOf("_")) : null;
1009
+ const feats = entities.search("", "feat", Number.POSITIVE_INFINITY);
1010
+ const byPath = (p) => feats.find((f) => f.filePath === `${p}.md` || f.filePath.endsWith(`/${p}.md`));
1011
+ const sameCompendiumTail = (s) => prefix ? feats.find((f) => f.slug.startsWith(`${prefix}_feat_`) && f.slug.endsWith(`_${s}`)) : void 0;
1012
+ const anyTail = (s) => feats.find((f) => f.slug.endsWith(`_${s}`));
1013
+ const tiers = [
1014
+ () => feats.find((f) => f.slug === slug),
1015
+ // 1 exact slug
1016
+ () => byPath(inner),
1017
+ // 2 PATH
1018
+ () => baseInner ? byPath(baseInner) : void 0,
1019
+ // 3 PATH, parenthetical stripped
1020
+ () => sameCompendiumTail(slug),
1021
+ // 4 same-compendium tail
1022
+ () => baseSlug ? sameCompendiumTail(baseSlug) : void 0,
1023
+ // 5 same-compendium tail, stripped
1024
+ () => anyTail(slug),
1025
+ // 6 any tail
1026
+ () => baseSlug ? anyTail(baseSlug) : void 0
1027
+ // 7 any tail, stripped
1028
+ ];
1029
+ let reg;
1030
+ let hitTier = 0;
1031
+ for (let i = 0; i < tiers.length && !reg; i++) {
1032
+ reg = tiers[i]();
1033
+ hitTier = i + 1;
1034
+ }
1035
+ if (!reg) return null;
1036
+ const variantName = baseSlug && (hitTier === 3 || hitTier === 5 || hitTier === 7) ? rawTail : void 0;
1037
+ const data = reg.data;
1038
+ const feat = data.slug == null ? { ...data, slug: reg.slug } : data;
1039
+ return { feat, display: (_c = variantName != null ? variantName : feat.name) != null ? _c : slug };
1040
+ }
1041
+ var PCResolver = class {
1042
+ constructor(entities) {
1043
+ this.entities = entities;
1044
+ }
1045
+ /** Exposed so recalc() can apply equipment bonuses (Pass A / Pass B). */
1046
+ get registry() {
1047
+ return this.entities;
1048
+ }
1049
+ resolve(character) {
1050
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
1051
+ const warnings = [];
1052
+ const lookup = (rawRef, type) => {
1053
+ const slug = stripSlug(rawRef);
1054
+ if (!slug) return null;
1055
+ const reg = this.entities.getByTypeAndSlug(type, slug);
1056
+ if (!reg) {
1057
+ warnings.push(`Slug [[${slug}]] not found in compendium as ${type}.`);
1058
+ return null;
1059
+ }
1060
+ const data = reg.data;
1061
+ return data.slug == null ? { ...data, slug: reg.slug } : data;
1062
+ };
1063
+ const race = lookup(character.race, "race");
1064
+ const background = lookup(character.background, "background");
1065
+ const classes = character.class.map((c) => ({
1066
+ entity: lookup(c.name, "class"),
1067
+ level: c.level,
1068
+ subclass: lookup(c.subclass, "subclass"),
1069
+ choices: c.choices
1070
+ }));
1071
+ const feats = [];
1072
+ const featVia = /* @__PURE__ */ new Map();
1073
+ for (const pick of collectFeatPicks(character)) {
1074
+ const f = lookup(`[[${pick.slug}]]`, "feat");
1075
+ if (!f) continue;
1076
+ feats.push(f);
1077
+ const classSlug = (_e = (_b = (_a = classes[pick.classIndex]) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug) != null ? _e : stripSlug((_d = (_c = character.class[pick.classIndex]) == null ? void 0 : _c.name) != null ? _d : null);
1078
+ if (classSlug && pick.level !== null && !featVia.has(f.slug)) featVia.set(f.slug, { kind: "class", slug: classSlug, level: pick.level });
1079
+ }
1080
+ const originFeat = (background == null ? void 0 : background.origin_feat) ? resolveOriginFeat(this.entities, background.origin_feat, background.slug) : null;
1081
+ const originFeatSlug = originFeat == null ? void 0 : originFeat.feat.slug;
1082
+ if (originFeat && !feats.some((f) => f.slug === originFeat.feat.slug)) {
1083
+ feats.push(originFeat.feat);
1084
+ }
1085
+ if (originFeat && background && !featVia.has(originFeat.feat.slug)) {
1086
+ featVia.set(originFeat.feat.slug, { kind: "background", slug: background.slug });
1087
+ }
1088
+ const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
1089
+ const features = collectResolvedFeatures(race, classes, background, feats, featVia);
1090
+ const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background);
1091
+ features.push(...extraFeatures);
1092
+ const casters = [];
1093
+ classes.forEach((c, i) => {
1094
+ const profile = c.entity ? resolveSpellcasting(c) : null;
1095
+ const classSlug = profile ? stripSlug(character.class[i].name) : null;
1096
+ if (profile && classSlug != null) casters.push({ classSlug, spellList: profile.spellList });
1097
+ });
1098
+ const spells = [];
1099
+ for (const raw of (_f = character.spells.known) != null ? _f : []) {
1100
+ const n = normalizeKnownSpell(raw);
1101
+ const reg = this.entities.getByTypeAndSlug("spell", n.slug);
1102
+ if (!reg) {
1103
+ warnings.push(`Spell [[${n.slug}]] not found in compendium.`);
1104
+ continue;
1105
+ }
1106
+ const entity = mirrorSpellShapes(reg.data);
1107
+ const isCantrip = ((_g = entity.level) != null ? _g : 0) === 0;
1108
+ const classSlug = (_h = n.classSlug) != null ? _h : attributeUnclassedSpell(entity.classes, casters, this.entities);
1109
+ const prep = isCantrip || n.alwaysPrepared ? true : (_i = n.preparedFlag) != null ? _i : false;
1110
+ spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared, persisted: true });
1111
+ }
1112
+ const oc = (_j = character.origin_choices) != null ? _j : {};
1113
+ if (originFeat) {
1114
+ const read = (childId) => oc[`background:feat:${childId}`];
1115
+ spells.push(...collectFeatGrantedSpells(originFeat.feat, read, this.entities, warnings));
1116
+ }
1117
+ for (const entry of character.class) {
1118
+ for (const atLevel of Object.values((_k = entry.choices) != null ? _k : {})) {
1119
+ const featRef = atLevel.feat;
1120
+ if (typeof featRef !== "string") continue;
1121
+ const slug = stripSlug(featRef);
1122
+ if (!slug) continue;
1123
+ const reg = this.entities.getByTypeAndSlug("feat", slug);
1124
+ if (!reg) continue;
1125
+ const feat = reg.data;
1126
+ const read = (childId) => atLevel[`feat:${childId}`];
1127
+ spells.push(...collectFeatGrantedSpells(feat, read, this.entities, warnings));
1128
+ }
1129
+ }
1130
+ const ownSpellcastingAbility = (_l = classes.map((c) => {
1131
+ if (!c.entity) return null;
1132
+ const p = resolveSpellcasting(c);
1133
+ return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
1134
+ }).find((a) => a != null)) != null ? _l : null;
1135
+ spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
1136
+ spells.push(...collectAdditionalSpells({
1137
+ race,
1138
+ feats,
1139
+ classes,
1140
+ totalLevel,
1141
+ ownAbility: ownSpellcastingAbility,
1142
+ alreadyCollected: spells,
1143
+ entities: this.entities,
1144
+ warnings
1145
+ }));
1146
+ const chosenMasteries = collectChosenWeaponMasteries(character);
1147
+ if (chosenMasteries.full.length > 0) {
1148
+ const target = features.find(
1149
+ (rf) => {
1150
+ var _a2;
1151
+ return rf.feature.id === "weapon-mastery" || ((_a2 = rf.feature.choices) == null ? void 0 : _a2.some(
1152
+ (ch) => ch.kind === "select-entity" && ch.id === "weapon-mastery" && ch.entity_type === "weapon"
1153
+ ));
1154
+ }
1155
+ );
1156
+ if (target) {
1157
+ const names = chosenMasteries.full.map(
1158
+ (fullSlug) => {
1159
+ var _a2, _b2;
1160
+ return (_b2 = (_a2 = this.entities.getByTypeAndSlug("weapon", fullSlug)) == null ? void 0 : _a2.name) != null ? _b2 : fullSlug;
1161
+ }
1162
+ );
1163
+ const entry = { label: "Mastered weapons", description: names.join(", ") };
1164
+ target.chosenInline = target.chosenInline ? [...target.chosenInline, entry] : [entry];
1165
+ }
1166
+ }
1167
+ const featureResources = resolveFeatureResources(features);
1168
+ const resolvedCharacter = {
1169
+ definition: character,
1170
+ race,
1171
+ classes,
1172
+ background,
1173
+ feats,
1174
+ ...originFeatSlug ? { originFeatSlug } : {},
1175
+ totalLevel,
1176
+ features,
1177
+ spells: dedupeResolvedSpells(spells),
1178
+ pools: [],
1179
+ resources: featureResources,
1180
+ weaponMasteries: chosenMasteries.bare,
1181
+ state: character.state
1182
+ };
1183
+ resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities, featureResources);
1184
+ resolvedCharacter.resources = resolveResourceIndex(resolvedCharacter);
1185
+ return { character: resolvedCharacter, warnings };
1186
+ }
1187
+ };
1188
+ function collectChosenWeaponMasteries(character) {
1189
+ var _a, _b;
1190
+ const full = /* @__PURE__ */ new Set();
1191
+ for (const cls of (_a = character.class) != null ? _a : []) {
1192
+ for (const lvl of Object.values((_b = cls.choices) != null ? _b : {})) {
1193
+ const pick = lvl["weapon-mastery"];
1194
+ if (Array.isArray(pick)) {
1195
+ for (const s of pick) if (typeof s === "string") full.add(s);
1196
+ }
1197
+ }
1198
+ }
1199
+ return { full: [...full], bare: [...new Set([...full].map(bareEntitySlug))] };
1200
+ }
1201
+ function collectFeatPicks(character) {
1202
+ const picks = /* @__PURE__ */ new Map();
1203
+ character.class.forEach((c, classIndex) => {
1204
+ for (const [lvl, choiceBlock] of Object.entries(c.choices)) {
1205
+ const feat = choiceBlock == null ? void 0 : choiceBlock.feat;
1206
+ if (typeof feat === "string") {
1207
+ const s = stripSlug(feat);
1208
+ const level = /^\d+$/.test(lvl) ? Number(lvl) : null;
1209
+ if (s && !picks.has(s)) picks.set(s, { slug: s, classIndex, level });
1210
+ }
1211
+ }
1212
+ });
1213
+ return [...picks.values()];
1214
+ }
1215
+ function collectFeatGrantedSpells(feat, read, entities, warnings) {
1216
+ var _a;
1217
+ const ability = readChosenSpellAbility(read);
1218
+ const slugs = [];
1219
+ collectSpellPickSlugs(feat.choices, read, slugs);
1220
+ const out = [];
1221
+ for (const rawSlug of slugs) {
1222
+ const slug = (_a = stripSlug(rawSlug)) != null ? _a : rawSlug;
1223
+ const reg = entities.getByTypeAndSlug("spell", slug);
1224
+ if (!reg) {
1225
+ warnings.push(`Feat spell [[${slug}]] not found in compendium.`);
1226
+ continue;
1227
+ }
1228
+ const entity = mirrorSpellShapes(reg.data);
1229
+ out.push({ entity, slug, classSlug: null, source: "feat", prepared: true, alwaysPrepared: true, ability });
1230
+ }
1231
+ return out;
1232
+ }
1233
+ function collectItemGrantedSpells(character, ownAbility, entities, warnings) {
1234
+ var _a;
1235
+ const out = [];
1236
+ ((_a = character.equipment) != null ? _a : []).forEach((entry, entryIndex) => {
1237
+ var _a2, _b, _c, _d, _e, _f;
1238
+ const spellRef = (_a2 = entry.overrides) == null ? void 0 : _a2.spell;
1239
+ if (!spellRef) return;
1240
+ const { entity: itemEntity } = resolveEntityForEntry(entry.item, entities);
1241
+ const scrollLevel = isItemEntity(itemEntity) ? itemEntity.scroll_level : void 0;
1242
+ if (scrollLevel == null) return;
1243
+ const slug = (_b = stripSlug(spellRef)) != null ? _b : spellRef;
1244
+ const reg = entities.getByTypeAndSlug("spell", slug);
1245
+ if (!reg) {
1246
+ warnings.push(`Scroll spell [[${slug}]] not found in compendium.`);
1247
+ return;
1248
+ }
1249
+ const entity = mirrorSpellShapes(reg.data);
1250
+ const ability = (_f = (_d = (_c = entry.overrides) == null ? void 0 : _c.spell_ability) != null ? _d : ownAbility) != null ? _f : (_e = character.overrides) == null ? void 0 : _e.spellcasting_ability;
1251
+ out.push({ entity, slug, classSlug: null, source: "item", prepared: true, alwaysPrepared: true, ability, entryIndex });
1252
+ });
1253
+ return out;
1254
+ }
1255
+ function dedupeResolvedSpells(spells) {
1256
+ const byKey = /* @__PURE__ */ new Map();
1257
+ for (const s of spells) {
1258
+ const key = s.source === "item" ? `${s.slug}#${s.entryIndex}` : s.slug;
1259
+ const existing = byKey.get(key);
1260
+ if (!existing) {
1261
+ byKey.set(key, s);
1262
+ continue;
1263
+ }
1264
+ if (existing.source === "feat" && s.source !== "feat") {
1265
+ byKey.set(key, { ...s, alwaysPrepared: s.alwaysPrepared || existing.alwaysPrepared, prepared: s.prepared || existing.alwaysPrepared, ...existing.persisted || s.persisted ? { persisted: true } : {} });
1266
+ } else if (s.alwaysPrepared && !existing.alwaysPrepared) {
1267
+ byKey.set(key, { ...existing, alwaysPrepared: true, prepared: true });
1268
+ }
1269
+ }
1270
+ return [...byKey.values()];
1271
+ }
1272
+ function readChosenSpellAbility(read) {
1273
+ const pick = read("spellcasting-ability");
1274
+ return typeof pick === "string" && ABILITY_KEYS.includes(pick) ? pick : null;
1275
+ }
1276
+ function collectSpellPickSlugs(choices, read, out) {
1277
+ for (const ch of choices != null ? choices : []) {
1278
+ if (ch.kind === "select-entity" && ch.entity_type === "spell") {
1279
+ const sel = read(ch.id);
1280
+ const picks = Array.isArray(sel) ? sel : typeof sel === "string" ? [sel] : [];
1281
+ for (const p of picks) if (typeof p === "string" && p.length > 0) out.push(p);
1282
+ } else if (ch.kind === "select-inline") {
1283
+ const sel = read(ch.id);
1284
+ const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
1285
+ if (branch == null ? void 0 : branch.choices) collectSpellPickSlugs(branch.choices, read, out);
1286
+ }
1287
+ }
1288
+ }
1289
+ function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null) {
1290
+ var _a, _b;
1291
+ const out = [];
1292
+ classes.forEach((c, i) => {
1293
+ var _a2, _b2, _c, _d;
1294
+ if (!c.entity) return;
1295
+ const entity = c.entity;
1296
+ const entry = character.class[i];
1297
+ if (!entry) return;
1298
+ for (const [lvlStr, atLevel] of Object.entries(entry.choices)) {
1299
+ const lvl = Number(lvlStr);
1300
+ if (!Number.isFinite(lvl) || lvl > entry.level) continue;
1301
+ const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
1302
+ const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
1303
+ for (const feature of [...features, ...subFeatures]) {
1304
+ walkChoiceGrants(feature.choices, atLevel, (granted, suppress) => {
1305
+ out.push({
1306
+ feature: granted,
1307
+ source: { kind: "class", slug: entity.slug, level: lvl },
1308
+ ...suppress ? { renderSuppressed: true } : {}
1309
+ });
1310
+ }, registry);
1311
+ }
1312
+ }
1313
+ });
1314
+ const oc = (_a = character.origin_choices) != null ? _a : {};
1315
+ const originAt = (ns) => {
1316
+ const at = {};
1317
+ for (const [key, val] of Object.entries(oc)) {
1318
+ if (key.startsWith(`${ns}:`)) at[key.slice(ns.length + 1)] = val;
1319
+ }
1320
+ return at;
1321
+ };
1322
+ if (race) {
1323
+ const atRace = originAt("race");
1324
+ const emitRace = (granted, suppress) => {
1325
+ out.push({
1326
+ feature: granted,
1327
+ source: { kind: "race", slug: race.slug },
1328
+ ...suppress ? { renderSuppressed: true } : {}
1329
+ });
1330
+ };
1331
+ walkChoiceGrants(race.choices, atRace, emitRace, registry);
1332
+ for (const trait of (_b = race.traits) != null ? _b : []) {
1333
+ walkChoiceGrants(trait.choices, atRace, emitRace, registry);
1334
+ }
1335
+ }
1336
+ if (background) {
1337
+ const atBg = originAt("background");
1338
+ const emitBg = (granted, suppress) => {
1339
+ out.push({
1340
+ feature: granted,
1341
+ source: { kind: "background", slug: background.slug },
1342
+ ...suppress ? { renderSuppressed: true } : {}
1343
+ });
1344
+ };
1345
+ walkChoiceGrants(background.choices, atBg, emitBg, registry);
1346
+ if (background.feature) {
1347
+ walkChoiceGrants(background.feature.choices, atBg, emitBg, registry);
1348
+ }
1349
+ }
1350
+ return out;
1351
+ }
1352
+ function walkChoiceGrants(choices, atLevel, emit, registry) {
1353
+ var _a, _b, _c, _d, _e;
1354
+ for (const ch of choices != null ? choices : []) {
1355
+ const sel = atLevel[ch.id];
1356
+ if (ch.kind === "select-entity" && ch.entity_type === "optional-feature") {
1357
+ const slugs = Array.isArray(sel) ? sel : typeof sel === "string" ? [sel] : [];
1358
+ for (const slug of slugs) {
1359
+ const reg = registry.getByTypeAndSlug("optional-feature", (_a = stripSlug(String(slug))) != null ? _a : String(slug));
1360
+ if (!reg) continue;
1361
+ const d = reg.data;
1362
+ emit({
1363
+ id: (_b = d.slug) != null ? _b : String(slug),
1364
+ name: (_c = d.name) != null ? _c : String(slug),
1365
+ description: d.description,
1366
+ effects: d.effects,
1367
+ ...d.action_cost ? { action: d.action_cost } : {}
1368
+ });
1369
+ }
1370
+ }
1371
+ if (ch.kind === "select-inline") {
1372
+ const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
1373
+ if ((_d = branch == null ? void 0 : branch.effects) == null ? void 0 : _d.length) {
1374
+ const wc = (_e = branch.choices) == null ? void 0 : _e.find(
1375
+ (c) => c.kind === "select-entity" && c.entity_type === "weapon"
1376
+ );
1377
+ const picked = wc ? atLevel == null ? void 0 : atLevel[wc.id] : void 0;
1378
+ const effects = branch.effects.map(
1379
+ (e) => e.kind === "weapon-ability" && e.weapons === "chosen" && typeof picked === "string" ? { ...e, weapons: [bareEntitySlug(picked)] } : e
1380
+ );
1381
+ emit({ id: `${ch.id}-${branch.value}`, name: branch.label, description: branch.description, effects }, true);
1382
+ }
1383
+ if (branch == null ? void 0 : branch.choices) walkChoiceGrants(branch.choices, atLevel, emit, registry);
1384
+ }
1385
+ }
1386
+ }
1387
+ function resolveChosenInline(feature, atLevel) {
1388
+ var _a;
1389
+ if (!atLevel) return void 0;
1390
+ const picks = [];
1391
+ for (const ch of (_a = feature.choices) != null ? _a : []) {
1392
+ if (ch.kind !== "select-inline") continue;
1393
+ const sel = atLevel[ch.id];
1394
+ if (typeof sel !== "string") continue;
1395
+ const opt = ch.options.find((o) => o.value === sel);
1396
+ if (!opt) continue;
1397
+ picks.push({ label: opt.label, description: opt.description });
1398
+ }
1399
+ return picks.length ? picks : void 0;
1400
+ }
1401
+ function isAsiSlotFeature(feature) {
1402
+ var _a, _b, _c, _d;
1403
+ return feature.id === "ability-score-improvement" && !((_a = feature.effects) != null ? _a : []).some((e) => !(e.kind === "ability-score-increase" && e.abilities === "chosen")) && !((_b = feature.resources) == null ? void 0 : _b.length) && !feature.action && !((_c = feature.sub_features) == null ? void 0 : _c.length) && !((_d = feature.attacks) == null ? void 0 : _d.length);
1404
+ }
1405
+ function foldRepeated(list) {
1406
+ const byId = /* @__PURE__ */ new Map();
1407
+ for (const rf of list) {
1408
+ const id = rf.feature.id;
1409
+ if (!id || rf.buildOnly) continue;
1410
+ const arr = byId.get(id);
1411
+ if (arr) arr.push(rf);
1412
+ else byId.set(id, [rf]);
1413
+ }
1414
+ const out = [];
1415
+ for (const rf of list) {
1416
+ if (!rf.feature.id || rf.buildOnly) {
1417
+ out.push(rf);
1418
+ continue;
1419
+ }
1420
+ const arr = byId.get(rf.feature.id);
1421
+ if (!arr || arr.length === 1) {
1422
+ out.push(rf);
1423
+ continue;
1424
+ }
1425
+ const sorted = [...arr].sort((a, b) => a.source.level - b.source.level);
1426
+ const top = sorted[sorted.length - 1];
1427
+ if (rf !== top) continue;
1428
+ const effects = sorted.flatMap((c) => {
1429
+ var _a;
1430
+ return (_a = c.feature.effects) != null ? _a : [];
1431
+ });
1432
+ const seen = /* @__PURE__ */ new Set();
1433
+ const resources = sorted.flatMap((c) => {
1434
+ var _a;
1435
+ return ((_a = c.feature.resources) != null ? _a : []).filter((r) => !seen.has(r.id) && seen.add(r.id));
1436
+ });
1437
+ const chosenInline = sorted.flatMap((c) => c.chosenInline ? [c.chosenInline] : []);
1438
+ const cardProse = (f) => {
1439
+ var _a, _b;
1440
+ return (_b = f.description) != null ? _b : ((_a = f.entries) == null ? void 0 : _a.length) ? f.entries.join("\n\n") : void 0;
1441
+ };
1442
+ const foldedFrom = sorted.slice(0, -1).map((c) => {
1443
+ var _a;
1444
+ return {
1445
+ level: c.source.level,
1446
+ name: c.feature.name,
1447
+ ...cardProse(c.feature) ? { description: cardProse(c.feature) } : {},
1448
+ ...((_a = c.feature.choices) == null ? void 0 : _a.length) ? { choices: c.feature.choices } : {}
1449
+ };
1450
+ });
1451
+ out.push({
1452
+ ...top,
1453
+ feature: { ...top.feature, ...effects.length ? { effects } : {}, ...resources.length ? { resources } : {} },
1454
+ ...chosenInline.length ? { chosenInline: chosenInline.flat() } : {},
1455
+ ...foldedFrom.length ? { foldedFrom } : {}
1456
+ });
1457
+ }
1458
+ return out;
1459
+ }
1460
+ function collectResolvedFeatures(race, classes, background, feats, featVia = /* @__PURE__ */ new Map()) {
1461
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
1462
+ const out = [];
1463
+ for (const c of classes) {
1464
+ if (!c.entity) continue;
1465
+ const slug = c.entity.slug;
1466
+ const byLevel = (_a = c.entity.features_by_level) != null ? _a : {};
1467
+ const classFeatures = [];
1468
+ const subclassFeatures = [];
1469
+ for (const [lvlStr, feats0] of Object.entries(byLevel)) {
1470
+ const lvl = parseInt(lvlStr, 10);
1471
+ if (Number.isNaN(lvl) || lvl > c.level) continue;
1472
+ for (const feat of feats0) {
1473
+ const chosenInline = resolveChosenInline(feat, (_b = c.choices) == null ? void 0 : _b[lvl]);
1474
+ const feature = withResolvedActionCost(feat);
1475
+ classFeatures.push({
1476
+ feature,
1477
+ source: { kind: "class", slug, level: lvl },
1478
+ ...chosenInline ? { chosenInline } : {},
1479
+ ...isAsiSlotFeature(feature) ? { buildOnly: true } : {}
1480
+ });
1481
+ }
1482
+ }
1483
+ if (c.subclass) {
1484
+ const sSlug = c.subclass.slug;
1485
+ const sByLevel = (_c = c.subclass.features_by_level) != null ? _c : {};
1486
+ for (const [lvlStr, feats0] of Object.entries(sByLevel)) {
1487
+ const lvl = parseInt(lvlStr, 10);
1488
+ if (Number.isNaN(lvl) || lvl > c.level) continue;
1489
+ for (const feat of feats0) {
1490
+ const chosenInline = resolveChosenInline(feat, (_d = c.choices) == null ? void 0 : _d[lvl]);
1491
+ const feature = withResolvedActionCost(feat);
1492
+ subclassFeatures.push({
1493
+ feature,
1494
+ source: { kind: "subclass", slug: sSlug, level: lvl },
1495
+ ...chosenInline ? { chosenInline } : {},
1496
+ ...isAsiSlotFeature(feature) ? { buildOnly: true } : {}
1497
+ });
1498
+ }
1499
+ }
1500
+ }
1501
+ const foldedClass = foldRepeated(classFeatures);
1502
+ const foldedSubclass = foldRepeated(subclassFeatures);
1503
+ out.push(...foldedClass);
1504
+ if ((_e = c.entity.resources) == null ? void 0 : _e.length) {
1505
+ out.push({
1506
+ feature: { name: c.entity.name, resources: c.entity.resources },
1507
+ source: { kind: "class", slug, level: 1 }
1508
+ });
1509
+ }
1510
+ out.push(...foldedSubclass);
1511
+ if ((_g = (_f = c.subclass) == null ? void 0 : _f.resources) == null ? void 0 : _g.length) {
1512
+ out.push({
1513
+ feature: { name: c.subclass.name, resources: c.subclass.resources },
1514
+ source: { kind: "subclass", slug: c.subclass.slug, level: 1 }
1515
+ });
1516
+ }
1517
+ }
1518
+ if (race) {
1519
+ const traits = (_h = race.traits) != null ? _h : [];
1520
+ for (const feat of traits) {
1521
+ out.push({ feature: withResolvedActionCost(feat), source: { kind: "race", slug: race.slug } });
1522
+ }
1523
+ }
1524
+ if (background) {
1525
+ const bgFeature = background.feature;
1526
+ if (bgFeature) {
1527
+ out.push({ feature: withResolvedActionCost(bgFeature), source: { kind: "background", slug: background.slug } });
1528
+ }
1529
+ }
1530
+ for (const feat of feats) {
1531
+ const via = featVia.get(feat.slug);
1532
+ const featSource = { kind: "feat", slug: feat.slug, ...via ? { via } : {} };
1533
+ const bundled = (_i = feat.features) != null ? _i : [];
1534
+ const entityEffects = (_j = feat.effects) != null ? _j : [];
1535
+ if (bundled.length > 0) {
1536
+ bundled.forEach((f, i) => {
1537
+ var _a2, _b2;
1538
+ const feature = i === 0 ? { ...f, action: (_a2 = f.action) != null ? _a2 : feat.action_cost, ...entityEffects.length > 0 ? { effects: [...(_b2 = f.effects) != null ? _b2 : [], ...entityEffects] } : {} } : f;
1539
+ out.push({ feature, source: featSource });
1540
+ });
1541
+ } else {
1542
+ const name = (_k = feat.name) != null ? _k : feat.slug;
1543
+ const foldedDesc = [feat.description, ...(_l = feat.benefits) != null ? _l : []].filter(Boolean).join("\n\n") || void 0;
1544
+ const buildOnly = ((_m = feat.choices) != null ? _m : []).some((c) => c.kind === "ability-points" && c.points >= 2) && !((_n = feat.effects) == null ? void 0 : _n.length) && !((_o = feat.resources) == null ? void 0 : _o.length) && bundled.length === 0;
1545
+ out.push({
1546
+ feature: {
1547
+ name,
1548
+ ...foldedDesc ? { description: foldedDesc } : {},
1549
+ ...feat.resources ? { resources: feat.resources } : {},
1550
+ // R4-G3a §10.2.3: a feat's ENTITY-level action cost (SRD 2024 Boon of the Night Spirit)
1551
+ // reaches the synthesized feature, so the row routes off Passive onto its own economy.
1552
+ ...feat.action_cost ? { action: feat.action_cost } : {},
1553
+ ...entityEffects.length > 0 ? { effects: entityEffects } : {}
1554
+ },
1555
+ source: featSource,
1556
+ ...buildOnly ? { buildOnly: true } : {}
1557
+ });
1558
+ }
1559
+ }
1560
+ return out;
1561
+ }
1562
+
1563
+ export {
1564
+ stripSlug,
1565
+ resolveOriginFeat,
1566
+ PCResolver,
1567
+ collectChosenWeaponMasteries,
1568
+ collectFeatPicks,
1569
+ collectFeatGrantedSpells,
1570
+ collectItemGrantedSpells,
1571
+ dedupeResolvedSpells,
1572
+ collectChosenGrantedFeatures,
1573
+ collectResolvedFeatures,
1574
+ collectProficiencyGrants,
1575
+ __matchesFilterForTest,
1576
+ collectChosenProficiencies,
1577
+ computeEffectiveProficiencies,
1578
+ collectChosenAbilityPoints,
1579
+ buildDecisionLedger
1580
+ };