@archivist-gg/dnd5e 0.3.11 → 0.3.17

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 (192) hide show
  1. package/LICENSES/SRD.md +38 -3
  2. package/dist/armor/armor.codec.js +3 -3
  3. package/dist/armor/armor.parser.js +2 -2
  4. package/dist/armor/armor.schema.d.ts +1 -0
  5. package/dist/armor/armor.schema.js +1 -1
  6. package/dist/armor/armor.types.d.ts +2 -0
  7. package/dist/background/background.codec.js +5 -5
  8. package/dist/background/background.parser.js +4 -4
  9. package/dist/background/background.schema.js +3 -3
  10. package/dist/{chunk-XTRDCSJB.js → chunk-2JSQE3DD.js} +15 -1
  11. package/dist/chunk-2LULMQTF.js +14 -0
  12. package/dist/{chunk-MAH6DCRI.js → chunk-2UDSN6VW.js} +1 -1
  13. package/dist/chunk-3LWMFYKS.js +57 -0
  14. package/dist/{chunk-JCEV7KYN.js → chunk-3MGQFFNE.js} +1 -1
  15. package/dist/chunk-3VZZDKRA.js +1014 -0
  16. package/dist/{chunk-2DFENQIJ.js → chunk-5GKJAUMX.js} +2 -0
  17. package/dist/{chunk-2M34FLAY.js → chunk-5MXFW4O3.js} +9 -3
  18. package/dist/{chunk-4DOMDEBZ.js → chunk-6EI737IO.js} +1 -1
  19. package/dist/{chunk-G247QXWW.js → chunk-7ATNGKBS.js} +1 -1
  20. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  21. package/dist/{chunk-T2SNEJOE.js → chunk-7QN7XSAK.js} +1 -1
  22. package/dist/{chunk-DNAJRMNN.js → chunk-A54HHCWW.js} +6 -7
  23. package/dist/{chunk-7YU4NPKR.js → chunk-AZRGFUSR.js} +1 -1
  24. package/dist/{chunk-PP2UHLBA.js → chunk-B3DP42OM.js} +5 -0
  25. package/dist/chunk-CM5CWO4K.js +735 -0
  26. package/dist/{chunk-MQ747UCR.js → chunk-DOXSFYLH.js} +70 -2
  27. package/dist/chunk-EJP263OP.js +186 -0
  28. package/dist/{chunk-SMLXZUBH.js → chunk-EYX4JMAU.js} +4 -1
  29. package/dist/{chunk-V6LJFP4R.js → chunk-FANQVQ3D.js} +2 -2
  30. package/dist/{chunk-AW3EHF3L.js → chunk-FC4IGH5Y.js} +1 -1
  31. package/dist/{chunk-23LZLBJK.js → chunk-G6MHJDVT.js} +6 -2
  32. package/dist/{chunk-JANNRGJC.js → chunk-G6PBI2BU.js} +1 -1
  33. package/dist/chunk-H5PTKYXL.js +101 -0
  34. package/dist/{chunk-LWFTHJIE.js → chunk-HHBD4SQ6.js} +1 -1
  35. package/dist/{chunk-JC5UNIVM.js → chunk-HS6FIOLP.js} +2 -0
  36. package/dist/chunk-HTYMW5CH.js +228 -0
  37. package/dist/chunk-IHE6KVC3.js +13 -0
  38. package/dist/{chunk-QHMWA3SZ.js → chunk-JCMUVZIK.js} +1 -1
  39. package/dist/{chunk-V373TRJE.js → chunk-JSM6JFZR.js} +198 -125
  40. package/dist/{chunk-LDDCNPHD.js → chunk-K5NJBTE2.js} +1 -1
  41. package/dist/chunk-KR6JF3BF.js +14 -0
  42. package/dist/{chunk-4ISR76DH.js → chunk-KSTV7J2U.js} +9 -3
  43. package/dist/chunk-LBEXLN6I.js +91 -0
  44. package/dist/{chunk-BLRW2UU6.js → chunk-LF5AGJA4.js} +2 -2
  45. package/dist/{chunk-UBBKSYBY.js → chunk-LHZXYFBU.js} +30 -1
  46. package/dist/{chunk-BIA32MQ5.js → chunk-MBX25B6D.js} +1 -1
  47. package/dist/{chunk-R2ZV233X.js → chunk-MYY2C5MB.js} +3 -3
  48. package/dist/{chunk-J77CKYV3.js → chunk-OFCSP6MT.js} +1 -1
  49. package/dist/{chunk-2INMRR3Q.js → chunk-ONTGZR5T.js} +326 -149
  50. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  51. package/dist/{chunk-U3BABVJF.js → chunk-PKX6TGRE.js} +2 -2
  52. package/dist/{chunk-PJMG5XV3.js → chunk-PQZ4LM4Z.js} +1 -1
  53. package/dist/chunk-PXVNAY7T.js +84 -0
  54. package/dist/chunk-QH36K2PU.js +158 -0
  55. package/dist/{chunk-R747YKS7.js → chunk-QZSWN2KT.js} +1 -1
  56. package/dist/{chunk-CHUALUIX.js → chunk-RYLXAYIQ.js} +1 -1
  57. package/dist/{chunk-GFZ7PITJ.js → chunk-S3YUXDSC.js} +6 -1
  58. package/dist/{chunk-PZCE7DJM.js → chunk-T6QIF5XR.js} +31 -1
  59. package/dist/{chunk-CBEKVAX4.js → chunk-TULQ4FFX.js} +41 -13
  60. package/dist/{chunk-S56D6B6C.js → chunk-UUBJCNQU.js} +12 -3
  61. package/dist/{chunk-5N2GW72F.js → chunk-UZXG6QSY.js} +2 -1
  62. package/dist/{chunk-4XTNPNQB.js → chunk-VPYBK2MG.js} +1 -1
  63. package/dist/{chunk-3HX5VLWP.js → chunk-VRV3NNTA.js} +2 -2
  64. package/dist/{chunk-HZA7OE7N.js → chunk-XCFC7LGF.js} +2 -1
  65. package/dist/{chunk-BL3VELKP.js → chunk-Y5VVP73C.js} +1 -1
  66. package/dist/{chunk-PI4LLJG4.js → chunk-YD2VAHA6.js} +2 -2
  67. package/dist/{chunk-UIRMA5KM.js → chunk-YDGMBMYI.js} +1 -1
  68. package/dist/{chunk-NWDF5HG3.js → chunk-YKL6SHRJ.js} +1 -1
  69. package/dist/{chunk-6X4KUDMU.js → chunk-ZCTUYGNV.js} +7 -2
  70. package/dist/chunk-ZUQW5VBZ.js +89 -0
  71. package/dist/class/class.codec.js +7 -7
  72. package/dist/class/class.parser.js +6 -6
  73. package/dist/class/class.schema.d.ts +18 -0
  74. package/dist/class/class.schema.js +5 -5
  75. package/dist/class/class.standard-array.d.ts +16 -0
  76. package/dist/class/class.standard-array.js +20 -0
  77. package/dist/class/class.types.d.ts +3 -0
  78. package/dist/dnd/amount-text.d.ts +38 -0
  79. package/dist/dnd/amount-text.js +13 -0
  80. package/dist/dnd/resource-words.d.ts +4 -0
  81. package/dist/dnd/resource-words.js +30 -0
  82. package/dist/entities/base-item-resolver.js +2 -2
  83. package/dist/entities/slug.d.ts +18 -8
  84. package/dist/entities/slug.js +3 -1
  85. package/dist/feat/feat.codec.js +5 -5
  86. package/dist/feat/feat.parser.js +4 -4
  87. package/dist/feat/feat.schema.d.ts +41 -3
  88. package/dist/feat/feat.schema.js +3 -3
  89. package/dist/index.js +7513 -4177
  90. package/dist/item/item.actions-map.d.ts +40 -5
  91. package/dist/item/item.actions-map.js +5 -84
  92. package/dist/item/item.attunement.js +3 -9
  93. package/dist/item/item.bonuses.js +5 -2
  94. package/dist/item/item.codec.js +3 -3
  95. package/dist/item/item.conditions.js +4 -1
  96. package/dist/item/item.conditions.types.d.ts +4 -0
  97. package/dist/item/item.cost.d.ts +3 -0
  98. package/dist/item/item.cost.js +29 -0
  99. package/dist/item/item.icon.d.ts +15 -0
  100. package/dist/item/item.icon.js +211 -0
  101. package/dist/item/item.parser.js +2 -2
  102. package/dist/item/item.schema.d.ts +16 -1
  103. package/dist/item/item.schema.js +3 -1
  104. package/dist/item/item.types.d.ts +9 -2
  105. package/dist/monster/monster.parser.js +5 -5
  106. package/dist/monster/monster.schema.js +4 -4
  107. package/dist/optional-feature/optional-feature.codec.js +5 -4
  108. package/dist/optional-feature/optional-feature.parser.js +4 -3
  109. package/dist/optional-feature/optional-feature.schema.d.ts +255 -3
  110. package/dist/optional-feature/optional-feature.schema.js +7 -2
  111. package/dist/pc/pc.additional-spells.js +3 -3
  112. package/dist/pc/pc.amount-bindings.d.ts +32 -0
  113. package/dist/pc/pc.amount-bindings.js +23 -0
  114. package/dist/pc/pc.asi-flatten.d.ts +62 -1
  115. package/dist/pc/pc.asi-flatten.js +16 -0
  116. package/dist/pc/pc.conditions.js +1 -1
  117. package/dist/pc/pc.containment.d.ts +45 -0
  118. package/dist/pc/pc.containment.js +24 -0
  119. package/dist/pc/pc.decision-engine.d.ts +24 -3
  120. package/dist/pc/pc.decision-engine.js +26 -16
  121. package/dist/pc/pc.entry-overrides.d.ts +79 -0
  122. package/dist/pc/pc.entry-overrides.js +40 -0
  123. package/dist/pc/pc.equipment.d.ts +2 -2
  124. package/dist/pc/pc.equipment.js +19 -7
  125. package/dist/pc/pc.feature-effects.d.ts +12 -0
  126. package/dist/pc/pc.feature-effects.js +2 -2
  127. package/dist/pc/pc.infusions.d.ts +228 -0
  128. package/dist/pc/pc.infusions.js +64 -0
  129. package/dist/pc/pc.member-choices.d.ts +90 -0
  130. package/dist/pc/pc.member-choices.js +32 -0
  131. package/dist/pc/pc.parser.js +18 -1
  132. package/dist/pc/pc.pools.d.ts +8 -1
  133. package/dist/pc/pc.pools.js +13 -3
  134. package/dist/pc/pc.proficiencies.js +23 -15
  135. package/dist/pc/pc.proficiency-query.js +6 -0
  136. package/dist/pc/pc.recalc.d.ts +39 -36
  137. package/dist/pc/pc.recalc.js +115 -70
  138. package/dist/pc/pc.resolver.d.ts +2 -1
  139. package/dist/pc/pc.resolver.js +22 -14
  140. package/dist/pc/pc.resource-seed.js +1 -1
  141. package/dist/pc/pc.resources.d.ts +6 -1
  142. package/dist/pc/pc.resources.js +3 -1
  143. package/dist/pc/pc.rest.d.ts +21 -1
  144. package/dist/pc/pc.rest.js +52 -19
  145. package/dist/pc/pc.schema.d.ts +43 -2
  146. package/dist/pc/pc.schema.js +1 -1
  147. package/dist/pc/pc.slotting.d.ts +36 -3
  148. package/dist/pc/pc.slotting.js +13 -5
  149. package/dist/pc/pc.tool-picks.d.ts +51 -0
  150. package/dist/pc/pc.tool-picks.js +18 -0
  151. package/dist/pc/pc.types.d.ts +115 -14
  152. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  153. package/dist/pc/pool-layout.d.ts +8 -0
  154. package/dist/pc/pool-layout.js +5 -3
  155. package/dist/pc/resource-equivalents.d.ts +44 -0
  156. package/dist/pc/spell-source.js +4 -9
  157. package/dist/race/race.codec.js +6 -6
  158. package/dist/race/race.parser.js +5 -5
  159. package/dist/race/race.schema.js +4 -4
  160. package/dist/schemas/attack-schema.d.ts +1 -1
  161. package/dist/schemas/choice-schema.js +2 -2
  162. package/dist/schemas/feature-effect-schema.d.ts +41 -3
  163. package/dist/schemas/feature-effect-schema.js +1 -1
  164. package/dist/schemas/feature-schema.js +3 -3
  165. package/dist/schemas/selection-pool-schema.d.ts +10 -0
  166. package/dist/schemas/selection-pool-schema.js +1 -1
  167. package/dist/spell/spell.codec.js +2 -2
  168. package/dist/spell/spell.parser.d.ts +6 -1
  169. package/dist/spell/spell.parser.js +5 -3
  170. package/dist/spell/spell.types.d.ts +10 -0
  171. package/dist/srd/retired-items.d.ts +14 -0
  172. package/dist/srd/retired-items.js +11 -0
  173. package/dist/subclass/subclass.codec.js +7 -7
  174. package/dist/subclass/subclass.parser.js +6 -6
  175. package/dist/subclass/subclass.schema.d.ts +10 -0
  176. package/dist/subclass/subclass.schema.js +5 -5
  177. package/dist/types/choice.d.ts +13 -0
  178. package/dist/types/equipment-grant.d.ts +18 -1
  179. package/dist/types/equipment-grant.js +12 -0
  180. package/dist/types/feature-effect.d.ts +12 -1
  181. package/dist/types/optional-feature.types.d.ts +84 -1
  182. package/dist/types/selection-pool.d.ts +18 -3
  183. package/dist/weapon/weapon.classify.d.ts +10 -8
  184. package/dist/weapon/weapon.classify.js +2 -2
  185. package/dist/weapon/weapon.codec.js +3 -3
  186. package/dist/weapon/weapon.parser.js +2 -2
  187. package/dist/weapon/weapon.schema.d.ts +1 -0
  188. package/dist/weapon/weapon.schema.js +1 -1
  189. package/dist/weapon/weapon.types.d.ts +2 -0
  190. package/package.json +1 -1
  191. package/dist/chunk-O4SRD3J5.js +0 -165
  192. package/dist/chunk-WXWPK3LI.js +0 -55
@@ -1,7 +1,36 @@
1
+ import {
2
+ equipmentCategoryKey,
3
+ isCategoryGrant
4
+ } from "./chunk-2LULMQTF.js";
5
+ import {
6
+ TOOL_CATEGORIES,
7
+ backgroundToolPicks,
8
+ categoryWords,
9
+ classToolPicks,
10
+ expandToolPool,
11
+ toolCategoriesOf
12
+ } from "./chunk-H5PTKYXL.js";
13
+ import {
14
+ assembleEffectFeatures,
15
+ collectProficiencyEffectGrants
16
+ } from "./chunk-TULQ4FFX.js";
17
+ import {
18
+ ALL_LANGUAGES,
19
+ ALL_SKILL_SLUGS,
20
+ ALL_TOOLS
21
+ } from "./chunk-KU3YLF6D.js";
22
+ import {
23
+ featOfSite,
24
+ featPickSites,
25
+ isTargetChoice,
26
+ memberChoices,
27
+ poolMemberSites,
28
+ poolMemberSitesOf
29
+ } from "./chunk-HTYMW5CH.js";
1
30
  import {
2
31
  resolveFeatureResources,
3
32
  resolveResourceIndex
4
- } from "./chunk-4ISR76DH.js";
33
+ } from "./chunk-KSTV7J2U.js";
5
34
  import {
6
35
  attributeUnclassedSpell,
7
36
  effectiveSpellcastingAbility,
@@ -9,9 +38,22 @@ import {
9
38
  resolveSpellcasting
10
39
  } from "./chunk-DPUJDOKV.js";
11
40
  import {
41
+ convertibleLegacyAsi,
42
+ flattenAsiOrFeat,
43
+ resolveLegacyAsiFeat
44
+ } from "./chunk-LBEXLN6I.js";
45
+ import {
46
+ humanizeProficiency,
47
+ proficiencyLabel,
48
+ toProfSlug
49
+ } from "./chunk-RLGQ5PZZ.js";
50
+ import {
51
+ bindFeatureConsumers,
52
+ buildInfusionContext,
12
53
  resolveAllPools,
54
+ resolveOptionRows,
13
55
  strandedPicks
14
- } from "./chunk-O4SRD3J5.js";
56
+ } from "./chunk-3VZZDKRA.js";
15
57
  import {
16
58
  countColumnsFor,
17
59
  readTableColumn
@@ -21,42 +63,28 @@ import {
21
63
  } from "./chunk-YHYTW5QA.js";
22
64
  import {
23
65
  collectAdditionalSpells
24
- } from "./chunk-3HX5VLWP.js";
66
+ } from "./chunk-VRV3NNTA.js";
25
67
  import {
26
68
  isItemEntity,
27
69
  resolveEntityForEntry
28
- } from "./chunk-UBBKSYBY.js";
29
- import {
30
- assembleEffectFeatures,
31
- collectProficiencyEffectGrants
32
- } from "./chunk-CBEKVAX4.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";
70
+ } from "./chunk-LHZXYFBU.js";
50
71
  import {
51
72
  FEATURE_TYPE_FOR_CATEGORY,
52
73
  featProgressionCategory
53
74
  } from "./chunk-YBLDE5CG.js";
75
+ import {
76
+ warnOnce
77
+ } from "./chunk-LVQLWRCU.js";
54
78
  import {
55
79
  isValidMaxFormula
56
80
  } from "./chunk-EVMUJYUD.js";
81
+ import {
82
+ bareEntitySlug,
83
+ wikilinkTailSlug
84
+ } from "./chunk-ZCTUYGNV.js";
57
85
  import {
58
86
  mirrorSpellShapes
59
- } from "./chunk-XY6X2APM.js";
87
+ } from "./chunk-7Q2VPKCD.js";
60
88
  import {
61
89
  ABILITY_KEYS
62
90
  } from "./chunk-WELIKLZF.js";
@@ -80,6 +108,7 @@ function collectProficiencyGrants(resolved) {
80
108
  for (const w of (_f = (_e = prof.weapons) == null ? void 0 : _e.fixed) != null ? _f : []) classWeaponFixed.push({ value: w, source });
81
109
  for (const w of (_h = (_g = prof.weapons) == null ? void 0 : _g.categories) != null ? _h : []) classWeaponCategories.push({ value: w, source });
82
110
  for (const t of (_j = (_i = prof.tools) == null ? void 0 : _i.fixed) != null ? _j : []) classToolFixed.push({ value: t, source });
111
+ for (const t of classToolPicks(c.entity).grants) classToolFixed.push({ value: t, source });
83
112
  }
84
113
  const raceSource = (_l = (_k = resolved.race) == null ? void 0 : _k.name) != null ? _l : UNKNOWN_SOURCE;
85
114
  const raceLangFixed = ((_o = (_n = (_m = resolved.race) == null ? void 0 : _m.languages) == null ? void 0 : _n.fixed) != null ? _o : []).map(
@@ -95,6 +124,7 @@ function collectProficiencyGrants(resolved) {
95
124
  for (const t of entry.items) bgToolFixed.push({ value: t, source });
96
125
  }
97
126
  }
127
+ for (const t of backgroundToolPicks(bg).grants) bgToolFixed.push({ value: t, source });
98
128
  for (const entry of (_r = bg.language_proficiencies) != null ? _r : []) {
99
129
  if (entry.kind === "fixed") {
100
130
  for (const l of entry.languages) bgLangFixed.push({ value: l, source });
@@ -164,25 +194,18 @@ function collectProficiencyGrants(resolved) {
164
194
  };
165
195
  }
166
196
 
167
- // src/pc/pc.asi-flatten.ts
168
- function flattenAsiOrFeat(choice) {
169
- var _a;
170
- if (choice.kind !== "select-inline" || choice.id !== "asi-or-feat") return choice;
171
- const featBranches = choice.options.filter((o) => {
172
- var _a2;
173
- return ((_a2 = o.choices) != null ? _a2 : []).some((c) => c.kind === "select-entity" && c.entity_type === "feat");
174
- });
175
- if (featBranches.length !== 1) return choice;
176
- const inner = (_a = featBranches[0].choices) != null ? _a : [];
177
- if (inner.length !== 1) return choice;
178
- const only = inner[0];
179
- if (only.kind !== "select-entity" || only.entity_type !== "feat") return choice;
180
- return only;
181
- }
182
-
183
197
  // src/pc/pc.decision-engine.ts
184
198
  var warnedDegradedEquipment = /* @__PURE__ */ new Set();
185
199
  var warnedAmbiguousBare = /* @__PURE__ */ new Set();
200
+ var foldedTypeMemo = /* @__PURE__ */ new Map();
201
+ function foldedType(s) {
202
+ let v = foldedTypeMemo.get(s);
203
+ if (v === void 0) {
204
+ v = categoryWords(s).join(" ");
205
+ foldedTypeMemo.set(s, v);
206
+ }
207
+ return v;
208
+ }
186
209
  function matchesFilter(e, where, ownerBare) {
187
210
  var _a, _b, _c, _d, _e;
188
211
  const d = e.data;
@@ -191,6 +214,7 @@ function matchesFilter(e, where, ownerBare) {
191
214
  if (where.list && !((_a = d.classes) != null ? _a : []).includes(where.list)) return false;
192
215
  if (where.level !== void 0 && ((_b = d.level) != null ? _b : 0) !== where.level) return false;
193
216
  if (where.edition && d.edition !== where.edition) return false;
217
+ if (where.ritual && d.ritual !== true) return false;
194
218
  if (where.weapon_category) {
195
219
  const cat = ((_c = d.category) != null ? _c : "").toLowerCase();
196
220
  if (!cat.startsWith(`${where.weapon_category}-`) && cat !== where.weapon_category) return false;
@@ -198,6 +222,14 @@ function matchesFilter(e, where, ownerBare) {
198
222
  if (where.armor_category) {
199
223
  if (((_d = d.category) != null ? _d : "").toLowerCase() !== where.armor_category) return false;
200
224
  }
225
+ if (where.item_type) {
226
+ const t = foldedType(typeof d.type === "string" ? d.type : "");
227
+ if (!t || !where.item_type.some((x) => foldedType(x) === t)) return false;
228
+ }
229
+ if (where.mundane) {
230
+ const r = typeof d.rarity === "string" ? d.rarity.trim().toLowerCase() : "";
231
+ if (r && r !== "none" && r !== "mundane") return false;
232
+ }
201
233
  if (where.parent_class === "self") {
202
234
  if (!d.parent_class || wikilinkTailSlug(d.parent_class) !== ownerBare) return false;
203
235
  }
@@ -208,17 +240,94 @@ function matchesFilter(e, where, ownerBare) {
208
240
  return true;
209
241
  }
210
242
  var __matchesFilterForTest = matchesFilter;
243
+ function parseEquipmentCategory(category) {
244
+ const tools = toolCategoriesOf(category);
245
+ if (tools) return { kind: "tool", tools };
246
+ const words = categoryWords(category).filter((w) => w !== "any");
247
+ const has = (w) => words.includes(w);
248
+ const only = (allowed) => words.every((w) => allowed.includes(w));
249
+ if (words.length === 1 && has("shield")) return { kind: "shield" };
250
+ if (has("armor") && only(["armor", "light", "medium", "heavy"]) && words.length <= 2) {
251
+ const armor = ["light", "medium", "heavy"].find((k) => has(k));
252
+ return { kind: "armor", armor };
253
+ }
254
+ if (has("weapon") && only(["weapon", "simple", "martial", "melee", "ranged"])) {
255
+ const klass = ["simple", "martial"].filter((k) => has(k));
256
+ const range = ["melee", "ranged"].filter((k) => has(k));
257
+ if (klass.length === 1 && range.length <= 1 && words.length === 2 + range.length) {
258
+ return { kind: "weapon", weapon: klass[0], range: range[0] };
259
+ }
260
+ }
261
+ return void 0;
262
+ }
211
263
  function categoryToEntitySelect(category, id) {
212
- const c = category.toLowerCase();
213
- if (c === "shield") {
214
- return { kind: "select-entity", id, label: "Choose a shield", count: 1, entity_type: "armor", where: { armor_category: "shield" } };
264
+ const spec = parseEquipmentCategory(category);
265
+ const pick = { kind: "select-entity", id, count: 1 };
266
+ switch (spec == null ? void 0 : spec.kind) {
267
+ case "shield":
268
+ return { ...pick, label: "Choose a shield", entity_type: "armor", where: { armor_category: "shield" } };
269
+ case "armor":
270
+ return spec.armor ? { ...pick, label: `Choose ${spec.armor} armor`, entity_type: "armor", where: { armor_category: spec.armor } } : { ...pick, label: "Choose armor", entity_type: "armor" };
271
+ case "weapon":
272
+ return {
273
+ ...pick,
274
+ label: `Choose a ${spec.weapon}${spec.range ? ` ${spec.range}` : ""} weapon`,
275
+ entity_type: "weapon",
276
+ where: { weapon_category: spec.weapon }
277
+ };
278
+ case "tool":
279
+ return {
280
+ ...pick,
281
+ label: `Choose ${spec.tools.map((t) => TOOL_CATEGORIES[t].label).join(" or ")}`,
282
+ entity_type: "item",
283
+ where: { item_type: spec.tools.map((t) => TOOL_CATEGORIES[t].itemType), mundane: true }
284
+ };
285
+ default:
286
+ warnOnce(
287
+ `equipment-category:${category}`,
288
+ `Archivist: starting-equipment category "${category}" is not a known item category; its pick offers no options.`
289
+ );
290
+ return { ...pick, label: `Choose a ${category.replace(/[-_]+/g, " ")}`, entity_type: "item", from: [] };
215
291
  }
216
- if (c.includes("armor")) {
217
- const klass = c.includes("light") ? "light" : c.includes("medium") ? "medium" : c.includes("heavy") ? "heavy" : null;
218
- 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" };
292
+ }
293
+ var __categoryToEntitySelectForTest = categoryToEntitySelect;
294
+ function equipmentDecisions(entries, ownerSlug) {
295
+ const out = [];
296
+ let degraded = false;
297
+ (entries != null ? entries : []).forEach((eq, i) => {
298
+ var _a;
299
+ if ((eq == null ? void 0 : eq.kind) !== "choice") return;
300
+ const options = (_a = eq.options) != null ? _a : [];
301
+ out.push({
302
+ kind: "select-inline",
303
+ id: `equipment-${i}`,
304
+ label: "Starting Equipment",
305
+ count: 1,
306
+ options: options.map((opt, j) => {
307
+ var _a2;
308
+ const label = typeof opt === "string" ? opt : (_a2 = opt == null ? void 0 : opt.label) != null ? _a2 : "";
309
+ const wellFormed = !!opt && typeof opt === "object" && Array.isArray(opt.grants);
310
+ if (!wellFormed) degraded = true;
311
+ const grants = wellFormed ? opt.grants : [];
312
+ return {
313
+ value: `option-${j}`,
314
+ label,
315
+ choices: grants.flatMap((g, k) => isCategoryGrant(g) ? [categoryToEntitySelect(g.category, equipmentCategoryKey(i, j, k))] : [])
316
+ };
317
+ })
318
+ });
319
+ });
320
+ if (degraded && !warnedDegradedEquipment.has(ownerSlug)) {
321
+ warnedDegradedEquipment.add(ownerSlug);
322
+ console.warn(
323
+ `[archivist] Starting equipment for "${ownerSlug}" is in an outdated/unstructured format; its nested picks and seeding are skipped. Re-sync the compendium (delete _compendium.md and reload) to fix.`
324
+ );
219
325
  }
220
- const wc = c.includes("martial") ? "martial" : "simple";
221
- return { kind: "select-entity", id, label: `Choose a ${c.replace(/-/g, " ")}`, count: 1, entity_type: "weapon", where: { weapon_category: wc } };
326
+ return out;
327
+ }
328
+ function authoredProficiencyPool(choice) {
329
+ if (!choice.from) return void 0;
330
+ return choice.domain === "tool" ? expandToolPool(choice.from) : choice.from;
222
331
  }
223
332
  function enumerateOptions(choice, ctx, ownerBare) {
224
333
  var _a, _b;
@@ -260,7 +369,7 @@ function enumerateOptions(choice, ctx, ownerBare) {
260
369
  return filtered.map((e) => ({ value: e.slug, label: e.name, entity: e }));
261
370
  }
262
371
  case "select-proficiency": {
263
- const pool = (_a = choice.from) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : choice.domain === "language" ? [...ALL_LANGUAGES] : choice.domain === "tool" ? [...ALL_TOOLS] : [];
372
+ const pool = (_a = authoredProficiencyPool(choice)) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : choice.domain === "language" ? [...ALL_LANGUAGES] : choice.domain === "tool" ? [...ALL_TOOLS] : [];
264
373
  return pool.map((v) => ({ value: v, label: humanizeProficiency(v) }));
265
374
  }
266
375
  case "ability-points": {
@@ -284,6 +393,12 @@ function requiredCount(choice) {
284
393
  if (choice.kind === "select-proficiency") return choice.count;
285
394
  return (_a = choice.count) != null ? _a : 1;
286
395
  }
396
+ function capAtCount(choice, value) {
397
+ const folds = choice.kind === "select-proficiency" || choice.kind === "select-entity" && choice.entity_type === "weapon" && choice.id !== "weapon-mastery";
398
+ if (!folds || !Array.isArray(value)) return value;
399
+ const n = requiredCount(choice);
400
+ return value.length > n ? value.slice(0, n) : value;
401
+ }
287
402
  function statusOf(choice, selected, satisfied) {
288
403
  const n = selectionCount(choice, selected);
289
404
  const need = requiredCount(choice);
@@ -317,7 +432,7 @@ function buildItem(inputChoice, source, level, featureName, readValue, ctx, owne
317
432
  const keyPrefix = (_a = opts == null ? void 0 : opts.keyPrefix) != null ? _a : "";
318
433
  const expandFeatChildren = (_b = opts == null ? void 0 : opts.expandFeatChildren) != null ? _b : true;
319
434
  const key = keyPrefix + choice.id;
320
- const raw = readValue(key);
435
+ const raw = capAtCount(choice, readValue(key));
321
436
  const preExclusionOptions = enumerateOptions(choice, ctx, ownerBare);
322
437
  let options = preExclusionOptions;
323
438
  const mine = new Set(
@@ -422,8 +537,13 @@ function visitProficiencyChoices(resolved, visit) {
422
537
  const originRead = (ns) => (id) => oc[`${ns}:${id}`];
423
538
  const stripRef = (ref) => ref.replace(/^\[\[/, "").replace(/\]\]$/, "");
424
539
  const featBySlug = new Map(((_b = resolved.feats) != null ? _b : []).map((f) => [f.slug, f]));
425
- resolved.classes.forEach((c, i) => {
540
+ const memberSites = poolMemberSitesOf(resolved);
541
+ const featSites = featPickSites(resolved.classes, memberSites, (ci) => {
426
542
  var _a2, _b2, _c2, _d;
543
+ return (_d = (_c2 = (_b2 = (_a2 = resolved.classes) == null ? void 0 : _a2[ci]) == null ? void 0 : _b2.entity) == null ? void 0 : _c2.slug) != null ? _d : null;
544
+ });
545
+ resolved.classes.forEach((c, i) => {
546
+ var _a2, _b2, _c2, _d, _e;
427
547
  if (!c.entity) return;
428
548
  const entity = c.entity;
429
549
  const readAt = (lvl) => (id) => {
@@ -440,6 +560,7 @@ function visitProficiencyChoices(resolved, visit) {
440
560
  }, readAt(1)("skills"));
441
561
  }
442
562
  if (i === 0) walk(entity.choices, readAt(1));
563
+ if (i === 0) walk(classToolPicks(entity).decisions, readAt(1));
443
564
  for (const rf of resolved.features) {
444
565
  if (rf.source.kind !== "class" && rf.source.kind !== "subclass") continue;
445
566
  const belongs = rf.source.kind === "class" ? rf.source.slug === entity.slug : c.subclass != null && rf.source.slug === c.subclass.slug;
@@ -447,14 +568,13 @@ function visitProficiencyChoices(resolved, visit) {
447
568
  for (const copy of (_c2 = rf.foldedFrom) != null ? _c2 : []) walk(copy.choices, readAt(copy.level));
448
569
  walk(rf.feature.choices, readAt(rf.source.level));
449
570
  }
450
- for (const atLevel of Object.values((_d = c.choices) != null ? _d : {})) {
451
- const block = atLevel;
452
- const featRef = block == null ? void 0 : block.feat;
453
- if (!block || typeof featRef !== "string") continue;
454
- const feat = featBySlug.get(stripRef(featRef));
571
+ for (const site of featSites) {
572
+ if (site.classIndex !== i) continue;
573
+ const feat = (_e = featBySlug.get(stripRef(site.slug))) != null ? _e : featOfSite(site, (_d = resolved.feats) != null ? _d : []);
455
574
  if (!feat) continue;
456
- walk(feat.choices, (id) => block[`feat:${id}`]);
575
+ walk(feat.choices, site.read);
457
576
  }
577
+ for (const site of memberSites) if (site.classIndex === i) walk(site.choices, site.read);
458
578
  });
459
579
  if (resolved.race) {
460
580
  walk(resolved.race.choices, originRead("race"));
@@ -462,6 +582,7 @@ function visitProficiencyChoices(resolved, visit) {
462
582
  }
463
583
  if (resolved.background) {
464
584
  walk(resolved.background.choices, originRead("background"));
585
+ walk(backgroundToolPicks(resolved.background).decisions, originRead("background"));
465
586
  if (resolved.background.feature) {
466
587
  const feature = resolved.background.feature;
467
588
  walk(feature.choices, originRead("background"));
@@ -476,7 +597,8 @@ var filterToPool = (vals, pool) => pool ? vals.map((v) => matchPool(v, pool)).fi
476
597
  function collectChosenProficiencies(resolved) {
477
598
  const out = { skills: [], expertise: [], languages: [], tools: [], weapons: [], saves: [] };
478
599
  const isAbility = (v) => ABILITY_KEYS.includes(v);
479
- visitProficiencyChoices(resolved, (choice, selected) => {
600
+ visitProficiencyChoices(resolved, (choice, stored) => {
601
+ const selected = capAtCount(choice, stored);
480
602
  const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
481
603
  if (choice.kind === "select-entity" && choice.entity_type === "weapon") {
482
604
  if (choice.id === "weapon-mastery") return;
@@ -487,7 +609,7 @@ function collectChosenProficiencies(resolved) {
487
609
  return;
488
610
  }
489
611
  if (choice.kind !== "select-proficiency") return;
490
- const valid = filterToPool(vals, choice.from);
612
+ const valid = filterToPool(vals, authoredProficiencyPool(choice));
491
613
  if (choice.domain === "save") {
492
614
  for (const v of valid) if (isAbility(v) && !out.saves.includes(v)) out.saves.push(v);
493
615
  return;
@@ -589,7 +711,7 @@ function collectChosenAbilityPoints(resolved) {
589
711
  return out;
590
712
  }
591
713
  function buildDecisionLedger(resolved, ctx) {
592
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
714
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
593
715
  const eff = computeEffectiveProficiencies(resolved);
594
716
  const chosen = collectChosenProficiencies(resolved);
595
717
  const { features, activeBuffs } = assembleEffectFeatures(resolved);
@@ -614,7 +736,7 @@ function buildDecisionLedger(resolved, ctx) {
614
736
  };
615
737
  const classes = [];
616
738
  resolved.classes.forEach((c, classIndex) => {
617
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n, _o, _p;
739
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2, _o;
618
740
  if (!c.entity) return;
619
741
  const entity = c.entity;
620
742
  const ownerBare = bareEntitySlug(entity.slug);
@@ -629,6 +751,19 @@ function buildDecisionLedger(resolved, ctx) {
629
751
  var _a3;
630
752
  return (_a3 = c.choices[lvl]) == null ? void 0 : _a3[id];
631
753
  };
754
+ let legacyAsiFeat;
755
+ const legacyAsiReader = (choice, lvl) => {
756
+ const alloc = convertibleLegacyAsi(c.choices[lvl]);
757
+ if (!alloc) return null;
758
+ const flat = flattenAsiOrFeat(choice);
759
+ if (flat.kind !== "select-entity" || flat.entity_type !== "feat" || flat.from || flat.where) return null;
760
+ if (legacyAsiFeat === void 0) legacyAsiFeat = resolveLegacyAsiFeat(ctx.registry, entity);
761
+ const asi = legacyAsiFeat;
762
+ if (!asi) return null;
763
+ const raw = readAt(lvl);
764
+ const childKey = `feat:${asi.childId}`;
765
+ return (id) => id === "feat" ? asi.slug : id === childKey ? alloc : raw(id);
766
+ };
632
767
  const featProgressions = [...(_a2 = entity.feat_progression) != null ? _a2 : [], ...(_c2 = (_b2 = c.subclass) == null ? void 0 : _b2.feat_progression) != null ? _c2 : []];
633
768
  const suppressedFeatureTypes = /* @__PURE__ */ new Set();
634
769
  for (const decl of [...(_d2 = entity.selection_pools) != null ? _d2 : [], ...(_f2 = (_e2 = c.subclass) == null ? void 0 : _e2.selection_pools) != null ? _f2 : []]) {
@@ -661,7 +796,7 @@ function buildDecisionLedger(resolved, ctx) {
661
796
  ));
662
797
  }
663
798
  if (classIndex === 0) {
664
- for (const ch of (_i2 = entity.choices) != null ? _i2 : []) {
799
+ for (const ch of [...(_i2 = entity.choices) != null ? _i2 : [], ...classToolPicks(entity).decisions]) {
665
800
  push(1, buildItem(
666
801
  ch,
667
802
  { kind: "class", slug: entity.slug, level: 1 },
@@ -675,36 +810,7 @@ function buildDecisionLedger(resolved, ctx) {
675
810
  }
676
811
  }
677
812
  if (classIndex === 0) {
678
- ((_j2 = entity.starting_equipment) != null ? _j2 : []).forEach((eq, i) => {
679
- var _a3;
680
- if (eq.kind !== "choice") return;
681
- const options = (_a3 = eq.options) != null ? _a3 : [];
682
- let degraded = false;
683
- const ch = {
684
- kind: "select-inline",
685
- id: `equipment-${i}`,
686
- label: "Starting Equipment",
687
- count: 1,
688
- options: options.map((opt, j) => {
689
- var _a4;
690
- const label = typeof opt === "string" ? opt : (_a4 = opt == null ? void 0 : opt.label) != null ? _a4 : "";
691
- const grants = opt && typeof opt === "object" && Array.isArray(opt.grants) ? opt.grants : [];
692
- if (typeof opt === "string" || !(opt && typeof opt === "object" && Array.isArray(opt.grants))) {
693
- degraded = true;
694
- }
695
- return {
696
- value: `option-${j}`,
697
- label,
698
- choices: grants.flatMap((g, k) => g && typeof g === "object" && "category" in g && g.category ? [categoryToEntitySelect(g.category, `equipment-${i}-opt-${j}-cat-${k}`)] : [])
699
- };
700
- })
701
- };
702
- if (degraded && !warnedDegradedEquipment.has(entity.slug)) {
703
- warnedDegradedEquipment.add(entity.slug);
704
- console.warn(
705
- `[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.`
706
- );
707
- }
813
+ for (const ch of equipmentDecisions(entity.starting_equipment, entity.slug)) {
708
814
  push(1, buildItem(
709
815
  ch,
710
816
  { kind: "class", slug: entity.slug, level: 1 },
@@ -715,7 +821,7 @@ function buildDecisionLedger(resolved, ctx) {
715
821
  ownerBare,
716
822
  effective
717
823
  ));
718
- });
824
+ }
719
825
  }
720
826
  let sawAuthoredSubclass = false;
721
827
  const staged = [];
@@ -735,7 +841,7 @@ function buildDecisionLedger(resolved, ctx) {
735
841
  staged.push({ level: atLvl, item, rank: authoredRank(src.kind, atLvl, rf.feature), seq: staged.length });
736
842
  };
737
843
  const emitChoices = (list, atLvl, name, description) => {
738
- var _a3;
844
+ var _a3, _b3;
739
845
  for (const ch of list) {
740
846
  if (ch.kind === "select-entity" && ch.entity_type === "subclass") {
741
847
  sawAuthoredSubclass = true;
@@ -753,7 +859,7 @@ function buildDecisionLedger(resolved, ctx) {
753
859
  src,
754
860
  atLvl,
755
861
  name,
756
- readAt(atLvl),
862
+ (_b3 = legacyAsiReader(emitted, atLvl)) != null ? _b3 : readAt(atLvl),
757
863
  ctx,
758
864
  ownerBare,
759
865
  effective,
@@ -800,22 +906,22 @@ function buildDecisionLedger(resolved, ctx) {
800
906
  }
801
907
  emitChoices(list, copy.level, copy.name, copy.description);
802
908
  };
803
- for (const copy of (_k2 = rf.foldedFrom) != null ? _k2 : []) emitCopy({ ...copy, prose: copy.description });
909
+ for (const copy of (_j2 = rf.foldedFrom) != null ? _j2 : []) emitCopy({ ...copy, prose: copy.description });
804
910
  emitCopy({
805
911
  level: lvl,
806
912
  name: rf.feature.name,
807
913
  description: rf.feature.description,
808
914
  // The card prose: the description, else the entries joined. `||`, not `??`, so an EMPTY description
809
915
  // falls through to the entries the way this branch's own guard always did.
810
- prose: rf.feature.description || (((_l2 = rf.feature.entries) == null ? void 0 : _l2.length) ? rf.feature.entries.join("\n\n") : void 0),
916
+ prose: rf.feature.description || (((_k2 = rf.feature.entries) == null ? void 0 : _k2.length) ? rf.feature.entries.join("\n\n") : void 0),
811
917
  choices: rf.feature.choices
812
918
  });
813
919
  }
814
920
  staged.sort((a, b) => a.rank - b.rank || a.seq - b.seq);
815
921
  for (const s of staged) push(s.level, s.item);
816
- const subclassLevel = (_m2 = entity.subclass_level) != null ? _m2 : null;
922
+ const subclassLevel = (_l2 = entity.subclass_level) != null ? _l2 : null;
817
923
  if (subclassLevel != null && subclassLevel <= c.level && !sawAuthoredSubclass) {
818
- const featureName = (_n = entity.subclass_feature_name) != null ? _n : "Subclass";
924
+ const featureName = (_m2 = entity.subclass_feature_name) != null ? _m2 : "Subclass";
819
925
  const synthChoice = {
820
926
  kind: "select-entity",
821
927
  id: "subclass",
@@ -851,13 +957,46 @@ function buildDecisionLedger(resolved, ctx) {
851
957
  { kind: "class", slug: entity.slug, level: pool.anchorLevel },
852
958
  pool.anchorLevel,
853
959
  pool.label,
854
- () => pool.selected.map((e) => e.slug),
960
+ () => pool.selected.map((e) => e.key),
855
961
  ctx,
856
962
  ownerBare,
857
963
  effective
858
964
  );
859
965
  item.pool = { id: pool.id, anchorLevel: pool.anchorLevel };
966
+ const repeatables = pool.available.filter((e) => e.entity.repeatable === true);
967
+ if (repeatables.length > 0) {
968
+ item.repeatable = repeatables.map((e) => {
969
+ var _a3, _b3;
970
+ return {
971
+ noteSlug: e.slug,
972
+ itemChoice: (_a3 = e.entity.item_choice) != null ? _a3 : "learn",
973
+ rows: resolveOptionRows(e.entity, ctx.registry, { edition: resolved.definition.edition, isEntityVisible: (_b3 = ctx.isEntityVisible) == null ? void 0 : _b3.bind(ctx) })
974
+ };
975
+ });
976
+ }
860
977
  for (const o of item.options) if (strandedSlugs.has(o.value)) o.stranded = true;
978
+ const memberChildren = [];
979
+ for (const entry of pool.selected) {
980
+ for (const ch of memberChoices(entry.entity)) {
981
+ const child = buildItem(
982
+ ch,
983
+ { kind: "class", slug: entity.slug, level: pool.anchorLevel },
984
+ pool.anchorLevel,
985
+ entry.entity.name,
986
+ readAt(pool.anchorLevel),
987
+ ctx,
988
+ ownerBare,
989
+ effective,
990
+ { keyPrefix: `${pool.id}:${entry.key}:` }
991
+ );
992
+ child.parentLabel = entry.entity.name;
993
+ memberChildren.push(child);
994
+ }
995
+ }
996
+ if (memberChildren.length) {
997
+ item.children = memberChildren;
998
+ if (item.status === "resolved" && memberChildren.some((c2) => c2.status !== "resolved")) item.status = "partial";
999
+ }
861
1000
  push(pool.anchorLevel, item);
862
1001
  }
863
1002
  for (const entry of featProgressions) {
@@ -865,7 +1004,7 @@ function buildDecisionLedger(resolved, ctx) {
865
1004
  if (!category) {
866
1005
  warnOnce(
867
1006
  `feat-progression-category:${entity.slug}:${entry.name}`,
868
- `Archivist: feat_progression "${entry.name}" on "${entity.slug}" carries no category this engine maps (${((_o = entry.category) != null ? _o : []).join(", ")}); no decision is offered`
1007
+ `Archivist: feat_progression "${entry.name}" on "${entity.slug}" carries no category this engine maps (${((_n2 = entry.category) != null ? _n2 : []).join(", ")}); no decision is offered`
869
1008
  );
870
1009
  continue;
871
1010
  }
@@ -875,7 +1014,7 @@ function buildDecisionLedger(resolved, ctx) {
875
1014
  const lvl = Number.parseInt(key, 10);
876
1015
  if (!Number.isInteger(lvl) || String(lvl) !== key) continue;
877
1016
  if (lvl > c.level) continue;
878
- if (((_p = byLevel.get(lvl)) != null ? _p : []).some((i) => i.key === "feat")) {
1017
+ if (((_o = byLevel.get(lvl)) != null ? _o : []).some((i) => i.key === "feat")) {
879
1018
  warnOnce(
880
1019
  `feat-progression-collision:${entity.slug}:${lvl}`,
881
1020
  `Archivist: "${entry.name}" on "${entity.slug}" would add a second feat pick at level ${lvl}, where one already exists; skipping`
@@ -947,10 +1086,10 @@ function buildDecisionLedger(resolved, ctx) {
947
1086
  if (resolved.background) {
948
1087
  const bare = bareEntitySlug(resolved.background.slug);
949
1088
  pushOrigin(
950
- resolved.background.choices,
1089
+ [...(_l = resolved.background.choices) != null ? _l : [], ...backgroundToolPicks(resolved.background).decisions],
951
1090
  "background",
952
1091
  { kind: "background", slug: resolved.background.slug },
953
- (_l = resolved.background.name) != null ? _l : "Background",
1092
+ (_m = resolved.background.name) != null ? _m : "Background",
954
1093
  bare,
955
1094
  resolved.background.description
956
1095
  );
@@ -973,7 +1112,7 @@ function buildDecisionLedger(resolved, ctx) {
973
1112
  if (originFeat) {
974
1113
  const featSource = { kind: "feat", slug: originFeat.feat.slug };
975
1114
  const featBare = bareEntitySlug(originFeat.feat.slug);
976
- for (const ch of (_m = originFeat.feat.choices) != null ? _m : []) {
1115
+ for (const ch of (_n = originFeat.feat.choices) != null ? _n : []) {
977
1116
  origin.push(buildItem(
978
1117
  ch,
979
1118
  featSource,
@@ -988,6 +1127,18 @@ function buildDecisionLedger(resolved, ctx) {
988
1127
  }
989
1128
  }
990
1129
  }
1130
+ for (const ch of equipmentDecisions(resolved.background.equipment, resolved.background.slug)) {
1131
+ origin.push(buildItem(
1132
+ ch,
1133
+ { kind: "background", slug: resolved.background.slug },
1134
+ 0,
1135
+ "Starting Equipment",
1136
+ originRead("background"),
1137
+ ctx,
1138
+ bare,
1139
+ effective
1140
+ ));
1141
+ }
991
1142
  }
992
1143
  return { classes, origin };
993
1144
  }
@@ -1014,7 +1165,10 @@ function resolveOriginFeat(entities, originFeatRef, ownerSlug) {
1014
1165
  const sameCompendiumTail = (s) => prefix ? feats.find((f) => f.slug.startsWith(`${prefix}_feat_`) && f.slug.endsWith(`_${s}`)) : void 0;
1015
1166
  const anyTail = (s) => feats.find((f) => f.slug.endsWith(`_${s}`));
1016
1167
  const tiers = [
1017
- () => feats.find((f) => f.slug === slug),
1168
+ () => {
1169
+ var _a2;
1170
+ return (_a2 = feats.find((f) => f.slug === inner)) != null ? _a2 : feats.find((f) => f.slug === slug);
1171
+ },
1018
1172
  // 1 exact slug
1019
1173
  () => byPath(inner),
1020
1174
  // 2 PATH
@@ -1050,7 +1204,7 @@ var PCResolver = class {
1050
1204
  return this.entities;
1051
1205
  }
1052
1206
  resolve(character) {
1053
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
1207
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1054
1208
  const warnings = [];
1055
1209
  const lookup = (rawRef, type) => {
1056
1210
  const slug = stripSlug(rawRef);
@@ -1071,14 +1225,19 @@ var PCResolver = class {
1071
1225
  subclass: lookup(c.subclass, "subclass"),
1072
1226
  choices: c.choices
1073
1227
  }));
1228
+ const memberSites = poolMemberSites(classes, this.entities);
1229
+ const classSlugOf = (i) => {
1230
+ var _a2, _b2, _c2, _d2, _e2;
1231
+ return (_e2 = (_b2 = (_a2 = classes[i]) == null ? void 0 : _a2.entity) == null ? void 0 : _b2.slug) != null ? _e2 : stripSlug((_d2 = (_c2 = character.class[i]) == null ? void 0 : _c2.name) != null ? _d2 : null);
1232
+ };
1233
+ const featSites = featPickSites(character.class, memberSites, classSlugOf);
1074
1234
  const feats = [];
1075
1235
  const featVia = /* @__PURE__ */ new Map();
1076
- for (const pick of collectFeatPicks(character)) {
1077
- const f = lookup(`[[${pick.slug}]]`, "feat");
1236
+ for (const site of featSites) {
1237
+ const f = lookup(`[[${site.slug}]]`, "feat");
1078
1238
  if (!f) continue;
1079
- feats.push(f);
1080
- 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);
1081
- if (classSlug && pick.level !== null && !featVia.has(f.slug)) featVia.set(f.slug, { kind: "class", slug: classSlug, level: pick.level });
1239
+ if (!feats.some((x) => x.slug === f.slug)) feats.push(f);
1240
+ if (site.via && !featVia.has(f.slug)) featVia.set(f.slug, site.via);
1082
1241
  }
1083
1242
  const originFeat = (background == null ? void 0 : background.origin_feat) ? resolveOriginFeat(this.entities, background.origin_feat, background.slug) : null;
1084
1243
  const originFeatSlug = originFeat == null ? void 0 : originFeat.feat.slug;
@@ -1090,7 +1249,7 @@ var PCResolver = class {
1090
1249
  }
1091
1250
  const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
1092
1251
  const features = collectResolvedFeatures(race, classes, background, feats, featVia);
1093
- const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background);
1252
+ const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background, memberSites);
1094
1253
  features.push(...extraFeatures);
1095
1254
  features.push(...collectAdditionalFeatures(character, this.entities, warnings));
1096
1255
  const casters = [];
@@ -1100,7 +1259,7 @@ var PCResolver = class {
1100
1259
  if (profile && classSlug != null) casters.push({ classSlug, spellList: profile.spellList });
1101
1260
  });
1102
1261
  const spells = [];
1103
- for (const raw of (_f = character.spells.known) != null ? _f : []) {
1262
+ for (const raw of (_a = character.spells.known) != null ? _a : []) {
1104
1263
  const n = normalizeKnownSpell(raw);
1105
1264
  const reg = this.entities.getByTypeAndSlug("spell", n.slug);
1106
1265
  if (!reg) {
@@ -1108,34 +1267,45 @@ var PCResolver = class {
1108
1267
  continue;
1109
1268
  }
1110
1269
  const entity = mirrorSpellShapes(reg.data);
1111
- const isCantrip = ((_g = entity.level) != null ? _g : 0) === 0;
1112
- const classSlug = (_h = n.classSlug) != null ? _h : attributeUnclassedSpell(entity.classes, casters, this.entities);
1113
- const prep = isCantrip || n.alwaysPrepared ? true : (_i = n.preparedFlag) != null ? _i : false;
1270
+ const isCantrip = ((_b = entity.level) != null ? _b : 0) === 0;
1271
+ const classSlug = (_c = n.classSlug) != null ? _c : attributeUnclassedSpell(entity.classes, casters, this.entities);
1272
+ const prep = isCantrip || n.alwaysPrepared ? true : (_d = n.preparedFlag) != null ? _d : false;
1114
1273
  spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared, persisted: true });
1115
1274
  }
1116
- const oc = (_j = character.origin_choices) != null ? _j : {};
1275
+ const oc = (_e = character.origin_choices) != null ? _e : {};
1117
1276
  if (originFeat) {
1118
1277
  const read = (childId) => oc[`background:feat:${childId}`];
1119
1278
  spells.push(...collectFeatGrantedSpells(originFeat.feat, read, this.entities, warnings));
1120
1279
  }
1121
- for (const entry of character.class) {
1122
- for (const atLevel of Object.values((_k = entry.choices) != null ? _k : {})) {
1123
- const featRef = atLevel.feat;
1124
- if (typeof featRef !== "string") continue;
1125
- const slug = stripSlug(featRef);
1126
- if (!slug) continue;
1127
- const reg = this.entities.getByTypeAndSlug("feat", slug);
1128
- if (!reg) continue;
1129
- const feat = reg.data;
1130
- const read = (childId) => atLevel[`feat:${childId}`];
1131
- spells.push(...collectFeatGrantedSpells(feat, read, this.entities, warnings));
1280
+ for (const site of featSites) {
1281
+ const reg = this.entities.getByTypeAndSlug("feat", site.slug);
1282
+ if (!reg) continue;
1283
+ const feat = reg.data;
1284
+ spells.push(...collectFeatGrantedSpells(feat, site.read, this.entities, warnings));
1285
+ }
1286
+ for (const site of memberSites) {
1287
+ const classSlug = stripSlug((_g = (_f = character.class[site.classIndex]) == null ? void 0 : _f.name) != null ? _g : null);
1288
+ for (const ch of site.choices) {
1289
+ if (ch.kind !== "select-entity" || ch.entity_type !== "spell" || isTargetChoice(ch)) continue;
1290
+ const v = site.read(ch.id);
1291
+ for (const ref of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
1292
+ const slug = typeof ref === "string" ? stripSlug(ref) : null;
1293
+ if (!slug) continue;
1294
+ const reg = this.entities.getByTypeAndSlug("spell", slug);
1295
+ if (!reg) {
1296
+ warnings.push(`Spell [[${slug}]] not found in compendium.`);
1297
+ continue;
1298
+ }
1299
+ const entity = mirrorSpellShapes(reg.data);
1300
+ spells.push({ entity, slug, classSlug, source: "class", prepared: true, alwaysPrepared: true });
1301
+ }
1132
1302
  }
1133
1303
  }
1134
- const ownSpellcastingAbility = (_l = classes.map((c) => {
1304
+ const ownSpellcastingAbility = (_h = classes.map((c) => {
1135
1305
  if (!c.entity) return null;
1136
1306
  const p = resolveSpellcasting(c);
1137
1307
  return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
1138
- }).find((a) => a != null)) != null ? _l : null;
1308
+ }).find((a) => a != null)) != null ? _h : null;
1139
1309
  spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
1140
1310
  spells.push(...collectAdditionalSpells({
1141
1311
  race,
@@ -1186,6 +1356,9 @@ var PCResolver = class {
1186
1356
  };
1187
1357
  resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities, featureResources);
1188
1358
  resolvedCharacter.resources = resolveResourceIndex(resolvedCharacter);
1359
+ resolvedCharacter.features = bindFeatureConsumers(resolvedCharacter.features, resolvedCharacter.resources, classes);
1360
+ const infusions = buildInfusionContext(character, resolvedCharacter, this.entities);
1361
+ if (infusions) resolvedCharacter.infusions = infusions;
1189
1362
  return { character: resolvedCharacter, warnings };
1190
1363
  }
1191
1364
  };
@@ -1204,16 +1377,9 @@ function collectChosenWeaponMasteries(character) {
1204
1377
  }
1205
1378
  function collectFeatPicks(character) {
1206
1379
  const picks = /* @__PURE__ */ new Map();
1207
- character.class.forEach((c, classIndex) => {
1208
- for (const [lvl, choiceBlock] of Object.entries(c.choices)) {
1209
- const feat = choiceBlock == null ? void 0 : choiceBlock.feat;
1210
- if (typeof feat === "string") {
1211
- const s = stripSlug(feat);
1212
- const level = /^\d+$/.test(lvl) ? Number(lvl) : null;
1213
- if (s && !picks.has(s)) picks.set(s, { slug: s, classIndex, level });
1214
- }
1215
- }
1216
- });
1380
+ for (const site of featPickSites(character.class, [], () => null)) {
1381
+ if (!picks.has(site.slug)) picks.set(site.slug, { slug: site.slug, classIndex: site.classIndex, level: site.level });
1382
+ }
1217
1383
  return [...picks.values()];
1218
1384
  }
1219
1385
  function collectFeatGrantedSpells(feat, read, entities, warnings) {
@@ -1290,11 +1456,21 @@ function collectSpellPickSlugs(choices, read, out) {
1290
1456
  }
1291
1457
  }
1292
1458
  }
1293
- function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null) {
1294
- var _a, _b;
1459
+ function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null, memberSites = []) {
1460
+ var _a, _b, _c, _d;
1295
1461
  const out = [];
1462
+ for (const site of memberSites) {
1463
+ const classSlug = (_b = (_a = classes[site.classIndex]) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug;
1464
+ if (!classSlug) continue;
1465
+ const at = {};
1466
+ const grants = site.choices.filter((ch) => !isTargetChoice(ch));
1467
+ for (const ch of grants) at[ch.id] = site.read(ch.id);
1468
+ walkChoiceGrants(grants, at, (granted, suppress) => {
1469
+ out.push({ feature: granted, source: { kind: "class", slug: classSlug, level: site.level }, ...suppress ? { renderSuppressed: true } : {} });
1470
+ }, registry);
1471
+ }
1296
1472
  classes.forEach((c, i) => {
1297
- var _a2, _b2, _c, _d;
1473
+ var _a2, _b2, _c2, _d2;
1298
1474
  if (!c.entity) return;
1299
1475
  const entity = c.entity;
1300
1476
  const entry = character.class[i];
@@ -1303,7 +1479,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1303
1479
  const lvl = Number(lvlStr);
1304
1480
  if (!Number.isFinite(lvl) || lvl > entry.level) continue;
1305
1481
  const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
1306
- const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
1482
+ const subFeatures = c.subclass ? (_d2 = ((_c2 = c.subclass.features_by_level) != null ? _c2 : {})[lvl]) != null ? _d2 : [] : [];
1307
1483
  for (const feature of [...features, ...subFeatures]) {
1308
1484
  walkChoiceGrants(feature.choices, atLevel, (granted, suppress) => {
1309
1485
  out.push({
@@ -1315,7 +1491,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1315
1491
  }
1316
1492
  }
1317
1493
  });
1318
- const oc = (_a = character.origin_choices) != null ? _a : {};
1494
+ const oc = (_c = character.origin_choices) != null ? _c : {};
1319
1495
  const originAt = (ns) => {
1320
1496
  const at = {};
1321
1497
  for (const [key, val] of Object.entries(oc)) {
@@ -1333,7 +1509,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1333
1509
  });
1334
1510
  };
1335
1511
  walkChoiceGrants(race.choices, atRace, emitRace, registry);
1336
- for (const trait of (_b = race.traits) != null ? _b : []) {
1512
+ for (const trait of (_d = race.traits) != null ? _d : []) {
1337
1513
  walkChoiceGrants(trait.choices, atRace, emitRace, registry);
1338
1514
  }
1339
1515
  }
@@ -1622,6 +1798,7 @@ export {
1622
1798
  collectResolvedFeatures,
1623
1799
  collectProficiencyGrants,
1624
1800
  __matchesFilterForTest,
1801
+ __categoryToEntitySelectForTest,
1625
1802
  collectChosenProficiencies,
1626
1803
  computeEffectiveProficiencies,
1627
1804
  collectChosenAbilityPoints,