@archivist-gg/dnd5e 0.3.17 → 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 (99) 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-7QN7XSAK.js → chunk-3SFGWFVQ.js} +1 -1
  5. package/dist/{chunk-2UDSN6VW.js → chunk-3SIUAFDQ.js} +1 -1
  6. package/dist/chunk-4J6DS3EJ.js +30 -0
  7. package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
  8. package/dist/{chunk-TULQ4FFX.js → chunk-64VNLMSG.js} +31 -9
  9. package/dist/{chunk-JSM6JFZR.js → chunk-7XJHI5H5.js} +2 -2
  10. package/dist/{chunk-YD2VAHA6.js → chunk-B2QHP6UF.js} +2 -2
  11. package/dist/{chunk-AZRGFUSR.js → chunk-BJGOSM76.js} +1 -1
  12. package/dist/{chunk-JCMUVZIK.js → chunk-BJWSOQUC.js} +1 -1
  13. package/dist/{chunk-ONTGZR5T.js → chunk-EJD47JHL.js} +64 -32
  14. package/dist/{chunk-FANQVQ3D.js → chunk-FJYBLXV7.js} +2 -2
  15. package/dist/{chunk-PQZ4LM4Z.js → chunk-FMUD3LDV.js} +1 -1
  16. package/dist/{chunk-PKX6TGRE.js → chunk-FMYWORTP.js} +2 -2
  17. package/dist/{chunk-G6PBI2BU.js → chunk-FVXLP7P3.js} +1 -1
  18. package/dist/{chunk-MBX25B6D.js → chunk-GVWEFECG.js} +1 -1
  19. package/dist/{chunk-Y5VVP73C.js → chunk-HY2SRSVM.js} +1 -1
  20. package/dist/{chunk-2JSQE3DD.js → chunk-JXWNXIIZ.js} +13 -0
  21. package/dist/{chunk-VRV3NNTA.js → chunk-LICYHPI3.js} +81 -5
  22. package/dist/{chunk-LF5AGJA4.js → chunk-LNT64X7C.js} +1 -1
  23. package/dist/{chunk-6EI737IO.js → chunk-NKZMQV2C.js} +1 -1
  24. package/dist/{chunk-T6QIF5XR.js → chunk-NTU2DXNG.js} +9 -2
  25. package/dist/{chunk-3VZZDKRA.js → chunk-NXUY5XQN.js} +63 -15
  26. package/dist/{chunk-QZSWN2KT.js → chunk-NZO7M5GV.js} +1 -1
  27. package/dist/{chunk-5MXFW4O3.js → chunk-OOL4KMG2.js} +1 -1
  28. package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
  29. package/dist/{chunk-3MGQFFNE.js → chunk-QFWQKS3M.js} +1 -1
  30. package/dist/{chunk-HTYMW5CH.js → chunk-RD2MID3M.js} +67 -3
  31. package/dist/{chunk-PXVNAY7T.js → chunk-TRBGC4ZG.js} +29 -1
  32. package/dist/{chunk-UUBJCNQU.js → chunk-UJCK5IW4.js} +2 -2
  33. package/dist/{chunk-OFCSP6MT.js → chunk-UNYR5GLI.js} +1 -1
  34. package/dist/{chunk-DOXSFYLH.js → chunk-VN5DLURA.js} +2 -2
  35. package/dist/{chunk-7ATNGKBS.js → chunk-VP5TIXDK.js} +1 -1
  36. package/dist/{chunk-QH36K2PU.js → chunk-WOHX7YTH.js} +7 -0
  37. package/dist/{chunk-YKL6SHRJ.js → chunk-XTEFPUSN.js} +1 -1
  38. package/dist/{chunk-RYLXAYIQ.js → chunk-ZOWKWMD2.js} +1 -1
  39. package/dist/class/class.codec.js +6 -6
  40. package/dist/class/class.parser.js +5 -5
  41. package/dist/class/class.schema.js +4 -4
  42. package/dist/dnd/amount-text.d.ts +4 -0
  43. package/dist/dnd/amount-text.js +3 -1
  44. package/dist/entities/registry-revision.d.ts +25 -0
  45. package/dist/entities/registry-revision.js +10 -0
  46. package/dist/feat/feat.codec.js +5 -5
  47. package/dist/feat/feat.parser.js +4 -4
  48. package/dist/feat/feat.schema.d.ts +9 -0
  49. package/dist/feat/feat.schema.js +3 -3
  50. package/dist/index.js +3720 -338
  51. package/dist/monster/monster.parser.js +5 -5
  52. package/dist/monster/monster.schema.js +4 -4
  53. package/dist/optional-feature/optional-feature.codec.js +5 -5
  54. package/dist/optional-feature/optional-feature.parser.js +4 -4
  55. package/dist/optional-feature/optional-feature.schema.d.ts +9 -0
  56. package/dist/optional-feature/optional-feature.schema.js +3 -3
  57. package/dist/pc/pc.additional-spells.d.ts +16 -2
  58. package/dist/pc/pc.additional-spells.js +4 -1
  59. package/dist/pc/pc.amount-bindings.d.ts +11 -2
  60. package/dist/pc/pc.amount-bindings.js +6 -1
  61. package/dist/pc/pc.decision-engine.d.ts +8 -0
  62. package/dist/pc/pc.decision-engine.js +13 -10
  63. package/dist/pc/pc.entry-overrides.js +3 -2
  64. package/dist/pc/pc.equipment.js +5 -4
  65. package/dist/pc/pc.feature-effects.d.ts +9 -2
  66. package/dist/pc/pc.feature-effects.js +15 -2
  67. package/dist/pc/pc.infusions.js +3 -2
  68. package/dist/pc/pc.member-choices.d.ts +33 -1
  69. package/dist/pc/pc.member-choices.js +12 -5
  70. package/dist/pc/pc.parser.js +1 -1
  71. package/dist/pc/pc.pools.js +3 -2
  72. package/dist/pc/pc.proficiencies.d.ts +14 -0
  73. package/dist/pc/pc.proficiencies.js +68 -19
  74. package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
  75. package/dist/pc/pc.proficiency-normalize.js +9 -3
  76. package/dist/pc/pc.proficiency-query.js +1 -1
  77. package/dist/pc/pc.recalc.js +151 -55
  78. package/dist/pc/pc.resolver.d.ts +4 -1
  79. package/dist/pc/pc.resolver.js +10 -9
  80. package/dist/pc/pc.rest.js +3 -2
  81. package/dist/pc/pc.schema.d.ts +19 -0
  82. package/dist/pc/pc.schema.js +1 -1
  83. package/dist/pc/pc.spell-scope.d.ts +22 -0
  84. package/dist/pc/pc.tool-picks.js +2 -2
  85. package/dist/pc/pc.types.d.ts +65 -4
  86. package/dist/race/race.codec.js +6 -6
  87. package/dist/race/race.parser.js +5 -5
  88. package/dist/race/race.schema.js +4 -4
  89. package/dist/schemas/choice-schema.js +2 -2
  90. package/dist/schemas/feature-effect-schema.d.ts +9 -0
  91. package/dist/schemas/feature-effect-schema.js +1 -1
  92. package/dist/schemas/feature-schema.js +3 -3
  93. package/dist/subclass/subclass.codec.js +6 -6
  94. package/dist/subclass/subclass.parser.js +5 -5
  95. package/dist/subclass/subclass.schema.js +4 -4
  96. package/dist/types/choice.d.ts +2 -2
  97. package/dist/types/feature-effect.d.ts +18 -0
  98. package/dist/types/optional-feature.types.d.ts +1 -1
  99. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  backgroundCodec
3
- } from "../chunk-2UDSN6VW.js";
4
- import "../chunk-6EI737IO.js";
5
- import "../chunk-3MGQFFNE.js";
3
+ } from "../chunk-3SIUAFDQ.js";
4
+ import "../chunk-NKZMQV2C.js";
5
+ import "../chunk-QFWQKS3M.js";
6
6
  import "../chunk-IN4M4X7P.js";
7
- import "../chunk-5MXFW4O3.js";
8
- import "../chunk-2JSQE3DD.js";
7
+ import "../chunk-OOL4KMG2.js";
8
+ import "../chunk-JXWNXIIZ.js";
9
9
  import "../chunk-UA5KS2PL.js";
10
10
  export {
11
11
  backgroundCodec
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  parseBackground
3
- } from "../chunk-6EI737IO.js";
4
- import "../chunk-3MGQFFNE.js";
3
+ } from "../chunk-NKZMQV2C.js";
4
+ import "../chunk-QFWQKS3M.js";
5
5
  import "../chunk-IN4M4X7P.js";
6
- import "../chunk-5MXFW4O3.js";
7
- import "../chunk-2JSQE3DD.js";
6
+ import "../chunk-OOL4KMG2.js";
7
+ import "../chunk-JXWNXIIZ.js";
8
8
  import "../chunk-UA5KS2PL.js";
9
9
  export {
10
10
  parseBackground
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  backgroundEntitySchema,
3
3
  langProfSchema
4
- } from "../chunk-3MGQFFNE.js";
4
+ } from "../chunk-QFWQKS3M.js";
5
5
  import "../chunk-IN4M4X7P.js";
6
- import "../chunk-5MXFW4O3.js";
7
- import "../chunk-2JSQE3DD.js";
6
+ import "../chunk-OOL4KMG2.js";
7
+ import "../chunk-JXWNXIIZ.js";
8
8
  import "../chunk-UA5KS2PL.js";
9
9
  export {
10
10
  backgroundEntitySchema,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featEntitySchema
3
- } from "./chunk-PKX6TGRE.js";
3
+ } from "./chunk-FMYWORTP.js";
4
4
 
5
5
  // src/feat/feat.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBackground
3
- } from "./chunk-6EI737IO.js";
3
+ } from "./chunk-NKZMQV2C.js";
4
4
 
5
5
  // src/background/background.codec.ts
6
6
  import yaml from "js-yaml";
@@ -0,0 +1,30 @@
1
+ // src/entities/registry-revision.ts
2
+ function registryRevision(registry) {
3
+ const r = registry == null ? void 0 : registry.revision;
4
+ return typeof r === "number" ? r : void 0;
5
+ }
6
+ function memoByRevision(cache, registry, compute) {
7
+ const revision = registryRevision(registry);
8
+ if (revision === void 0) return compute();
9
+ const hit = cache.get(registry);
10
+ if (hit && hit.revision === revision) return hit.value;
11
+ const value = compute();
12
+ cache.set(registry, { revision, value });
13
+ return value;
14
+ }
15
+ var BUCKETS = /* @__PURE__ */ new WeakMap();
16
+ function bucketOf(registry, type) {
17
+ const byType = memoByRevision(BUCKETS, registry, () => /* @__PURE__ */ new Map());
18
+ let bucket = byType.get(type);
19
+ if (!bucket) {
20
+ bucket = registry.search("", type, Number.POSITIVE_INFINITY);
21
+ byType.set(type, bucket);
22
+ }
23
+ return bucket;
24
+ }
25
+
26
+ export {
27
+ registryRevision,
28
+ memoByRevision,
29
+ bucketOf
30
+ };
@@ -7,10 +7,20 @@ var proficiencyLabel = (raw) => {
7
7
  if (!/\s/.test(text) || text === text.toLowerCase()) return humanizeProficiency(toProfSlug(raw));
8
8
  return text.charAt(0).toUpperCase() + text.slice(1);
9
9
  };
10
+ var weaponProfKey = (raw) => {
11
+ const key = normKey(raw);
12
+ const bare = key.split(" ").filter((w) => w !== "weapon").join(" ");
13
+ return bare === "simple" || bare === "martial" ? bare : key;
14
+ };
15
+ var armorProfKey = (raw) => normKey(raw).split(" ").filter((w) => w !== "armor").join(" ");
16
+ var proficiencyKey = (kind, raw) => kind === "weapons" ? weaponProfKey(raw) : kind === "armor" ? armorProfKey(raw) : toProfSlug(raw);
10
17
 
11
18
  export {
12
19
  normKey,
13
20
  toProfSlug,
14
21
  humanizeProficiency,
15
- proficiencyLabel
22
+ proficiencyLabel,
23
+ weaponProfKey,
24
+ armorProfKey,
25
+ proficiencyKey
16
26
  };
@@ -3,7 +3,10 @@ import {
3
3
  } from "./chunk-TOM66P2P.js";
4
4
  import {
5
5
  toProfSlug
6
- } from "./chunk-RLGQ5PZZ.js";
6
+ } from "./chunk-5JXFJ5AU.js";
7
+ import {
8
+ memberSpellTargets
9
+ } from "./chunk-RD2MID3M.js";
7
10
  import {
8
11
  warnOnce
9
12
  } from "./chunk-LVQLWRCU.js";
@@ -42,7 +45,8 @@ function emptyFeatureEffectTotals() {
42
45
  critRange: 20,
43
46
  extraAttack: 0,
44
47
  attackNotes: [],
45
- damageBonuses: []
48
+ damageBonuses: [],
49
+ spellDamageBonuses: []
46
50
  };
47
51
  }
48
52
  function pushUnique(list, value) {
@@ -87,14 +91,20 @@ function assembleEffectFeatures(resolved) {
87
91
  for (const a of aliases) activeBuffs.add(a);
88
92
  const buffFeatures = [];
89
93
  const pushBoon = (item, pool) => {
90
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
94
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k;
91
95
  const e = item.entity;
92
96
  if (!e || item.itemBound === true || ((_b2 = (_a2 = e.effects) == null ? void 0 : _a2.length) != null ? _b2 : 0) === 0) return;
97
+ const block = (_f2 = (_e2 = (_d2 = (_c2 = resolved.classes) == null ? void 0 : _c2[pool.classIndex]) == null ? void 0 : _d2.choices) == null ? void 0 : _e2[pool.anchorLevel]) != null ? _f2 : {};
98
+ const spellTargets = memberSpellTargets(e, (id) => {
99
+ var _a3;
100
+ return block[`${pool.id}:${(_a3 = item.key) != null ? _a3 : item.slug}:${id}`];
101
+ });
93
102
  buffFeatures.push({
94
- feature: { id: item.slug, name: e.name, activatable: (_c2 = e.activatable) != null ? _c2 : false, effects: e.effects },
103
+ feature: { id: item.slug, name: e.name, activatable: (_g2 = e.activatable) != null ? _g2 : false, effects: e.effects },
95
104
  // source is inert for the fold; attribute to the pool's owning class
96
105
  // (never a hardcoded class) so the generic engine carries no homebrew name.
97
- source: { kind: "class", slug: (_g2 = (_f2 = (_e2 = (_d2 = resolved.classes) == null ? void 0 : _d2[pool.classIndex]) == null ? void 0 : _e2.entity) == null ? void 0 : _f2.slug) != null ? _g2 : pool.id, level: pool.anchorLevel }
106
+ source: { kind: "class", slug: (_k = (_j2 = (_i2 = (_h2 = resolved.classes) == null ? void 0 : _h2[pool.classIndex]) == null ? void 0 : _i2.entity) == null ? void 0 : _j2.slug) != null ? _k : pool.id, level: pool.anchorLevel },
107
+ ...spellTargets ? { spellTargets } : {}
98
108
  });
99
109
  };
100
110
  for (const pool of (_g = resolved.pools) != null ? _g : []) {
@@ -119,7 +129,7 @@ function computeFeatureEffects(features, opts) {
119
129
  if (!foldsNow(rf, (_a = opts == null ? void 0 : opts.activeBuffs) != null ? _a : /* @__PURE__ */ new Set())) continue;
120
130
  const level = (_b = opts == null ? void 0 : opts.levelFor) == null ? void 0 : _b.call(opts, rf.source);
121
131
  for (const eff of selfEffectsOf(rf.feature)) {
122
- applyEffect(out, columnScaledRider(eff, rf, level, opts == null ? void 0 : opts.tableDiceFor), (_c = rf.feature.name) != null ? _c : "Feature", level, rf.source);
132
+ applyEffect(out, columnScaledRider(eff, rf, level, opts == null ? void 0 : opts.tableDiceFor), (_c = rf.feature.name) != null ? _c : "Feature", level, rf.source, rf.spellTargets);
123
133
  }
124
134
  }
125
135
  return out;
@@ -198,8 +208,8 @@ function riderScope(appliesTo) {
198
208
  const words = Array.isArray(appliesTo) ? appliesTo : [appliesTo];
199
209
  return words.includes("weapon") ? void 0 : [...words];
200
210
  }
201
- function applyEffect(out, eff, label, level, owner) {
202
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
211
+ function applyEffect(out, eff, label, level, owner, targets) {
212
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
203
213
  switch (eff.kind) {
204
214
  case "initiative-bonus":
205
215
  out.initiative_bonus += eff.value;
@@ -331,10 +341,22 @@ function applyEffect(out, eff, label, level, owner) {
331
341
  ...owner ? { owner } : {}
332
342
  });
333
343
  }
344
+ if (eff.applies_to === "spell" || eff.applies_to === "all") {
345
+ const spellScope = (_j = eff.spell_scope) != null ? _j : eff.applies_to === "all" ? {} : void 0;
346
+ out.spellDamageBonuses.push({
347
+ amount: eff.amount,
348
+ damage_type: eff.damage_type,
349
+ source: label,
350
+ ...eff.condition ? { condition: eff.condition } : {},
351
+ ...owner ? { owner } : {},
352
+ ...targets ? { targets } : {},
353
+ ...spellScope ? { scope: spellScope } : {}
354
+ });
355
+ }
334
356
  break;
335
357
  case "ability-score-increase":
336
358
  if (eff.abilities === "chosen") break;
337
- for (const ab of eff.abilities) out.ability_bonus[ab] = ((_j = out.ability_bonus[ab]) != null ? _j : 0) + eff.amount;
359
+ for (const ab of eff.abilities) out.ability_bonus[ab] = ((_k = out.ability_bonus[ab]) != null ? _k : 0) + eff.amount;
338
360
  break;
339
361
  default:
340
362
  break;
@@ -9,11 +9,11 @@ import {
9
9
  } from "./chunk-EJP263OP.js";
10
10
  import {
11
11
  normKey
12
- } from "./chunk-RLGQ5PZZ.js";
12
+ } from "./chunk-5JXFJ5AU.js";
13
13
  import {
14
14
  WEAPON_DEFAULT_ACTION_COST,
15
15
  resolveEntryEntity
16
- } from "./chunk-3VZZDKRA.js";
16
+ } from "./chunk-NXUY5XQN.js";
17
17
  import {
18
18
  readNumericBonus
19
19
  } from "./chunk-HHBD4SQ6.js";
@@ -8,10 +8,10 @@ import {
8
8
  } from "./chunk-ZJXSXVFP.js";
9
9
  import {
10
10
  choiceSchema
11
- } from "./chunk-5MXFW4O3.js";
11
+ } from "./chunk-OOL4KMG2.js";
12
12
  import {
13
13
  featureEffectSchema
14
- } from "./chunk-2JSQE3DD.js";
14
+ } from "./chunk-JXWNXIIZ.js";
15
15
 
16
16
  // src/schemas/feature-schema.ts
17
17
  import { z as z2 } from "zod";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseRace
3
- } from "./chunk-Y5VVP73C.js";
3
+ } from "./chunk-HY2SRSVM.js";
4
4
 
5
5
  // src/race/race.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MONSTER_KNOWN_KEYS,
3
3
  monsterSchema
4
- } from "./chunk-7ATNGKBS.js";
4
+ } from "./chunk-VP5TIXDK.js";
5
5
 
6
6
  // src/monster/monster.parser.ts
7
7
  import { parseYaml } from "@archivist-gg/core";
@@ -9,24 +9,33 @@ import {
9
9
  classToolPicks,
10
10
  expandToolPool,
11
11
  toolCategoriesOf
12
- } from "./chunk-H5PTKYXL.js";
12
+ } from "./chunk-QA3YCOML.js";
13
13
  import {
14
14
  assembleEffectFeatures,
15
15
  collectProficiencyEffectGrants
16
- } from "./chunk-TULQ4FFX.js";
16
+ } from "./chunk-64VNLMSG.js";
17
17
  import {
18
18
  ALL_LANGUAGES,
19
19
  ALL_SKILL_SLUGS,
20
20
  ALL_TOOLS
21
21
  } from "./chunk-KU3YLF6D.js";
22
+ import {
23
+ humanizeProficiency,
24
+ proficiencyLabel,
25
+ toProfSlug
26
+ } from "./chunk-5JXFJ5AU.js";
22
27
  import {
23
28
  featOfSite,
24
29
  featPickSites,
25
30
  isTargetChoice,
26
31
  memberChoices,
27
32
  poolMemberSites,
28
- poolMemberSitesOf
29
- } from "./chunk-HTYMW5CH.js";
33
+ poolMemberSitesOf,
34
+ raceFeatSites
35
+ } from "./chunk-RD2MID3M.js";
36
+ import {
37
+ collectAdditionalSpells
38
+ } from "./chunk-LICYHPI3.js";
30
39
  import {
31
40
  resolveFeatureResources,
32
41
  resolveResourceIndex
@@ -42,18 +51,13 @@ import {
42
51
  flattenAsiOrFeat,
43
52
  resolveLegacyAsiFeat
44
53
  } from "./chunk-LBEXLN6I.js";
45
- import {
46
- humanizeProficiency,
47
- proficiencyLabel,
48
- toProfSlug
49
- } from "./chunk-RLGQ5PZZ.js";
50
54
  import {
51
55
  bindFeatureConsumers,
52
56
  buildInfusionContext,
53
57
  resolveAllPools,
54
58
  resolveOptionRows,
55
59
  strandedPicks
56
- } from "./chunk-3VZZDKRA.js";
60
+ } from "./chunk-NXUY5XQN.js";
57
61
  import {
58
62
  countColumnsFor,
59
63
  readTableColumn
@@ -61,9 +65,6 @@ import {
61
65
  import {
62
66
  recognizeDecision
63
67
  } from "./chunk-YHYTW5QA.js";
64
- import {
65
- collectAdditionalSpells
66
- } from "./chunk-VRV3NNTA.js";
67
68
  import {
68
69
  isItemEntity,
69
70
  resolveEntityForEntry
@@ -75,6 +76,9 @@ import {
75
76
  import {
76
77
  warnOnce
77
78
  } from "./chunk-LVQLWRCU.js";
79
+ import {
80
+ bucketOf
81
+ } from "./chunk-4J6DS3EJ.js";
78
82
  import {
79
83
  isValidMaxFormula
80
84
  } from "./chunk-EVMUJYUD.js";
@@ -522,7 +526,7 @@ function buildSubclassItem(choice, source, level, featureName, c, ctx, ownerBare
522
526
  };
523
527
  }
524
528
  function visitProficiencyChoices(resolved, visit) {
525
- var _a, _b, _c;
529
+ var _a, _b, _c, _d, _e;
526
530
  const walk = (choices, read) => {
527
531
  for (const ch of choices != null ? choices : []) {
528
532
  visit(ch, read(ch.id));
@@ -539,11 +543,11 @@ function visitProficiencyChoices(resolved, visit) {
539
543
  const featBySlug = new Map(((_b = resolved.feats) != null ? _b : []).map((f) => [f.slug, f]));
540
544
  const memberSites = poolMemberSitesOf(resolved);
541
545
  const featSites = featPickSites(resolved.classes, memberSites, (ci) => {
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;
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;
544
548
  });
545
549
  resolved.classes.forEach((c, i) => {
546
- var _a2, _b2, _c2, _d, _e;
550
+ var _a2, _b2, _c2, _d2, _e2;
547
551
  if (!c.entity) return;
548
552
  const entity = c.entity;
549
553
  const readAt = (lvl) => (id) => {
@@ -570,7 +574,7 @@ function visitProficiencyChoices(resolved, visit) {
570
574
  }
571
575
  for (const site of featSites) {
572
576
  if (site.classIndex !== i) continue;
573
- const feat = (_e = featBySlug.get(stripRef(site.slug))) != null ? _e : featOfSite(site, (_d = resolved.feats) != null ? _d : []);
577
+ const feat = (_e2 = featBySlug.get(stripRef(site.slug))) != null ? _e2 : featOfSite(site, (_d2 = resolved.feats) != null ? _d2 : []);
574
578
  if (!feat) continue;
575
579
  walk(feat.choices, site.read);
576
580
  }
@@ -592,6 +596,10 @@ function visitProficiencyChoices(resolved, visit) {
592
596
  const of = featBySlug.get(resolved.originFeatSlug);
593
597
  if (of) walk(of.choices, (id) => oc[`background:feat:${id}`]);
594
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
+ }
595
603
  }
596
604
  var filterToPool = (vals, pool) => pool ? vals.map((v) => matchPool(v, pool)).filter((v) => !!v) : vals;
597
605
  function collectChosenProficiencies(resolved) {
@@ -628,6 +636,14 @@ function proficiencyEntryFor(raw, vocab, origin, sources, expertise) {
628
636
  return { value, label, sources, origin, ...expertise ? { expertise: true } : {} };
629
637
  }
630
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) {
631
647
  const grants = collectProficiencyGrants(resolved);
632
648
  const chosen = collectChosenProficiencies(resolved);
633
649
  const build = (domain) => {
@@ -673,7 +689,9 @@ function computeEffectiveProficiencies(resolved) {
673
689
  }
674
690
  }
675
691
  const suppressed = /* @__PURE__ */ new Set([...removes.map((r) => toProfSlug(r)), ...triSuppressed]);
676
- 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) };
677
695
  };
678
696
  return { languages: build("languages"), tools: build("tools") };
679
697
  }
@@ -1160,7 +1178,7 @@ function resolveOriginFeat(entities, originFeatRef, ownerSlug) {
1160
1178
  const baseSlug = base && base !== rawTail ? wikilinkTailSlug(`[[${base}]]`) : null;
1161
1179
  const baseInner = baseSlug ? inner.slice(0, inner.length - rawTail.length) + base : null;
1162
1180
  const prefix = (ownerSlug == null ? void 0 : ownerSlug.includes("_")) ? ownerSlug.slice(0, ownerSlug.indexOf("_")) : null;
1163
- const feats = entities.search("", "feat", Number.POSITIVE_INFINITY);
1181
+ const feats = bucketOf(entities, "feat");
1164
1182
  const byPath = (p) => feats.find((f) => f.filePath === `${p}.md` || f.filePath.endsWith(`/${p}.md`));
1165
1183
  const sameCompendiumTail = (s) => prefix ? feats.find((f) => f.slug.startsWith(`${prefix}_feat_`) && f.slug.endsWith(`_${s}`)) : void 0;
1166
1184
  const anyTail = (s) => feats.find((f) => f.slug.endsWith(`_${s}`));
@@ -1247,6 +1265,14 @@ var PCResolver = class {
1247
1265
  if (originFeat && background && !featVia.has(originFeat.feat.slug)) {
1248
1266
  featVia.set(originFeat.feat.slug, { kind: "background", slug: background.slug });
1249
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
+ }
1250
1276
  const totalLevel = classes.reduce((sum, c) => sum + c.level, 0);
1251
1277
  const features = collectResolvedFeatures(race, classes, background, feats, featVia);
1252
1278
  const extraFeatures = collectChosenGrantedFeatures(character, classes, this.entities, race, background, memberSites);
@@ -1277,6 +1303,7 @@ var PCResolver = class {
1277
1303
  const read = (childId) => oc[`background:feat:${childId}`];
1278
1304
  spells.push(...collectFeatGrantedSpells(originFeat.feat, read, this.entities, warnings));
1279
1305
  }
1306
+ for (const rf of raceFeats) spells.push(...collectFeatGrantedSpells(rf.feat, rf.read, this.entities, warnings));
1280
1307
  for (const site of featSites) {
1281
1308
  const reg = this.entities.getByTypeAndSlug("feat", site.slug);
1282
1309
  if (!reg) continue;
@@ -1307,16 +1334,6 @@ var PCResolver = class {
1307
1334
  return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
1308
1335
  }).find((a) => a != null)) != null ? _h : null;
1309
1336
  spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
1310
- spells.push(...collectAdditionalSpells({
1311
- race,
1312
- feats,
1313
- classes,
1314
- totalLevel,
1315
- ownAbility: ownSpellcastingAbility,
1316
- alreadyCollected: spells,
1317
- entities: this.entities,
1318
- warnings
1319
- }));
1320
1337
  const chosenMasteries = collectChosenWeaponMasteries(character);
1321
1338
  if (chosenMasteries.full.length > 0) {
1322
1339
  const target = features.find(
@@ -1348,13 +1365,27 @@ var PCResolver = class {
1348
1365
  ...originFeatSlug ? { originFeatSlug } : {},
1349
1366
  totalLevel,
1350
1367
  features,
1351
- spells: dedupeResolvedSpells(spells),
1368
+ spells: [],
1352
1369
  pools: [],
1353
1370
  resources: featureResources,
1354
1371
  weaponMasteries: chosenMasteries.bare,
1355
1372
  state: character.state
1356
1373
  };
1357
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);
1358
1389
  resolvedCharacter.resources = resolveResourceIndex(resolvedCharacter);
1359
1390
  resolvedCharacter.features = bindFeatureConsumers(resolvedCharacter.features, resolvedCharacter.resources, classes);
1360
1391
  const infusions = buildInfusionContext(character, resolvedCharacter, this.entities);
@@ -1425,7 +1456,7 @@ function collectItemGrantedSpells(character, ownAbility, entities, warnings) {
1425
1456
  function dedupeResolvedSpells(spells) {
1426
1457
  const byKey = /* @__PURE__ */ new Map();
1427
1458
  for (const s of spells) {
1428
- 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;
1429
1460
  const existing = byKey.get(key);
1430
1461
  if (!existing) {
1431
1462
  byKey.set(key, s);
@@ -1801,6 +1832,7 @@ export {
1801
1832
  __categoryToEntitySelectForTest,
1802
1833
  collectChosenProficiencies,
1803
1834
  computeEffectiveProficiencies,
1835
+ computeSuppressedProficiencies,
1804
1836
  collectChosenAbilityPoints,
1805
1837
  buildDecisionLedger
1806
1838
  };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  featureSchema
3
- } from "./chunk-YD2VAHA6.js";
3
+ } from "./chunk-B2QHP6UF.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-5MXFW4O3.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-DOXSFYLH.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-5MXFW4O3.js";
7
+ } from "./chunk-OOL4KMG2.js";
8
8
  import {
9
9
  featureEffectSchema
10
- } from "./chunk-2JSQE3DD.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-PQZ4LM4Z.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-YKL6SHRJ.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-FANQVQ3D.js";
3
+ } from "./chunk-FJYBLXV7.js";
4
4
  import {
5
5
  aliasFeatureListActionCost
6
6
  } from "./chunk-4U55VNC7.js";
@@ -4,6 +4,15 @@ 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 }),
@@ -83,6 +92,10 @@ var featureEffectSchema = z.discriminatedUnion("kind", [
83
92
  // ART-c1 Task 9b: `spell` and `all` as before, or a weapon scope, one word or an any-of list, which the engine
84
93
  // evaluates per attack row (pc.recalc.ts `riderReachesRow`). `weapon`, like an absent value, reaches every row.
85
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(),
86
99
  condition: conditionField,
87
100
  subject: subjectField
88
101
  }),