@archivist-gg/dnd5e 0.3.13 → 0.3.19

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 (122) hide show
  1. package/dist/background/background.codec.js +5 -5
  2. package/dist/background/background.parser.js +4 -4
  3. package/dist/background/background.schema.js +3 -3
  4. package/dist/chunk-3LWMFYKS.js +57 -0
  5. package/dist/{chunk-4SLOJXJ4.js → chunk-3SFGWFVQ.js} +1 -1
  6. package/dist/{chunk-Q6UOHCCT.js → chunk-3SIUAFDQ.js} +1 -1
  7. package/dist/chunk-4J6DS3EJ.js +30 -0
  8. package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
  9. package/dist/{chunk-4BZ47YJ4.js → chunk-64VNLMSG.js} +59 -16
  10. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  11. package/dist/{chunk-Q5XTEYHD.js → chunk-7XJHI5H5.js} +16 -9
  12. package/dist/{chunk-FPGXFD2M.js → chunk-B2QHP6UF.js} +2 -2
  13. package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
  14. package/dist/{chunk-MC5YJ4PB.js → chunk-BJGOSM76.js} +1 -1
  15. package/dist/{chunk-VDPRBUU5.js → chunk-BJWSOQUC.js} +1 -1
  16. package/dist/{chunk-QEO7LYTG.js → chunk-EJD47JHL.js} +181 -93
  17. package/dist/{chunk-X7VZY4ON.js → chunk-FJYBLXV7.js} +2 -2
  18. package/dist/{chunk-QSBJ6NEM.js → chunk-FMUD3LDV.js} +1 -1
  19. package/dist/{chunk-LVX6TQ3R.js → chunk-FMYWORTP.js} +2 -2
  20. package/dist/{chunk-PX25R3DK.js → chunk-FVXLP7P3.js} +1 -1
  21. package/dist/{chunk-XJRNEQPD.js → chunk-GVWEFECG.js} +1 -1
  22. package/dist/{chunk-R2V5LN3I.js → chunk-HY2SRSVM.js} +1 -1
  23. package/dist/{chunk-ZMJQUZQH.js → chunk-JXWNXIIZ.js} +24 -0
  24. package/dist/chunk-KR6JF3BF.js +14 -0
  25. package/dist/{chunk-DPM6V7YX.js → chunk-LICYHPI3.js} +82 -6
  26. package/dist/{chunk-J53ERF6K.js → chunk-LNT64X7C.js} +1 -1
  27. package/dist/{chunk-RQNJXADQ.js → chunk-NKZMQV2C.js} +1 -1
  28. package/dist/{chunk-UE6LKRXQ.js → chunk-NTU2DXNG.js} +24 -1
  29. package/dist/{chunk-T5GZBKIQ.js → chunk-NXUY5XQN.js} +168 -33
  30. package/dist/{chunk-M2OWVAZG.js → chunk-NZO7M5GV.js} +1 -1
  31. package/dist/{chunk-BRGLDWUP.js → chunk-OOL4KMG2.js} +7 -3
  32. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  33. package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
  34. package/dist/{chunk-MUIU7RZ3.js → chunk-QFWQKS3M.js} +1 -1
  35. package/dist/chunk-RD2MID3M.js +292 -0
  36. package/dist/chunk-TRBGC4ZG.js +112 -0
  37. package/dist/{chunk-PLOSP4XQ.js → chunk-UJCK5IW4.js} +11 -2
  38. package/dist/{chunk-5KGV6TF7.js → chunk-UNYR5GLI.js} +1 -1
  39. package/dist/{chunk-JNNNBEUH.js → chunk-VN5DLURA.js} +7 -2
  40. package/dist/{chunk-EL6QPF6I.js → chunk-VP5TIXDK.js} +1 -1
  41. package/dist/chunk-WOHX7YTH.js +165 -0
  42. package/dist/{chunk-G7ED5H2G.js → chunk-XTEFPUSN.js} +1 -1
  43. package/dist/{chunk-6ZY3U6BQ.js → chunk-ZOWKWMD2.js} +1 -1
  44. package/dist/class/class.codec.js +6 -6
  45. package/dist/class/class.parser.js +5 -5
  46. package/dist/class/class.schema.d.ts +8 -0
  47. package/dist/class/class.schema.js +4 -4
  48. package/dist/class/class.standard-array.d.ts +16 -0
  49. package/dist/class/class.standard-array.js +20 -0
  50. package/dist/class/class.types.d.ts +3 -0
  51. package/dist/dnd/amount-text.d.ts +42 -0
  52. package/dist/dnd/amount-text.js +15 -0
  53. package/dist/dnd/resource-words.d.ts +4 -0
  54. package/dist/dnd/resource-words.js +30 -0
  55. package/dist/entities/registry-revision.d.ts +25 -0
  56. package/dist/entities/registry-revision.js +10 -0
  57. package/dist/feat/feat.codec.js +5 -5
  58. package/dist/feat/feat.parser.js +4 -4
  59. package/dist/feat/feat.schema.d.ts +34 -0
  60. package/dist/feat/feat.schema.js +3 -3
  61. package/dist/index.js +4027 -383
  62. package/dist/monster/monster.parser.js +5 -5
  63. package/dist/monster/monster.schema.js +4 -4
  64. package/dist/optional-feature/optional-feature.codec.js +5 -4
  65. package/dist/optional-feature/optional-feature.parser.js +4 -3
  66. package/dist/optional-feature/optional-feature.schema.d.ts +35 -0
  67. package/dist/optional-feature/optional-feature.schema.js +3 -2
  68. package/dist/pc/pc.additional-spells.d.ts +16 -2
  69. package/dist/pc/pc.additional-spells.js +5 -2
  70. package/dist/pc/pc.amount-bindings.d.ts +41 -0
  71. package/dist/pc/pc.amount-bindings.js +28 -0
  72. package/dist/pc/pc.decision-engine.d.ts +11 -0
  73. package/dist/pc/pc.decision-engine.js +20 -16
  74. package/dist/pc/pc.entry-overrides.js +3 -2
  75. package/dist/pc/pc.equipment.js +7 -5
  76. package/dist/pc/pc.feature-effects.d.ts +21 -2
  77. package/dist/pc/pc.feature-effects.js +15 -2
  78. package/dist/pc/pc.infusions.d.ts +9 -1
  79. package/dist/pc/pc.infusions.js +3 -2
  80. package/dist/pc/pc.member-choices.d.ts +122 -0
  81. package/dist/pc/pc.member-choices.js +39 -0
  82. package/dist/pc/pc.parser.js +1 -1
  83. package/dist/pc/pc.pools.d.ts +7 -0
  84. package/dist/pc/pc.pools.js +5 -2
  85. package/dist/pc/pc.proficiencies.d.ts +14 -0
  86. package/dist/pc/pc.proficiencies.js +72 -22
  87. package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
  88. package/dist/pc/pc.proficiency-normalize.js +9 -3
  89. package/dist/pc/pc.proficiency-query.js +7 -1
  90. package/dist/pc/pc.recalc.js +212 -99
  91. package/dist/pc/pc.resolver.d.ts +6 -2
  92. package/dist/pc/pc.resolver.js +16 -14
  93. package/dist/pc/pc.rest.js +4 -3
  94. package/dist/pc/pc.schema.d.ts +48 -2
  95. package/dist/pc/pc.schema.js +1 -1
  96. package/dist/pc/pc.spell-scope.d.ts +22 -0
  97. package/dist/pc/pc.tool-picks.js +2 -2
  98. package/dist/pc/pc.types.d.ts +110 -6
  99. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  100. package/dist/pc/pool-layout.d.ts +3 -9
  101. package/dist/pc/pool-layout.js +1 -3
  102. package/dist/pc/resource-equivalents.d.ts +44 -0
  103. package/dist/pc/spell-source.js +4 -9
  104. package/dist/race/race.codec.js +6 -6
  105. package/dist/race/race.parser.js +5 -5
  106. package/dist/race/race.schema.js +4 -4
  107. package/dist/schemas/choice-schema.js +2 -2
  108. package/dist/schemas/feature-effect-schema.d.ts +34 -0
  109. package/dist/schemas/feature-effect-schema.js +1 -1
  110. package/dist/schemas/feature-schema.js +3 -3
  111. package/dist/spell/spell.codec.js +2 -2
  112. package/dist/spell/spell.parser.d.ts +6 -1
  113. package/dist/spell/spell.parser.js +5 -3
  114. package/dist/spell/spell.types.d.ts +10 -0
  115. package/dist/subclass/subclass.codec.js +6 -6
  116. package/dist/subclass/subclass.parser.js +5 -5
  117. package/dist/subclass/subclass.schema.js +4 -4
  118. package/dist/types/choice.d.ts +7 -2
  119. package/dist/types/feature-effect.d.ts +24 -0
  120. package/dist/types/optional-feature.types.d.ts +6 -1
  121. package/dist/types/selection-pool.d.ts +2 -2
  122. package/package.json +1 -1
@@ -9,21 +9,11 @@ import {
9
9
  classToolPicks,
10
10
  expandToolPool,
11
11
  toolCategoriesOf
12
- } from "./chunk-H5PTKYXL.js";
13
- import {
14
- resolveFeatureResources,
15
- resolveResourceIndex
16
- } from "./chunk-KSTV7J2U.js";
17
- import {
18
- attributeUnclassedSpell,
19
- effectiveSpellcastingAbility,
20
- normalizeKnownSpell,
21
- resolveSpellcasting
22
- } from "./chunk-DPUJDOKV.js";
12
+ } from "./chunk-QA3YCOML.js";
23
13
  import {
24
14
  assembleEffectFeatures,
25
15
  collectProficiencyEffectGrants
26
- } from "./chunk-4BZ47YJ4.js";
16
+ } from "./chunk-64VNLMSG.js";
27
17
  import {
28
18
  ALL_LANGUAGES,
29
19
  ALL_SKILL_SLUGS,
@@ -33,13 +23,41 @@ import {
33
23
  humanizeProficiency,
34
24
  proficiencyLabel,
35
25
  toProfSlug
36
- } from "./chunk-RLGQ5PZZ.js";
26
+ } from "./chunk-5JXFJ5AU.js";
27
+ import {
28
+ featOfSite,
29
+ featPickSites,
30
+ isTargetChoice,
31
+ memberChoices,
32
+ poolMemberSites,
33
+ poolMemberSitesOf,
34
+ raceFeatSites
35
+ } from "./chunk-RD2MID3M.js";
36
+ import {
37
+ collectAdditionalSpells
38
+ } from "./chunk-LICYHPI3.js";
37
39
  import {
40
+ resolveFeatureResources,
41
+ resolveResourceIndex
42
+ } from "./chunk-KSTV7J2U.js";
43
+ import {
44
+ attributeUnclassedSpell,
45
+ effectiveSpellcastingAbility,
46
+ normalizeKnownSpell,
47
+ resolveSpellcasting
48
+ } from "./chunk-DPUJDOKV.js";
49
+ import {
50
+ convertibleLegacyAsi,
51
+ flattenAsiOrFeat,
52
+ resolveLegacyAsiFeat
53
+ } from "./chunk-LBEXLN6I.js";
54
+ import {
55
+ bindFeatureConsumers,
38
56
  buildInfusionContext,
39
57
  resolveAllPools,
40
58
  resolveOptionRows,
41
59
  strandedPicks
42
- } from "./chunk-T5GZBKIQ.js";
60
+ } from "./chunk-NXUY5XQN.js";
43
61
  import {
44
62
  countColumnsFor,
45
63
  readTableColumn
@@ -47,35 +65,30 @@ import {
47
65
  import {
48
66
  recognizeDecision
49
67
  } from "./chunk-YHYTW5QA.js";
50
- import {
51
- collectAdditionalSpells
52
- } from "./chunk-DPM6V7YX.js";
53
- import {
54
- convertibleLegacyAsi,
55
- flattenAsiOrFeat,
56
- resolveLegacyAsiFeat
57
- } from "./chunk-LBEXLN6I.js";
58
68
  import {
59
69
  isItemEntity,
60
70
  resolveEntityForEntry
61
71
  } from "./chunk-LHZXYFBU.js";
62
- import {
63
- warnOnce
64
- } from "./chunk-LVQLWRCU.js";
65
- import {
66
- bareEntitySlug,
67
- wikilinkTailSlug
68
- } from "./chunk-ZCTUYGNV.js";
69
72
  import {
70
73
  FEATURE_TYPE_FOR_CATEGORY,
71
74
  featProgressionCategory
72
75
  } from "./chunk-YBLDE5CG.js";
76
+ import {
77
+ warnOnce
78
+ } from "./chunk-LVQLWRCU.js";
79
+ import {
80
+ bucketOf
81
+ } from "./chunk-4J6DS3EJ.js";
73
82
  import {
74
83
  isValidMaxFormula
75
84
  } from "./chunk-EVMUJYUD.js";
85
+ import {
86
+ bareEntitySlug,
87
+ wikilinkTailSlug
88
+ } from "./chunk-ZCTUYGNV.js";
76
89
  import {
77
90
  mirrorSpellShapes
78
- } from "./chunk-XY6X2APM.js";
91
+ } from "./chunk-7Q2VPKCD.js";
79
92
  import {
80
93
  ABILITY_KEYS
81
94
  } from "./chunk-WELIKLZF.js";
@@ -205,6 +218,7 @@ function matchesFilter(e, where, ownerBare) {
205
218
  if (where.list && !((_a = d.classes) != null ? _a : []).includes(where.list)) return false;
206
219
  if (where.level !== void 0 && ((_b = d.level) != null ? _b : 0) !== where.level) return false;
207
220
  if (where.edition && d.edition !== where.edition) return false;
221
+ if (where.ritual && d.ritual !== true) return false;
208
222
  if (where.weapon_category) {
209
223
  const cat = ((_c = d.category) != null ? _c : "").toLowerCase();
210
224
  if (!cat.startsWith(`${where.weapon_category}-`) && cat !== where.weapon_category) return false;
@@ -512,7 +526,7 @@ function buildSubclassItem(choice, source, level, featureName, c, ctx, ownerBare
512
526
  };
513
527
  }
514
528
  function visitProficiencyChoices(resolved, visit) {
515
- var _a, _b, _c;
529
+ var _a, _b, _c, _d, _e;
516
530
  const walk = (choices, read) => {
517
531
  for (const ch of choices != null ? choices : []) {
518
532
  visit(ch, read(ch.id));
@@ -527,8 +541,13 @@ function visitProficiencyChoices(resolved, visit) {
527
541
  const originRead = (ns) => (id) => oc[`${ns}:${id}`];
528
542
  const stripRef = (ref) => ref.replace(/^\[\[/, "").replace(/\]\]$/, "");
529
543
  const featBySlug = new Map(((_b = resolved.feats) != null ? _b : []).map((f) => [f.slug, f]));
544
+ const memberSites = poolMemberSitesOf(resolved);
545
+ const featSites = featPickSites(resolved.classes, memberSites, (ci) => {
546
+ var _a2, _b2, _c2, _d2;
547
+ return (_d2 = (_c2 = (_b2 = (_a2 = resolved.classes) == null ? void 0 : _a2[ci]) == null ? void 0 : _b2.entity) == null ? void 0 : _c2.slug) != null ? _d2 : null;
548
+ });
530
549
  resolved.classes.forEach((c, i) => {
531
- var _a2, _b2, _c2, _d;
550
+ var _a2, _b2, _c2, _d2, _e2;
532
551
  if (!c.entity) return;
533
552
  const entity = c.entity;
534
553
  const readAt = (lvl) => (id) => {
@@ -553,14 +572,13 @@ function visitProficiencyChoices(resolved, visit) {
553
572
  for (const copy of (_c2 = rf.foldedFrom) != null ? _c2 : []) walk(copy.choices, readAt(copy.level));
554
573
  walk(rf.feature.choices, readAt(rf.source.level));
555
574
  }
556
- for (const atLevel of Object.values((_d = c.choices) != null ? _d : {})) {
557
- const block = atLevel;
558
- const featRef = block == null ? void 0 : block.feat;
559
- if (!block || typeof featRef !== "string") continue;
560
- const feat = featBySlug.get(stripRef(featRef));
575
+ for (const site of featSites) {
576
+ if (site.classIndex !== i) continue;
577
+ const feat = (_e2 = featBySlug.get(stripRef(site.slug))) != null ? _e2 : featOfSite(site, (_d2 = resolved.feats) != null ? _d2 : []);
561
578
  if (!feat) continue;
562
- walk(feat.choices, (id) => block[`feat:${id}`]);
579
+ walk(feat.choices, site.read);
563
580
  }
581
+ for (const site of memberSites) if (site.classIndex === i) walk(site.choices, site.read);
564
582
  });
565
583
  if (resolved.race) {
566
584
  walk(resolved.race.choices, originRead("race"));
@@ -578,6 +596,10 @@ function visitProficiencyChoices(resolved, visit) {
578
596
  const of = featBySlug.get(resolved.originFeatSlug);
579
597
  if (of) walk(of.choices, (id) => oc[`background:feat:${id}`]);
580
598
  }
599
+ for (const site of raceFeatSites(resolved.race, oc)) {
600
+ const rf = (_e = featBySlug.get(stripRef(site.slug))) != null ? _e : ((_d = resolved.feats) != null ? _d : []).find((f) => bareEntitySlug(f.slug) === bareEntitySlug(site.slug));
601
+ if (rf) walk(rf.choices, site.read);
602
+ }
581
603
  }
582
604
  var filterToPool = (vals, pool) => pool ? vals.map((v) => matchPool(v, pool)).filter((v) => !!v) : vals;
583
605
  function collectChosenProficiencies(resolved) {
@@ -614,6 +636,14 @@ function proficiencyEntryFor(raw, vocab, origin, sources, expertise) {
614
636
  return { value, label, sources, origin, ...expertise ? { expertise: true } : {} };
615
637
  }
616
638
  function computeEffectiveProficiencies(resolved) {
639
+ const c = composeLanguageToolProficiencies(resolved);
640
+ return { languages: c.languages.kept, tools: c.tools.kept };
641
+ }
642
+ function computeSuppressedProficiencies(resolved) {
643
+ const c = composeLanguageToolProficiencies(resolved);
644
+ return { languages: c.languages.suppressed, tools: c.tools.suppressed };
645
+ }
646
+ function composeLanguageToolProficiencies(resolved) {
617
647
  const grants = collectProficiencyGrants(resolved);
618
648
  const chosen = collectChosenProficiencies(resolved);
619
649
  const build = (domain) => {
@@ -659,7 +689,9 @@ function computeEffectiveProficiencies(resolved) {
659
689
  }
660
690
  }
661
691
  const suppressed = /* @__PURE__ */ new Set([...removes.map((r) => toProfSlug(r)), ...triSuppressed]);
662
- return [...byValue.values()].filter((e) => !suppressed.has(toProfSlug(e.value))).sort((a, b) => a.label < b.label ? -1 : a.label > b.label ? 1 : 0);
692
+ const byLabel = (a, b) => a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
693
+ const kept = [...byValue.values()].filter((e) => !suppressed.has(toProfSlug(e.value))).sort(byLabel);
694
+ return { kept, suppressed: [...byValue.values()].filter((e) => suppressed.has(toProfSlug(e.value))).sort(byLabel) };
663
695
  };
664
696
  return { languages: build("languages"), tools: build("tools") };
665
697
  }
@@ -961,6 +993,28 @@ function buildDecisionLedger(resolved, ctx) {
961
993
  });
962
994
  }
963
995
  for (const o of item.options) if (strandedSlugs.has(o.value)) o.stranded = true;
996
+ const memberChildren = [];
997
+ for (const entry of pool.selected) {
998
+ for (const ch of memberChoices(entry.entity)) {
999
+ const child = buildItem(
1000
+ ch,
1001
+ { kind: "class", slug: entity.slug, level: pool.anchorLevel },
1002
+ pool.anchorLevel,
1003
+ entry.entity.name,
1004
+ readAt(pool.anchorLevel),
1005
+ ctx,
1006
+ ownerBare,
1007
+ effective,
1008
+ { keyPrefix: `${pool.id}:${entry.key}:` }
1009
+ );
1010
+ child.parentLabel = entry.entity.name;
1011
+ memberChildren.push(child);
1012
+ }
1013
+ }
1014
+ if (memberChildren.length) {
1015
+ item.children = memberChildren;
1016
+ if (item.status === "resolved" && memberChildren.some((c2) => c2.status !== "resolved")) item.status = "partial";
1017
+ }
964
1018
  push(pool.anchorLevel, item);
965
1019
  }
966
1020
  for (const entry of featProgressions) {
@@ -1124,7 +1178,7 @@ function resolveOriginFeat(entities, originFeatRef, ownerSlug) {
1124
1178
  const baseSlug = base && base !== rawTail ? wikilinkTailSlug(`[[${base}]]`) : null;
1125
1179
  const baseInner = baseSlug ? inner.slice(0, inner.length - rawTail.length) + base : null;
1126
1180
  const prefix = (ownerSlug == null ? void 0 : ownerSlug.includes("_")) ? ownerSlug.slice(0, ownerSlug.indexOf("_")) : null;
1127
- const feats = entities.search("", "feat", Number.POSITIVE_INFINITY);
1181
+ const feats = bucketOf(entities, "feat");
1128
1182
  const byPath = (p) => feats.find((f) => f.filePath === `${p}.md` || f.filePath.endsWith(`/${p}.md`));
1129
1183
  const sameCompendiumTail = (s) => prefix ? feats.find((f) => f.slug.startsWith(`${prefix}_feat_`) && f.slug.endsWith(`_${s}`)) : void 0;
1130
1184
  const anyTail = (s) => feats.find((f) => f.slug.endsWith(`_${s}`));
@@ -1168,7 +1222,7 @@ var PCResolver = class {
1168
1222
  return this.entities;
1169
1223
  }
1170
1224
  resolve(character) {
1171
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
1225
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1172
1226
  const warnings = [];
1173
1227
  const lookup = (rawRef, type) => {
1174
1228
  const slug = stripSlug(rawRef);
@@ -1189,14 +1243,19 @@ var PCResolver = class {
1189
1243
  subclass: lookup(c.subclass, "subclass"),
1190
1244
  choices: c.choices
1191
1245
  }));
1246
+ const memberSites = poolMemberSites(classes, this.entities);
1247
+ const classSlugOf = (i) => {
1248
+ var _a2, _b2, _c2, _d2, _e2;
1249
+ 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);
1250
+ };
1251
+ const featSites = featPickSites(character.class, memberSites, classSlugOf);
1192
1252
  const feats = [];
1193
1253
  const featVia = /* @__PURE__ */ new Map();
1194
- for (const pick of collectFeatPicks(character)) {
1195
- const f = lookup(`[[${pick.slug}]]`, "feat");
1254
+ for (const site of featSites) {
1255
+ const f = lookup(`[[${site.slug}]]`, "feat");
1196
1256
  if (!f) continue;
1197
- feats.push(f);
1198
- 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);
1199
- if (classSlug && pick.level !== null && !featVia.has(f.slug)) featVia.set(f.slug, { kind: "class", slug: classSlug, level: pick.level });
1257
+ if (!feats.some((x) => x.slug === f.slug)) feats.push(f);
1258
+ if (site.via && !featVia.has(f.slug)) featVia.set(f.slug, site.via);
1200
1259
  }
1201
1260
  const originFeat = (background == null ? void 0 : background.origin_feat) ? resolveOriginFeat(this.entities, background.origin_feat, background.slug) : null;
1202
1261
  const originFeatSlug = originFeat == null ? void 0 : originFeat.feat.slug;
@@ -1206,9 +1265,17 @@ var PCResolver = class {
1206
1265
  if (originFeat && background && !featVia.has(originFeat.feat.slug)) {
1207
1266
  featVia.set(originFeat.feat.slug, { kind: "background", slug: background.slug });
1208
1267
  }
1268
+ const raceFeats = [];
1269
+ for (const site of raceFeatSites(race, character.origin_choices)) {
1270
+ const f = lookup(`[[${site.slug}]]`, "feat");
1271
+ if (!f) continue;
1272
+ raceFeats.push({ feat: f, read: site.read });
1273
+ if (!feats.some((x) => x.slug === f.slug)) feats.push(f);
1274
+ if (!featVia.has(f.slug)) featVia.set(f.slug, site.via);
1275
+ }
1209
1276
  const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
1210
1277
  const features = collectResolvedFeatures(race, classes, background, feats, featVia);
1211
- const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background);
1278
+ const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background, memberSites);
1212
1279
  features.push(...extraFeatures);
1213
1280
  features.push(...collectAdditionalFeatures(character, this.entities, warnings));
1214
1281
  const casters = [];
@@ -1218,7 +1285,7 @@ var PCResolver = class {
1218
1285
  if (profile && classSlug != null) casters.push({ classSlug, spellList: profile.spellList });
1219
1286
  });
1220
1287
  const spells = [];
1221
- for (const raw of (_f = character.spells.known) != null ? _f : []) {
1288
+ for (const raw of (_a = character.spells.known) != null ? _a : []) {
1222
1289
  const n = normalizeKnownSpell(raw);
1223
1290
  const reg = this.entities.getByTypeAndSlug("spell", n.slug);
1224
1291
  if (!reg) {
@@ -1226,45 +1293,47 @@ var PCResolver = class {
1226
1293
  continue;
1227
1294
  }
1228
1295
  const entity = mirrorSpellShapes(reg.data);
1229
- const isCantrip = ((_g = entity.level) != null ? _g : 0) === 0;
1230
- const classSlug = (_h = n.classSlug) != null ? _h : attributeUnclassedSpell(entity.classes, casters, this.entities);
1231
- const prep = isCantrip || n.alwaysPrepared ? true : (_i = n.preparedFlag) != null ? _i : false;
1296
+ const isCantrip = ((_b = entity.level) != null ? _b : 0) === 0;
1297
+ const classSlug = (_c = n.classSlug) != null ? _c : attributeUnclassedSpell(entity.classes, casters, this.entities);
1298
+ const prep = isCantrip || n.alwaysPrepared ? true : (_d = n.preparedFlag) != null ? _d : false;
1232
1299
  spells.push({ entity, slug: n.slug, classSlug, source: n.source, prepared: prep, alwaysPrepared: n.alwaysPrepared, persisted: true });
1233
1300
  }
1234
- const oc = (_j = character.origin_choices) != null ? _j : {};
1301
+ const oc = (_e = character.origin_choices) != null ? _e : {};
1235
1302
  if (originFeat) {
1236
1303
  const read = (childId) => oc[`background:feat:${childId}`];
1237
1304
  spells.push(...collectFeatGrantedSpells(originFeat.feat, read, this.entities, warnings));
1238
1305
  }
1239
- for (const entry of character.class) {
1240
- for (const atLevel of Object.values((_k = entry.choices) != null ? _k : {})) {
1241
- const featRef = atLevel.feat;
1242
- if (typeof featRef !== "string") continue;
1243
- const slug = stripSlug(featRef);
1244
- if (!slug) continue;
1245
- const reg = this.entities.getByTypeAndSlug("feat", slug);
1246
- if (!reg) continue;
1247
- const feat = reg.data;
1248
- const read = (childId) => atLevel[`feat:${childId}`];
1249
- spells.push(...collectFeatGrantedSpells(feat, read, this.entities, warnings));
1306
+ for (const rf of raceFeats) spells.push(...collectFeatGrantedSpells(rf.feat, rf.read, this.entities, warnings));
1307
+ for (const site of featSites) {
1308
+ const reg = this.entities.getByTypeAndSlug("feat", site.slug);
1309
+ if (!reg) continue;
1310
+ const feat = reg.data;
1311
+ spells.push(...collectFeatGrantedSpells(feat, site.read, this.entities, warnings));
1312
+ }
1313
+ for (const site of memberSites) {
1314
+ const classSlug = stripSlug((_g = (_f = character.class[site.classIndex]) == null ? void 0 : _f.name) != null ? _g : null);
1315
+ for (const ch of site.choices) {
1316
+ if (ch.kind !== "select-entity" || ch.entity_type !== "spell" || isTargetChoice(ch)) continue;
1317
+ const v = site.read(ch.id);
1318
+ for (const ref of Array.isArray(v) ? v : typeof v === "string" ? [v] : []) {
1319
+ const slug = typeof ref === "string" ? stripSlug(ref) : null;
1320
+ if (!slug) continue;
1321
+ const reg = this.entities.getByTypeAndSlug("spell", slug);
1322
+ if (!reg) {
1323
+ warnings.push(`Spell [[${slug}]] not found in compendium.`);
1324
+ continue;
1325
+ }
1326
+ const entity = mirrorSpellShapes(reg.data);
1327
+ spells.push({ entity, slug, classSlug, source: "class", prepared: true, alwaysPrepared: true });
1328
+ }
1250
1329
  }
1251
1330
  }
1252
- const ownSpellcastingAbility = (_l = classes.map((c) => {
1331
+ const ownSpellcastingAbility = (_h = classes.map((c) => {
1253
1332
  if (!c.entity) return null;
1254
1333
  const p = resolveSpellcasting(c);
1255
1334
  return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
1256
- }).find((a) => a != null)) != null ? _l : null;
1335
+ }).find((a) => a != null)) != null ? _h : null;
1257
1336
  spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
1258
- spells.push(...collectAdditionalSpells({
1259
- race,
1260
- feats,
1261
- classes,
1262
- totalLevel,
1263
- ownAbility: ownSpellcastingAbility,
1264
- alreadyCollected: spells,
1265
- entities: this.entities,
1266
- warnings
1267
- }));
1268
1337
  const chosenMasteries = collectChosenWeaponMasteries(character);
1269
1338
  if (chosenMasteries.full.length > 0) {
1270
1339
  const target = features.find(
@@ -1296,14 +1365,29 @@ var PCResolver = class {
1296
1365
  ...originFeatSlug ? { originFeatSlug } : {},
1297
1366
  totalLevel,
1298
1367
  features,
1299
- spells: dedupeResolvedSpells(spells),
1368
+ spells: [],
1300
1369
  pools: [],
1301
1370
  resources: featureResources,
1302
1371
  weaponMasteries: chosenMasteries.bare,
1303
1372
  state: character.state
1304
1373
  };
1305
1374
  resolvedCharacter.pools = resolveAllPools(resolvedCharacter, this.entities, featureResources);
1375
+ spells.push(...collectAdditionalSpells({
1376
+ race,
1377
+ feats,
1378
+ classes,
1379
+ totalLevel,
1380
+ ownAbility: ownSpellcastingAbility,
1381
+ alreadyCollected: spells,
1382
+ entities: this.entities,
1383
+ warnings,
1384
+ // SRD-G T4: the race's lineage and spellcasting-ability picks select and qualify its spell blocks.
1385
+ raceChoice: (id) => oc[`race:${id}`],
1386
+ pools: resolvedCharacter.pools
1387
+ }));
1388
+ resolvedCharacter.spells = dedupeResolvedSpells(spells);
1306
1389
  resolvedCharacter.resources = resolveResourceIndex(resolvedCharacter);
1390
+ resolvedCharacter.features = bindFeatureConsumers(resolvedCharacter.features, resolvedCharacter.resources, classes);
1307
1391
  const infusions = buildInfusionContext(character, resolvedCharacter, this.entities);
1308
1392
  if (infusions) resolvedCharacter.infusions = infusions;
1309
1393
  return { character: resolvedCharacter, warnings };
@@ -1324,16 +1408,9 @@ function collectChosenWeaponMasteries(character) {
1324
1408
  }
1325
1409
  function collectFeatPicks(character) {
1326
1410
  const picks = /* @__PURE__ */ new Map();
1327
- character.class.forEach((c, classIndex) => {
1328
- for (const [lvl, choiceBlock] of Object.entries(c.choices)) {
1329
- const feat = choiceBlock == null ? void 0 : choiceBlock.feat;
1330
- if (typeof feat === "string") {
1331
- const s = stripSlug(feat);
1332
- const level = /^\d+$/.test(lvl) ? Number(lvl) : null;
1333
- if (s && !picks.has(s)) picks.set(s, { slug: s, classIndex, level });
1334
- }
1335
- }
1336
- });
1411
+ for (const site of featPickSites(character.class, [], () => null)) {
1412
+ if (!picks.has(site.slug)) picks.set(site.slug, { slug: site.slug, classIndex: site.classIndex, level: site.level });
1413
+ }
1337
1414
  return [...picks.values()];
1338
1415
  }
1339
1416
  function collectFeatGrantedSpells(feat, read, entities, warnings) {
@@ -1379,7 +1456,7 @@ function collectItemGrantedSpells(character, ownAbility, entities, warnings) {
1379
1456
  function dedupeResolvedSpells(spells) {
1380
1457
  const byKey = /* @__PURE__ */ new Map();
1381
1458
  for (const s of spells) {
1382
- const key = s.source === "item" ? `${s.slug}#${s.entryIndex}` : s.slug;
1459
+ const key = s.source === "item" ? `${s.slug}#${s.entryIndex}` : s.atWill ? `${s.slug}#atwill` : s.slug;
1383
1460
  const existing = byKey.get(key);
1384
1461
  if (!existing) {
1385
1462
  byKey.set(key, s);
@@ -1410,11 +1487,21 @@ function collectSpellPickSlugs(choices, read, out) {
1410
1487
  }
1411
1488
  }
1412
1489
  }
1413
- function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null) {
1414
- var _a, _b;
1490
+ function collectChosenGrantedFeatures(character, classes, registry, race = null, background = null, memberSites = []) {
1491
+ var _a, _b, _c, _d;
1415
1492
  const out = [];
1493
+ for (const site of memberSites) {
1494
+ const classSlug = (_b = (_a = classes[site.classIndex]) == null ? void 0 : _a.entity) == null ? void 0 : _b.slug;
1495
+ if (!classSlug) continue;
1496
+ const at = {};
1497
+ const grants = site.choices.filter((ch) => !isTargetChoice(ch));
1498
+ for (const ch of grants) at[ch.id] = site.read(ch.id);
1499
+ walkChoiceGrants(grants, at, (granted, suppress) => {
1500
+ out.push({ feature: granted, source: { kind: "class", slug: classSlug, level: site.level }, ...suppress ? { renderSuppressed: true } : {} });
1501
+ }, registry);
1502
+ }
1416
1503
  classes.forEach((c, i) => {
1417
- var _a2, _b2, _c, _d;
1504
+ var _a2, _b2, _c2, _d2;
1418
1505
  if (!c.entity) return;
1419
1506
  const entity = c.entity;
1420
1507
  const entry = character.class[i];
@@ -1423,7 +1510,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1423
1510
  const lvl = Number(lvlStr);
1424
1511
  if (!Number.isFinite(lvl) || lvl > entry.level) continue;
1425
1512
  const features = (_b2 = ((_a2 = entity.features_by_level) != null ? _a2 : {})[lvl]) != null ? _b2 : [];
1426
- const subFeatures = c.subclass ? (_d = ((_c = c.subclass.features_by_level) != null ? _c : {})[lvl]) != null ? _d : [] : [];
1513
+ const subFeatures = c.subclass ? (_d2 = ((_c2 = c.subclass.features_by_level) != null ? _c2 : {})[lvl]) != null ? _d2 : [] : [];
1427
1514
  for (const feature of [...features, ...subFeatures]) {
1428
1515
  walkChoiceGrants(feature.choices, atLevel, (granted, suppress) => {
1429
1516
  out.push({
@@ -1435,7 +1522,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1435
1522
  }
1436
1523
  }
1437
1524
  });
1438
- const oc = (_a = character.origin_choices) != null ? _a : {};
1525
+ const oc = (_c = character.origin_choices) != null ? _c : {};
1439
1526
  const originAt = (ns) => {
1440
1527
  const at = {};
1441
1528
  for (const [key, val] of Object.entries(oc)) {
@@ -1453,7 +1540,7 @@ function collectChosenGrantedFeatures(character, classes, registry, race = null,
1453
1540
  });
1454
1541
  };
1455
1542
  walkChoiceGrants(race.choices, atRace, emitRace, registry);
1456
- for (const trait of (_b = race.traits) != null ? _b : []) {
1543
+ for (const trait of (_d = race.traits) != null ? _d : []) {
1457
1544
  walkChoiceGrants(trait.choices, atRace, emitRace, registry);
1458
1545
  }
1459
1546
  }
@@ -1745,6 +1832,7 @@ export {
1745
1832
  __categoryToEntitySelectForTest,
1746
1833
  collectChosenProficiencies,
1747
1834
  computeEffectiveProficiencies,
1835
+ computeSuppressedProficiencies,
1748
1836
  collectChosenAbilityPoints,
1749
1837
  buildDecisionLedger
1750
1838
  };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  featureSchema
3
- } from "./chunk-FPGXFD2M.js";
3
+ } from "./chunk-B2QHP6UF.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-BRGLDWUP.js";
6
+ } from "./chunk-OOL4KMG2.js";
7
7
  import {
8
8
  additionalSpellsEntrySchema,
9
9
  imageField
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  optionalFeatureEntitySchema
3
- } from "./chunk-JNNNBEUH.js";
3
+ } from "./chunk-VN5DLURA.js";
4
4
 
5
5
  // src/optional-feature/optional-feature.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-ZJXSXVFP.js";
5
5
  import {
6
6
  choiceSchema
7
- } from "./chunk-BRGLDWUP.js";
7
+ } from "./chunk-OOL4KMG2.js";
8
8
  import {
9
9
  featureEffectSchema
10
- } from "./chunk-ZMJQUZQH.js";
10
+ } from "./chunk-JXWNXIIZ.js";
11
11
  import {
12
12
  additionalSpellsEntrySchema,
13
13
  imageField,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseOptionalFeature
3
- } from "./chunk-QSBJ6NEM.js";
3
+ } from "./chunk-FMUD3LDV.js";
4
4
 
5
5
  // src/optional-feature/optional-feature.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseClass
3
- } from "./chunk-G7ED5H2G.js";
3
+ } from "./chunk-XTEFPUSN.js";
4
4
 
5
5
  // src/class/class.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raceEntitySchema
3
- } from "./chunk-X7VZY4ON.js";
3
+ } from "./chunk-FJYBLXV7.js";
4
4
  import {
5
5
  aliasFeatureListActionCost
6
6
  } from "./chunk-4U55VNC7.js";
@@ -4,9 +4,29 @@ var abilityEnum = z.enum(["str", "dex", "con", "int", "wis", "cha"]);
4
4
  var subjectField = z.string().min(1).optional();
5
5
  var conditionField = z.string().optional();
6
6
  var weaponScopeEnum = z.enum(["weapon", "melee", "ranged", "unarmed", "finesse", "thrown"]);
7
+ var spellScopeSchema = z.object({
8
+ list: z.string().min(1).optional(),
9
+ level: z.number().int().min(0).max(9).optional(),
10
+ school: z.string().min(1).optional(),
11
+ damage_types: z.array(z.string().min(1)).min(1).optional(),
12
+ damage_type_pick: z.string().min(1).optional(),
13
+ spells: z.array(z.string().min(1)).min(1).optional(),
14
+ once: z.boolean().optional()
15
+ }).strict();
7
16
  var featureEffectSchema = z.discriminatedUnion("kind", [
8
17
  z.object({ kind: z.literal("initiative-bonus"), value: z.number().int(), condition: conditionField, subject: subjectField }),
9
18
  z.object({ kind: z.literal("skill-bonus"), skills: z.array(z.string().min(1)).nonempty(), ability: abilityEnum, minimum: z.number().int().optional(), condition: conditionField, subject: subjectField }),
19
+ // SL (2026-09-27): the `skill-bonus` twin for saving throws (Aura of Protection). `saves` absent = every save; `inactive_while` turns
20
+ // the bonus off while the character is incapacitated (any condition that disables actions) or unconscious.
21
+ z.object({
22
+ kind: z.literal("save-bonus"),
23
+ ability: abilityEnum,
24
+ minimum: z.number().int().optional(),
25
+ saves: z.array(abilityEnum).nonempty().optional(),
26
+ inactive_while: z.enum(["incapacitated", "unconscious"]).optional(),
27
+ condition: conditionField,
28
+ subject: subjectField
29
+ }),
10
30
  // Jack of All Trades / Remarkable Athlete: half the proficiency bonus, and ONLY on a skill the character is
11
31
  // not already proficient in — the "doesn't otherwise use your Proficiency Bonus" clause is the whole mechanic,
12
32
  // so it is the kind's semantics, not a field. `skills` omitted = every skill (Jack of All Trades); a list
@@ -72,6 +92,10 @@ var featureEffectSchema = z.discriminatedUnion("kind", [
72
92
  // ART-c1 Task 9b: `spell` and `all` as before, or a weapon scope, one word or an any-of list, which the engine
73
93
  // evaluates per attack row (pc.recalc.ts `riderReachesRow`). `weapon`, like an absent value, reaches every row.
74
94
  applies_to: z.union([z.enum(["spell", "all"]), weaponScopeEnum, z.array(weaponScopeEnum).min(1)]).optional(),
95
+ // The spells a `spell` or `all` rider reaches (Potent Spellcasting's Cleric cantrips, Empowered Evocation's Wizard
96
+ // evocation spells); pc.recalc.ts `spellsInScope` reads it. A `spell` rider without one reaches only the spells its
97
+ // pool member's data names (Agonizing Blast's target pick); an `all` rider without one reaches every damaging spell.
98
+ spell_scope: spellScopeSchema.optional(),
75
99
  condition: conditionField,
76
100
  subject: subjectField
77
101
  }),
@@ -0,0 +1,14 @@
1
+ // src/pc/spell-source.ts
2
+ var SPELL_SOURCE = {
3
+ class: { ownAbility: false, section: "spellbook", freeCastWhenNoSlot: false, showInPrepare: true, countsAsGranted: false },
4
+ feat: { ownAbility: true, section: "spellbook", freeCastWhenNoSlot: true, showInPrepare: true, countsAsGranted: true },
5
+ item: { ownAbility: false, section: "consumable", freeCastWhenNoSlot: false, showInPrepare: false, countsAsGranted: true },
6
+ race: { ownAbility: true, section: "spellbook", freeCastWhenNoSlot: true, showInPrepare: true, countsAsGranted: true },
7
+ domain: { ownAbility: false, section: "spellbook", freeCastWhenNoSlot: false, showInPrepare: true, countsAsGranted: false }
8
+ };
9
+ var spellSource = (s) => SPELL_SOURCE[s.source];
10
+
11
+ export {
12
+ SPELL_SOURCE,
13
+ spellSource
14
+ };