@archivist-gg/dnd5e 0.3.11 → 0.3.13

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 (164) 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-2LULMQTF.js +14 -0
  11. package/dist/{chunk-CBEKVAX4.js → chunk-4BZ47YJ4.js} +10 -3
  12. package/dist/{chunk-T2SNEJOE.js → chunk-4SLOJXJ4.js} +1 -1
  13. package/dist/{chunk-2DFENQIJ.js → chunk-5GKJAUMX.js} +2 -0
  14. package/dist/{chunk-J77CKYV3.js → chunk-5KGV6TF7.js} +1 -1
  15. package/dist/{chunk-CHUALUIX.js → chunk-6ZY3U6BQ.js} +1 -1
  16. package/dist/{chunk-DNAJRMNN.js → chunk-A54HHCWW.js} +6 -7
  17. package/dist/{chunk-2M34FLAY.js → chunk-BRGLDWUP.js} +3 -1
  18. package/dist/chunk-CM5CWO4K.js +735 -0
  19. package/dist/{chunk-3HX5VLWP.js → chunk-DPM6V7YX.js} +1 -1
  20. package/dist/chunk-EJP263OP.js +186 -0
  21. package/dist/{chunk-G247QXWW.js → chunk-EL6QPF6I.js} +1 -1
  22. package/dist/{chunk-SMLXZUBH.js → chunk-EYX4JMAU.js} +4 -1
  23. package/dist/{chunk-AW3EHF3L.js → chunk-FC4IGH5Y.js} +1 -1
  24. package/dist/{chunk-PI4LLJG4.js → chunk-FPGXFD2M.js} +2 -2
  25. package/dist/{chunk-23LZLBJK.js → chunk-G6MHJDVT.js} +6 -2
  26. package/dist/{chunk-NWDF5HG3.js → chunk-G7ED5H2G.js} +1 -1
  27. package/dist/chunk-H5PTKYXL.js +101 -0
  28. package/dist/{chunk-LWFTHJIE.js → chunk-HHBD4SQ6.js} +1 -1
  29. package/dist/{chunk-PP2UHLBA.js → chunk-HJQNK3J5.js} +8 -0
  30. package/dist/{chunk-JC5UNIVM.js → chunk-HS6FIOLP.js} +2 -0
  31. package/dist/chunk-IHE6KVC3.js +13 -0
  32. package/dist/{chunk-BLRW2UU6.js → chunk-J53ERF6K.js} +2 -2
  33. package/dist/{chunk-MQ747UCR.js → chunk-JNNNBEUH.js} +65 -2
  34. package/dist/{chunk-LDDCNPHD.js → chunk-K5NJBTE2.js} +1 -1
  35. package/dist/{chunk-4ISR76DH.js → chunk-KSTV7J2U.js} +9 -3
  36. package/dist/chunk-LBEXLN6I.js +91 -0
  37. package/dist/{chunk-UBBKSYBY.js → chunk-LHZXYFBU.js} +30 -1
  38. package/dist/{chunk-U3BABVJF.js → chunk-LVX6TQ3R.js} +2 -2
  39. package/dist/{chunk-R747YKS7.js → chunk-M2OWVAZG.js} +1 -1
  40. package/dist/{chunk-7YU4NPKR.js → chunk-MC5YJ4PB.js} +1 -1
  41. package/dist/{chunk-JCEV7KYN.js → chunk-MUIU7RZ3.js} +1 -1
  42. package/dist/{chunk-R2ZV233X.js → chunk-MYY2C5MB.js} +3 -3
  43. package/dist/{chunk-S56D6B6C.js → chunk-PLOSP4XQ.js} +3 -3
  44. package/dist/{chunk-JANNRGJC.js → chunk-PX25R3DK.js} +1 -1
  45. package/dist/{chunk-V373TRJE.js → chunk-Q5XTEYHD.js} +191 -125
  46. package/dist/{chunk-MAH6DCRI.js → chunk-Q6UOHCCT.js} +1 -1
  47. package/dist/{chunk-2INMRR3Q.js → chunk-QEO7LYTG.js} +215 -94
  48. package/dist/{chunk-PJMG5XV3.js → chunk-QSBJ6NEM.js} +1 -1
  49. package/dist/{chunk-BL3VELKP.js → chunk-R2V5LN3I.js} +1 -1
  50. package/dist/{chunk-4DOMDEBZ.js → chunk-RQNJXADQ.js} +1 -1
  51. package/dist/{chunk-GFZ7PITJ.js → chunk-S3YUXDSC.js} +6 -1
  52. package/dist/chunk-T5GZBKIQ.js +927 -0
  53. package/dist/{chunk-PZCE7DJM.js → chunk-UE6LKRXQ.js} +15 -1
  54. package/dist/{chunk-5N2GW72F.js → chunk-UZXG6QSY.js} +2 -1
  55. package/dist/{chunk-QHMWA3SZ.js → chunk-VDPRBUU5.js} +1 -1
  56. package/dist/{chunk-4XTNPNQB.js → chunk-VPYBK2MG.js} +1 -1
  57. package/dist/{chunk-V6LJFP4R.js → chunk-X7VZY4ON.js} +2 -2
  58. package/dist/{chunk-HZA7OE7N.js → chunk-XCFC7LGF.js} +2 -1
  59. package/dist/{chunk-BIA32MQ5.js → chunk-XJRNEQPD.js} +1 -1
  60. package/dist/{chunk-UIRMA5KM.js → chunk-YDGMBMYI.js} +1 -1
  61. package/dist/{chunk-6X4KUDMU.js → chunk-ZCTUYGNV.js} +7 -2
  62. package/dist/{chunk-XTRDCSJB.js → chunk-ZMJQUZQH.js} +4 -1
  63. package/dist/chunk-ZUQW5VBZ.js +89 -0
  64. package/dist/class/class.codec.js +7 -7
  65. package/dist/class/class.parser.js +6 -6
  66. package/dist/class/class.schema.d.ts +10 -0
  67. package/dist/class/class.schema.js +5 -5
  68. package/dist/entities/base-item-resolver.js +2 -2
  69. package/dist/entities/slug.d.ts +18 -8
  70. package/dist/entities/slug.js +3 -1
  71. package/dist/feat/feat.codec.js +5 -5
  72. package/dist/feat/feat.parser.js +4 -4
  73. package/dist/feat/feat.schema.d.ts +16 -3
  74. package/dist/feat/feat.schema.js +3 -3
  75. package/dist/index.js +7190 -4116
  76. package/dist/item/item.actions-map.d.ts +40 -5
  77. package/dist/item/item.actions-map.js +5 -84
  78. package/dist/item/item.attunement.js +3 -9
  79. package/dist/item/item.bonuses.js +5 -2
  80. package/dist/item/item.codec.js +3 -3
  81. package/dist/item/item.conditions.js +4 -1
  82. package/dist/item/item.conditions.types.d.ts +4 -0
  83. package/dist/item/item.cost.d.ts +3 -0
  84. package/dist/item/item.cost.js +29 -0
  85. package/dist/item/item.icon.d.ts +15 -0
  86. package/dist/item/item.icon.js +211 -0
  87. package/dist/item/item.parser.js +2 -2
  88. package/dist/item/item.schema.d.ts +16 -1
  89. package/dist/item/item.schema.js +3 -1
  90. package/dist/item/item.types.d.ts +9 -2
  91. package/dist/monster/monster.parser.js +5 -5
  92. package/dist/monster/monster.schema.js +4 -4
  93. package/dist/optional-feature/optional-feature.codec.js +4 -4
  94. package/dist/optional-feature/optional-feature.parser.js +3 -3
  95. package/dist/optional-feature/optional-feature.schema.d.ts +229 -3
  96. package/dist/optional-feature/optional-feature.schema.js +6 -2
  97. package/dist/pc/pc.additional-spells.js +2 -2
  98. package/dist/pc/pc.asi-flatten.d.ts +62 -1
  99. package/dist/pc/pc.asi-flatten.js +16 -0
  100. package/dist/pc/pc.conditions.js +1 -1
  101. package/dist/pc/pc.containment.d.ts +45 -0
  102. package/dist/pc/pc.containment.js +24 -0
  103. package/dist/pc/pc.decision-engine.d.ts +21 -3
  104. package/dist/pc/pc.decision-engine.js +21 -12
  105. package/dist/pc/pc.entry-overrides.d.ts +79 -0
  106. package/dist/pc/pc.entry-overrides.js +40 -0
  107. package/dist/pc/pc.equipment.d.ts +2 -2
  108. package/dist/pc/pc.equipment.js +18 -7
  109. package/dist/pc/pc.feature-effects.js +2 -2
  110. package/dist/pc/pc.infusions.d.ts +220 -0
  111. package/dist/pc/pc.infusions.js +64 -0
  112. package/dist/pc/pc.parser.js +18 -1
  113. package/dist/pc/pc.pools.d.ts +1 -1
  114. package/dist/pc/pc.pools.js +11 -3
  115. package/dist/pc/pc.proficiencies.js +19 -12
  116. package/dist/pc/pc.recalc.d.ts +39 -36
  117. package/dist/pc/pc.recalc.js +58 -30
  118. package/dist/pc/pc.resolver.js +19 -12
  119. package/dist/pc/pc.resource-seed.js +1 -1
  120. package/dist/pc/pc.resources.d.ts +6 -1
  121. package/dist/pc/pc.resources.js +3 -1
  122. package/dist/pc/pc.rest.d.ts +21 -1
  123. package/dist/pc/pc.rest.js +52 -19
  124. package/dist/pc/pc.schema.d.ts +14 -0
  125. package/dist/pc/pc.schema.js +1 -1
  126. package/dist/pc/pc.slotting.d.ts +36 -3
  127. package/dist/pc/pc.slotting.js +13 -5
  128. package/dist/pc/pc.tool-picks.d.ts +51 -0
  129. package/dist/pc/pc.tool-picks.js +18 -0
  130. package/dist/pc/pc.types.d.ts +70 -12
  131. package/dist/pc/pool-layout.d.ts +14 -0
  132. package/dist/pc/pool-layout.js +7 -3
  133. package/dist/race/race.codec.js +6 -6
  134. package/dist/race/race.parser.js +5 -5
  135. package/dist/race/race.schema.js +4 -4
  136. package/dist/schemas/attack-schema.d.ts +1 -1
  137. package/dist/schemas/choice-schema.js +2 -2
  138. package/dist/schemas/feature-effect-schema.d.ts +16 -3
  139. package/dist/schemas/feature-effect-schema.js +1 -1
  140. package/dist/schemas/feature-schema.js +3 -3
  141. package/dist/schemas/selection-pool-schema.d.ts +10 -0
  142. package/dist/schemas/selection-pool-schema.js +1 -1
  143. package/dist/srd/retired-items.d.ts +14 -0
  144. package/dist/srd/retired-items.js +11 -0
  145. package/dist/subclass/subclass.codec.js +7 -7
  146. package/dist/subclass/subclass.parser.js +6 -6
  147. package/dist/subclass/subclass.schema.d.ts +10 -0
  148. package/dist/subclass/subclass.schema.js +5 -5
  149. package/dist/types/choice.d.ts +8 -0
  150. package/dist/types/equipment-grant.d.ts +18 -1
  151. package/dist/types/equipment-grant.js +12 -0
  152. package/dist/types/feature-effect.d.ts +6 -1
  153. package/dist/types/optional-feature.types.d.ts +79 -1
  154. package/dist/types/selection-pool.d.ts +16 -1
  155. package/dist/weapon/weapon.classify.d.ts +10 -8
  156. package/dist/weapon/weapon.classify.js +2 -2
  157. package/dist/weapon/weapon.codec.js +3 -3
  158. package/dist/weapon/weapon.parser.js +2 -2
  159. package/dist/weapon/weapon.schema.d.ts +1 -0
  160. package/dist/weapon/weapon.schema.js +1 -1
  161. package/dist/weapon/weapon.types.d.ts +2 -0
  162. package/package.json +1 -1
  163. package/dist/chunk-O4SRD3J5.js +0 -165
  164. package/dist/chunk-WXWPK3LI.js +0 -55
@@ -1,7 +1,19 @@
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";
1
13
  import {
2
14
  resolveFeatureResources,
3
15
  resolveResourceIndex
4
- } from "./chunk-4ISR76DH.js";
16
+ } from "./chunk-KSTV7J2U.js";
5
17
  import {
6
18
  attributeUnclassedSpell,
7
19
  effectiveSpellcastingAbility,
@@ -9,9 +21,25 @@ import {
9
21
  resolveSpellcasting
10
22
  } from "./chunk-DPUJDOKV.js";
11
23
  import {
24
+ assembleEffectFeatures,
25
+ collectProficiencyEffectGrants
26
+ } from "./chunk-4BZ47YJ4.js";
27
+ import {
28
+ ALL_LANGUAGES,
29
+ ALL_SKILL_SLUGS,
30
+ ALL_TOOLS
31
+ } from "./chunk-KU3YLF6D.js";
32
+ import {
33
+ humanizeProficiency,
34
+ proficiencyLabel,
35
+ toProfSlug
36
+ } from "./chunk-RLGQ5PZZ.js";
37
+ import {
38
+ buildInfusionContext,
12
39
  resolveAllPools,
40
+ resolveOptionRows,
13
41
  strandedPicks
14
- } from "./chunk-O4SRD3J5.js";
42
+ } from "./chunk-T5GZBKIQ.js";
15
43
  import {
16
44
  countColumnsFor,
17
45
  readTableColumn
@@ -21,32 +49,23 @@ import {
21
49
  } from "./chunk-YHYTW5QA.js";
22
50
  import {
23
51
  collectAdditionalSpells
24
- } from "./chunk-3HX5VLWP.js";
52
+ } from "./chunk-DPM6V7YX.js";
53
+ import {
54
+ convertibleLegacyAsi,
55
+ flattenAsiOrFeat,
56
+ resolveLegacyAsiFeat
57
+ } from "./chunk-LBEXLN6I.js";
25
58
  import {
26
59
  isItemEntity,
27
60
  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";
61
+ } from "./chunk-LHZXYFBU.js";
43
62
  import {
44
63
  warnOnce
45
64
  } from "./chunk-LVQLWRCU.js";
46
65
  import {
47
66
  bareEntitySlug,
48
67
  wikilinkTailSlug
49
- } from "./chunk-6X4KUDMU.js";
68
+ } from "./chunk-ZCTUYGNV.js";
50
69
  import {
51
70
  FEATURE_TYPE_FOR_CATEGORY,
52
71
  featProgressionCategory
@@ -80,6 +99,7 @@ function collectProficiencyGrants(resolved) {
80
99
  for (const w of (_f = (_e = prof.weapons) == null ? void 0 : _e.fixed) != null ? _f : []) classWeaponFixed.push({ value: w, source });
81
100
  for (const w of (_h = (_g = prof.weapons) == null ? void 0 : _g.categories) != null ? _h : []) classWeaponCategories.push({ value: w, source });
82
101
  for (const t of (_j = (_i = prof.tools) == null ? void 0 : _i.fixed) != null ? _j : []) classToolFixed.push({ value: t, source });
102
+ for (const t of classToolPicks(c.entity).grants) classToolFixed.push({ value: t, source });
83
103
  }
84
104
  const raceSource = (_l = (_k = resolved.race) == null ? void 0 : _k.name) != null ? _l : UNKNOWN_SOURCE;
85
105
  const raceLangFixed = ((_o = (_n = (_m = resolved.race) == null ? void 0 : _m.languages) == null ? void 0 : _n.fixed) != null ? _o : []).map(
@@ -95,6 +115,7 @@ function collectProficiencyGrants(resolved) {
95
115
  for (const t of entry.items) bgToolFixed.push({ value: t, source });
96
116
  }
97
117
  }
118
+ for (const t of backgroundToolPicks(bg).grants) bgToolFixed.push({ value: t, source });
98
119
  for (const entry of (_r = bg.language_proficiencies) != null ? _r : []) {
99
120
  if (entry.kind === "fixed") {
100
121
  for (const l of entry.languages) bgLangFixed.push({ value: l, source });
@@ -164,25 +185,18 @@ function collectProficiencyGrants(resolved) {
164
185
  };
165
186
  }
166
187
 
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
188
  // src/pc/pc.decision-engine.ts
184
189
  var warnedDegradedEquipment = /* @__PURE__ */ new Set();
185
190
  var warnedAmbiguousBare = /* @__PURE__ */ new Set();
191
+ var foldedTypeMemo = /* @__PURE__ */ new Map();
192
+ function foldedType(s) {
193
+ let v = foldedTypeMemo.get(s);
194
+ if (v === void 0) {
195
+ v = categoryWords(s).join(" ");
196
+ foldedTypeMemo.set(s, v);
197
+ }
198
+ return v;
199
+ }
186
200
  function matchesFilter(e, where, ownerBare) {
187
201
  var _a, _b, _c, _d, _e;
188
202
  const d = e.data;
@@ -198,6 +212,14 @@ function matchesFilter(e, where, ownerBare) {
198
212
  if (where.armor_category) {
199
213
  if (((_d = d.category) != null ? _d : "").toLowerCase() !== where.armor_category) return false;
200
214
  }
215
+ if (where.item_type) {
216
+ const t = foldedType(typeof d.type === "string" ? d.type : "");
217
+ if (!t || !where.item_type.some((x) => foldedType(x) === t)) return false;
218
+ }
219
+ if (where.mundane) {
220
+ const r = typeof d.rarity === "string" ? d.rarity.trim().toLowerCase() : "";
221
+ if (r && r !== "none" && r !== "mundane") return false;
222
+ }
201
223
  if (where.parent_class === "self") {
202
224
  if (!d.parent_class || wikilinkTailSlug(d.parent_class) !== ownerBare) return false;
203
225
  }
@@ -208,17 +230,94 @@ function matchesFilter(e, where, ownerBare) {
208
230
  return true;
209
231
  }
210
232
  var __matchesFilterForTest = matchesFilter;
233
+ function parseEquipmentCategory(category) {
234
+ const tools = toolCategoriesOf(category);
235
+ if (tools) return { kind: "tool", tools };
236
+ const words = categoryWords(category).filter((w) => w !== "any");
237
+ const has = (w) => words.includes(w);
238
+ const only = (allowed) => words.every((w) => allowed.includes(w));
239
+ if (words.length === 1 && has("shield")) return { kind: "shield" };
240
+ if (has("armor") && only(["armor", "light", "medium", "heavy"]) && words.length <= 2) {
241
+ const armor = ["light", "medium", "heavy"].find((k) => has(k));
242
+ return { kind: "armor", armor };
243
+ }
244
+ if (has("weapon") && only(["weapon", "simple", "martial", "melee", "ranged"])) {
245
+ const klass = ["simple", "martial"].filter((k) => has(k));
246
+ const range = ["melee", "ranged"].filter((k) => has(k));
247
+ if (klass.length === 1 && range.length <= 1 && words.length === 2 + range.length) {
248
+ return { kind: "weapon", weapon: klass[0], range: range[0] };
249
+ }
250
+ }
251
+ return void 0;
252
+ }
211
253
  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" } };
254
+ const spec = parseEquipmentCategory(category);
255
+ const pick = { kind: "select-entity", id, count: 1 };
256
+ switch (spec == null ? void 0 : spec.kind) {
257
+ case "shield":
258
+ return { ...pick, label: "Choose a shield", entity_type: "armor", where: { armor_category: "shield" } };
259
+ case "armor":
260
+ return spec.armor ? { ...pick, label: `Choose ${spec.armor} armor`, entity_type: "armor", where: { armor_category: spec.armor } } : { ...pick, label: "Choose armor", entity_type: "armor" };
261
+ case "weapon":
262
+ return {
263
+ ...pick,
264
+ label: `Choose a ${spec.weapon}${spec.range ? ` ${spec.range}` : ""} weapon`,
265
+ entity_type: "weapon",
266
+ where: { weapon_category: spec.weapon }
267
+ };
268
+ case "tool":
269
+ return {
270
+ ...pick,
271
+ label: `Choose ${spec.tools.map((t) => TOOL_CATEGORIES[t].label).join(" or ")}`,
272
+ entity_type: "item",
273
+ where: { item_type: spec.tools.map((t) => TOOL_CATEGORIES[t].itemType), mundane: true }
274
+ };
275
+ default:
276
+ warnOnce(
277
+ `equipment-category:${category}`,
278
+ `Archivist: starting-equipment category "${category}" is not a known item category; its pick offers no options.`
279
+ );
280
+ return { ...pick, label: `Choose a ${category.replace(/[-_]+/g, " ")}`, entity_type: "item", from: [] };
215
281
  }
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" };
282
+ }
283
+ var __categoryToEntitySelectForTest = categoryToEntitySelect;
284
+ function equipmentDecisions(entries, ownerSlug) {
285
+ const out = [];
286
+ let degraded = false;
287
+ (entries != null ? entries : []).forEach((eq, i) => {
288
+ var _a;
289
+ if ((eq == null ? void 0 : eq.kind) !== "choice") return;
290
+ const options = (_a = eq.options) != null ? _a : [];
291
+ out.push({
292
+ kind: "select-inline",
293
+ id: `equipment-${i}`,
294
+ label: "Starting Equipment",
295
+ count: 1,
296
+ options: options.map((opt, j) => {
297
+ var _a2;
298
+ const label = typeof opt === "string" ? opt : (_a2 = opt == null ? void 0 : opt.label) != null ? _a2 : "";
299
+ const wellFormed = !!opt && typeof opt === "object" && Array.isArray(opt.grants);
300
+ if (!wellFormed) degraded = true;
301
+ const grants = wellFormed ? opt.grants : [];
302
+ return {
303
+ value: `option-${j}`,
304
+ label,
305
+ choices: grants.flatMap((g, k) => isCategoryGrant(g) ? [categoryToEntitySelect(g.category, equipmentCategoryKey(i, j, k))] : [])
306
+ };
307
+ })
308
+ });
309
+ });
310
+ if (degraded && !warnedDegradedEquipment.has(ownerSlug)) {
311
+ warnedDegradedEquipment.add(ownerSlug);
312
+ console.warn(
313
+ `[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.`
314
+ );
219
315
  }
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 } };
316
+ return out;
317
+ }
318
+ function authoredProficiencyPool(choice) {
319
+ if (!choice.from) return void 0;
320
+ return choice.domain === "tool" ? expandToolPool(choice.from) : choice.from;
222
321
  }
223
322
  function enumerateOptions(choice, ctx, ownerBare) {
224
323
  var _a, _b;
@@ -260,7 +359,7 @@ function enumerateOptions(choice, ctx, ownerBare) {
260
359
  return filtered.map((e) => ({ value: e.slug, label: e.name, entity: e }));
261
360
  }
262
361
  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] : [];
362
+ const pool = (_a = authoredProficiencyPool(choice)) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : choice.domain === "language" ? [...ALL_LANGUAGES] : choice.domain === "tool" ? [...ALL_TOOLS] : [];
264
363
  return pool.map((v) => ({ value: v, label: humanizeProficiency(v) }));
265
364
  }
266
365
  case "ability-points": {
@@ -284,6 +383,12 @@ function requiredCount(choice) {
284
383
  if (choice.kind === "select-proficiency") return choice.count;
285
384
  return (_a = choice.count) != null ? _a : 1;
286
385
  }
386
+ function capAtCount(choice, value) {
387
+ const folds = choice.kind === "select-proficiency" || choice.kind === "select-entity" && choice.entity_type === "weapon" && choice.id !== "weapon-mastery";
388
+ if (!folds || !Array.isArray(value)) return value;
389
+ const n = requiredCount(choice);
390
+ return value.length > n ? value.slice(0, n) : value;
391
+ }
287
392
  function statusOf(choice, selected, satisfied) {
288
393
  const n = selectionCount(choice, selected);
289
394
  const need = requiredCount(choice);
@@ -317,7 +422,7 @@ function buildItem(inputChoice, source, level, featureName, readValue, ctx, owne
317
422
  const keyPrefix = (_a = opts == null ? void 0 : opts.keyPrefix) != null ? _a : "";
318
423
  const expandFeatChildren = (_b = opts == null ? void 0 : opts.expandFeatChildren) != null ? _b : true;
319
424
  const key = keyPrefix + choice.id;
320
- const raw = readValue(key);
425
+ const raw = capAtCount(choice, readValue(key));
321
426
  const preExclusionOptions = enumerateOptions(choice, ctx, ownerBare);
322
427
  let options = preExclusionOptions;
323
428
  const mine = new Set(
@@ -440,6 +545,7 @@ function visitProficiencyChoices(resolved, visit) {
440
545
  }, readAt(1)("skills"));
441
546
  }
442
547
  if (i === 0) walk(entity.choices, readAt(1));
548
+ if (i === 0) walk(classToolPicks(entity).decisions, readAt(1));
443
549
  for (const rf of resolved.features) {
444
550
  if (rf.source.kind !== "class" && rf.source.kind !== "subclass") continue;
445
551
  const belongs = rf.source.kind === "class" ? rf.source.slug === entity.slug : c.subclass != null && rf.source.slug === c.subclass.slug;
@@ -462,6 +568,7 @@ function visitProficiencyChoices(resolved, visit) {
462
568
  }
463
569
  if (resolved.background) {
464
570
  walk(resolved.background.choices, originRead("background"));
571
+ walk(backgroundToolPicks(resolved.background).decisions, originRead("background"));
465
572
  if (resolved.background.feature) {
466
573
  const feature = resolved.background.feature;
467
574
  walk(feature.choices, originRead("background"));
@@ -476,7 +583,8 @@ var filterToPool = (vals, pool) => pool ? vals.map((v) => matchPool(v, pool)).fi
476
583
  function collectChosenProficiencies(resolved) {
477
584
  const out = { skills: [], expertise: [], languages: [], tools: [], weapons: [], saves: [] };
478
585
  const isAbility = (v) => ABILITY_KEYS.includes(v);
479
- visitProficiencyChoices(resolved, (choice, selected) => {
586
+ visitProficiencyChoices(resolved, (choice, stored) => {
587
+ const selected = capAtCount(choice, stored);
480
588
  const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
481
589
  if (choice.kind === "select-entity" && choice.entity_type === "weapon") {
482
590
  if (choice.id === "weapon-mastery") return;
@@ -487,7 +595,7 @@ function collectChosenProficiencies(resolved) {
487
595
  return;
488
596
  }
489
597
  if (choice.kind !== "select-proficiency") return;
490
- const valid = filterToPool(vals, choice.from);
598
+ const valid = filterToPool(vals, authoredProficiencyPool(choice));
491
599
  if (choice.domain === "save") {
492
600
  for (const v of valid) if (isAbility(v) && !out.saves.includes(v)) out.saves.push(v);
493
601
  return;
@@ -589,7 +697,7 @@ function collectChosenAbilityPoints(resolved) {
589
697
  return out;
590
698
  }
591
699
  function buildDecisionLedger(resolved, ctx) {
592
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
700
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
593
701
  const eff = computeEffectiveProficiencies(resolved);
594
702
  const chosen = collectChosenProficiencies(resolved);
595
703
  const { features, activeBuffs } = assembleEffectFeatures(resolved);
@@ -614,7 +722,7 @@ function buildDecisionLedger(resolved, ctx) {
614
722
  };
615
723
  const classes = [];
616
724
  resolved.classes.forEach((c, classIndex) => {
617
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n, _o, _p;
725
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2, _o;
618
726
  if (!c.entity) return;
619
727
  const entity = c.entity;
620
728
  const ownerBare = bareEntitySlug(entity.slug);
@@ -629,6 +737,19 @@ function buildDecisionLedger(resolved, ctx) {
629
737
  var _a3;
630
738
  return (_a3 = c.choices[lvl]) == null ? void 0 : _a3[id];
631
739
  };
740
+ let legacyAsiFeat;
741
+ const legacyAsiReader = (choice, lvl) => {
742
+ const alloc = convertibleLegacyAsi(c.choices[lvl]);
743
+ if (!alloc) return null;
744
+ const flat = flattenAsiOrFeat(choice);
745
+ if (flat.kind !== "select-entity" || flat.entity_type !== "feat" || flat.from || flat.where) return null;
746
+ if (legacyAsiFeat === void 0) legacyAsiFeat = resolveLegacyAsiFeat(ctx.registry, entity);
747
+ const asi = legacyAsiFeat;
748
+ if (!asi) return null;
749
+ const raw = readAt(lvl);
750
+ const childKey = `feat:${asi.childId}`;
751
+ return (id) => id === "feat" ? asi.slug : id === childKey ? alloc : raw(id);
752
+ };
632
753
  const featProgressions = [...(_a2 = entity.feat_progression) != null ? _a2 : [], ...(_c2 = (_b2 = c.subclass) == null ? void 0 : _b2.feat_progression) != null ? _c2 : []];
633
754
  const suppressedFeatureTypes = /* @__PURE__ */ new Set();
634
755
  for (const decl of [...(_d2 = entity.selection_pools) != null ? _d2 : [], ...(_f2 = (_e2 = c.subclass) == null ? void 0 : _e2.selection_pools) != null ? _f2 : []]) {
@@ -661,7 +782,7 @@ function buildDecisionLedger(resolved, ctx) {
661
782
  ));
662
783
  }
663
784
  if (classIndex === 0) {
664
- for (const ch of (_i2 = entity.choices) != null ? _i2 : []) {
785
+ for (const ch of [...(_i2 = entity.choices) != null ? _i2 : [], ...classToolPicks(entity).decisions]) {
665
786
  push(1, buildItem(
666
787
  ch,
667
788
  { kind: "class", slug: entity.slug, level: 1 },
@@ -675,36 +796,7 @@ function buildDecisionLedger(resolved, ctx) {
675
796
  }
676
797
  }
677
798
  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
- }
799
+ for (const ch of equipmentDecisions(entity.starting_equipment, entity.slug)) {
708
800
  push(1, buildItem(
709
801
  ch,
710
802
  { kind: "class", slug: entity.slug, level: 1 },
@@ -715,7 +807,7 @@ function buildDecisionLedger(resolved, ctx) {
715
807
  ownerBare,
716
808
  effective
717
809
  ));
718
- });
810
+ }
719
811
  }
720
812
  let sawAuthoredSubclass = false;
721
813
  const staged = [];
@@ -735,7 +827,7 @@ function buildDecisionLedger(resolved, ctx) {
735
827
  staged.push({ level: atLvl, item, rank: authoredRank(src.kind, atLvl, rf.feature), seq: staged.length });
736
828
  };
737
829
  const emitChoices = (list, atLvl, name, description) => {
738
- var _a3;
830
+ var _a3, _b3;
739
831
  for (const ch of list) {
740
832
  if (ch.kind === "select-entity" && ch.entity_type === "subclass") {
741
833
  sawAuthoredSubclass = true;
@@ -753,7 +845,7 @@ function buildDecisionLedger(resolved, ctx) {
753
845
  src,
754
846
  atLvl,
755
847
  name,
756
- readAt(atLvl),
848
+ (_b3 = legacyAsiReader(emitted, atLvl)) != null ? _b3 : readAt(atLvl),
757
849
  ctx,
758
850
  ownerBare,
759
851
  effective,
@@ -800,22 +892,22 @@ function buildDecisionLedger(resolved, ctx) {
800
892
  }
801
893
  emitChoices(list, copy.level, copy.name, copy.description);
802
894
  };
803
- for (const copy of (_k2 = rf.foldedFrom) != null ? _k2 : []) emitCopy({ ...copy, prose: copy.description });
895
+ for (const copy of (_j2 = rf.foldedFrom) != null ? _j2 : []) emitCopy({ ...copy, prose: copy.description });
804
896
  emitCopy({
805
897
  level: lvl,
806
898
  name: rf.feature.name,
807
899
  description: rf.feature.description,
808
900
  // The card prose: the description, else the entries joined. `||`, not `??`, so an EMPTY description
809
901
  // 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),
902
+ prose: rf.feature.description || (((_k2 = rf.feature.entries) == null ? void 0 : _k2.length) ? rf.feature.entries.join("\n\n") : void 0),
811
903
  choices: rf.feature.choices
812
904
  });
813
905
  }
814
906
  staged.sort((a, b) => a.rank - b.rank || a.seq - b.seq);
815
907
  for (const s of staged) push(s.level, s.item);
816
- const subclassLevel = (_m2 = entity.subclass_level) != null ? _m2 : null;
908
+ const subclassLevel = (_l2 = entity.subclass_level) != null ? _l2 : null;
817
909
  if (subclassLevel != null && subclassLevel <= c.level && !sawAuthoredSubclass) {
818
- const featureName = (_n = entity.subclass_feature_name) != null ? _n : "Subclass";
910
+ const featureName = (_m2 = entity.subclass_feature_name) != null ? _m2 : "Subclass";
819
911
  const synthChoice = {
820
912
  kind: "select-entity",
821
913
  id: "subclass",
@@ -851,12 +943,23 @@ function buildDecisionLedger(resolved, ctx) {
851
943
  { kind: "class", slug: entity.slug, level: pool.anchorLevel },
852
944
  pool.anchorLevel,
853
945
  pool.label,
854
- () => pool.selected.map((e) => e.slug),
946
+ () => pool.selected.map((e) => e.key),
855
947
  ctx,
856
948
  ownerBare,
857
949
  effective
858
950
  );
859
951
  item.pool = { id: pool.id, anchorLevel: pool.anchorLevel };
952
+ const repeatables = pool.available.filter((e) => e.entity.repeatable === true);
953
+ if (repeatables.length > 0) {
954
+ item.repeatable = repeatables.map((e) => {
955
+ var _a3, _b3;
956
+ return {
957
+ noteSlug: e.slug,
958
+ itemChoice: (_a3 = e.entity.item_choice) != null ? _a3 : "learn",
959
+ rows: resolveOptionRows(e.entity, ctx.registry, { edition: resolved.definition.edition, isEntityVisible: (_b3 = ctx.isEntityVisible) == null ? void 0 : _b3.bind(ctx) })
960
+ };
961
+ });
962
+ }
860
963
  for (const o of item.options) if (strandedSlugs.has(o.value)) o.stranded = true;
861
964
  push(pool.anchorLevel, item);
862
965
  }
@@ -865,7 +968,7 @@ function buildDecisionLedger(resolved, ctx) {
865
968
  if (!category) {
866
969
  warnOnce(
867
970
  `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`
971
+ `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
972
  );
870
973
  continue;
871
974
  }
@@ -875,7 +978,7 @@ function buildDecisionLedger(resolved, ctx) {
875
978
  const lvl = Number.parseInt(key, 10);
876
979
  if (!Number.isInteger(lvl) || String(lvl) !== key) continue;
877
980
  if (lvl > c.level) continue;
878
- if (((_p = byLevel.get(lvl)) != null ? _p : []).some((i) => i.key === "feat")) {
981
+ if (((_o = byLevel.get(lvl)) != null ? _o : []).some((i) => i.key === "feat")) {
879
982
  warnOnce(
880
983
  `feat-progression-collision:${entity.slug}:${lvl}`,
881
984
  `Archivist: "${entry.name}" on "${entity.slug}" would add a second feat pick at level ${lvl}, where one already exists; skipping`
@@ -947,10 +1050,10 @@ function buildDecisionLedger(resolved, ctx) {
947
1050
  if (resolved.background) {
948
1051
  const bare = bareEntitySlug(resolved.background.slug);
949
1052
  pushOrigin(
950
- resolved.background.choices,
1053
+ [...(_l = resolved.background.choices) != null ? _l : [], ...backgroundToolPicks(resolved.background).decisions],
951
1054
  "background",
952
1055
  { kind: "background", slug: resolved.background.slug },
953
- (_l = resolved.background.name) != null ? _l : "Background",
1056
+ (_m = resolved.background.name) != null ? _m : "Background",
954
1057
  bare,
955
1058
  resolved.background.description
956
1059
  );
@@ -973,7 +1076,7 @@ function buildDecisionLedger(resolved, ctx) {
973
1076
  if (originFeat) {
974
1077
  const featSource = { kind: "feat", slug: originFeat.feat.slug };
975
1078
  const featBare = bareEntitySlug(originFeat.feat.slug);
976
- for (const ch of (_m = originFeat.feat.choices) != null ? _m : []) {
1079
+ for (const ch of (_n = originFeat.feat.choices) != null ? _n : []) {
977
1080
  origin.push(buildItem(
978
1081
  ch,
979
1082
  featSource,
@@ -988,6 +1091,18 @@ function buildDecisionLedger(resolved, ctx) {
988
1091
  }
989
1092
  }
990
1093
  }
1094
+ for (const ch of equipmentDecisions(resolved.background.equipment, resolved.background.slug)) {
1095
+ origin.push(buildItem(
1096
+ ch,
1097
+ { kind: "background", slug: resolved.background.slug },
1098
+ 0,
1099
+ "Starting Equipment",
1100
+ originRead("background"),
1101
+ ctx,
1102
+ bare,
1103
+ effective
1104
+ ));
1105
+ }
991
1106
  }
992
1107
  return { classes, origin };
993
1108
  }
@@ -1014,7 +1129,10 @@ function resolveOriginFeat(entities, originFeatRef, ownerSlug) {
1014
1129
  const sameCompendiumTail = (s) => prefix ? feats.find((f) => f.slug.startsWith(`${prefix}_feat_`) && f.slug.endsWith(`_${s}`)) : void 0;
1015
1130
  const anyTail = (s) => feats.find((f) => f.slug.endsWith(`_${s}`));
1016
1131
  const tiers = [
1017
- () => feats.find((f) => f.slug === slug),
1132
+ () => {
1133
+ var _a2;
1134
+ return (_a2 = feats.find((f) => f.slug === inner)) != null ? _a2 : feats.find((f) => f.slug === slug);
1135
+ },
1018
1136
  // 1 exact slug
1019
1137
  () => byPath(inner),
1020
1138
  // 2 PATH
@@ -1186,6 +1304,8 @@ var PCResolver = class {
1186
1304
  };
1187
1305
  resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities, featureResources);
1188
1306
  resolvedCharacter.resources = resolveResourceIndex(resolvedCharacter);
1307
+ const infusions = buildInfusionContext(character, resolvedCharacter, this.entities);
1308
+ if (infusions) resolvedCharacter.infusions = infusions;
1189
1309
  return { character: resolvedCharacter, warnings };
1190
1310
  }
1191
1311
  };
@@ -1622,6 +1742,7 @@ export {
1622
1742
  collectResolvedFeatures,
1623
1743
  collectProficiencyGrants,
1624
1744
  __matchesFilterForTest,
1745
+ __categoryToEntitySelectForTest,
1625
1746
  collectChosenProficiencies,
1626
1747
  computeEffectiveProficiencies,
1627
1748
  collectChosenAbilityPoints,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  optionalFeatureEntitySchema
3
- } from "./chunk-MQ747UCR.js";
3
+ } from "./chunk-JNNNBEUH.js";
4
4
 
5
5
  // src/optional-feature/optional-feature.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raceEntitySchema
3
- } from "./chunk-V6LJFP4R.js";
3
+ } from "./chunk-X7VZY4ON.js";
4
4
  import {
5
5
  aliasFeatureListActionCost
6
6
  } from "./chunk-4U55VNC7.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backgroundEntitySchema
3
- } from "./chunk-JCEV7KYN.js";
3
+ } from "./chunk-MUIU7RZ3.js";
4
4
 
5
5
  // src/background/background.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -174,8 +174,9 @@ var containerCapacitySchema = z.strictObject({
174
174
  });
175
175
  var containerSchema = z.object({
176
176
  capacity_weight: z.number().optional(),
177
- weightless: z.boolean().optional(),
177
+ holds_items: z.boolean().optional(),
178
178
  pack_contents: z.array(z.string()).optional(),
179
+ pack_atomic: z.boolean().optional(),
179
180
  capacity: containerCapacitySchema.optional()
180
181
  });
181
182
  var lightSchema = z.object({
@@ -206,6 +207,7 @@ var itemEntitySchema = z.object({
206
207
  light: z.union([lightSchema, z.array(lightEntrySchema).nonempty()]).optional(),
207
208
  cursed: z.boolean().optional(),
208
209
  sentient: z.boolean().optional(),
210
+ tattoo: z.boolean().optional(),
209
211
  // 54 docs carry a LIST of classes that may use the item as a focus
210
212
  // (e.g. ["Druid", "Ranger"]); 26 carry a single string, 2 a boolean.
211
213
  focus: z.union([z.boolean(), z.string(), z.array(z.string())]).optional(),
@@ -254,6 +256,8 @@ var itemEntitySchema = z.object({
254
256
  // Carried by all 6,046 converter item docs, ALWAYS as the empty string today:
255
257
  // `.min(1)` is forbidden here.
256
258
  rendering_hint: z.string().optional(),
259
+ // The icon key (I3): a key of the renderer's icon set or a Lucide name; itemIconKey reads it first.
260
+ icon: z.string().optional(),
257
261
  has_fluff: z.boolean().optional(),
258
262
  has_fluff_images: z.boolean().optional(),
259
263
  // Already `.optional()` at its source — never append a second one.
@@ -269,5 +273,6 @@ var itemEntitySchema = z.object({
269
273
 
270
274
  export {
271
275
  conditionSchema,
276
+ containerSchema,
272
277
  itemEntitySchema
273
278
  };