@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
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  backgroundCodec
3
- } from "../chunk-Q6UOHCCT.js";
4
- import "../chunk-RQNJXADQ.js";
5
- import "../chunk-MUIU7RZ3.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-BRGLDWUP.js";
8
- import "../chunk-ZMJQUZQH.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-RQNJXADQ.js";
4
- import "../chunk-MUIU7RZ3.js";
3
+ } from "../chunk-NKZMQV2C.js";
4
+ import "../chunk-QFWQKS3M.js";
5
5
  import "../chunk-IN4M4X7P.js";
6
- import "../chunk-BRGLDWUP.js";
7
- import "../chunk-ZMJQUZQH.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-MUIU7RZ3.js";
4
+ } from "../chunk-QFWQKS3M.js";
5
5
  import "../chunk-IN4M4X7P.js";
6
- import "../chunk-BRGLDWUP.js";
7
- import "../chunk-ZMJQUZQH.js";
6
+ import "../chunk-OOL4KMG2.js";
7
+ import "../chunk-JXWNXIIZ.js";
8
8
  import "../chunk-UA5KS2PL.js";
9
9
  export {
10
10
  backgroundEntitySchema,
@@ -0,0 +1,57 @@
1
+ import {
2
+ classifyWeaponRange
3
+ } from "./chunk-YDGMBMYI.js";
4
+
5
+ // src/pc/pc.weapon-grant.ts
6
+ var PROPERTY_KEY = {
7
+ "2h": "two_handed",
8
+ "two-handed": "two_handed",
9
+ "two handed": "two_handed",
10
+ h: "heavy",
11
+ l: "light",
12
+ f: "finesse",
13
+ t: "thrown",
14
+ v: "versatile",
15
+ r: "reach",
16
+ a: "ammunition",
17
+ ld: "loading",
18
+ s: "special"
19
+ };
20
+ var propertyKey = (word) => {
21
+ var _a;
22
+ const w = word.trim().toLowerCase();
23
+ return (_a = PROPERTY_KEY[w]) != null ? _a : w.replace(/[\s-]+/g, "_");
24
+ };
25
+ var GRANT_RE = new RegExp(
26
+ "^(?:all\\s+)?(?:(melee|ranged)\\s+)?(simple|martial)(?:\\s+(melee|ranged))?\\s+weapons?\\s+(?:that|which)\\s+(have|has|don'?t\\s+have|do\\s+not\\s+have|doesn'?t\\s+have|does\\s+not\\s+have)\\s+the\\s+(.+?)\\s+propert(?:y|ies)\\.?$",
27
+ "i"
28
+ );
29
+ function parseQualifiedWeaponGrant(text) {
30
+ var _a, _b;
31
+ const m = GRANT_RE.exec(text.replace(/’/g, "'").trim().replace(/\s+/g, " "));
32
+ if (!m) return null;
33
+ const range = (_b = (_a = m[1]) != null ? _a : m[3]) == null ? void 0 : _b.toLowerCase();
34
+ const properties = m[5].split(/\s*,\s*|\s+or\s+|\s+and\s+/i).map((w) => w.trim()).filter(Boolean).map(propertyKey);
35
+ if (properties.length === 0) return null;
36
+ return {
37
+ category: m[2].toLowerCase(),
38
+ ...range ? { range } : {},
39
+ properties,
40
+ without: /n'?t|not/i.test(m[4])
41
+ };
42
+ }
43
+ function qualifiedGrantCovers(text, weapon) {
44
+ const g = parseQualifiedWeaponGrant(text);
45
+ if (!g || typeof weapon.category !== "string") return false;
46
+ if (!new RegExp(`^${g.category}\\b`).test(weapon.category)) return false;
47
+ if (g.range) {
48
+ const melee = classifyWeaponRange(weapon).melee !== null;
49
+ if (g.range === "melee" !== melee) return false;
50
+ }
51
+ const has = g.properties.some((p) => weapon.properties.some((x) => x === p));
52
+ return g.without ? !has : has;
53
+ }
54
+
55
+ export {
56
+ qualifiedGrantCovers
57
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featEntitySchema
3
- } from "./chunk-LVX6TQ3R.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-RQNJXADQ.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";
@@ -20,6 +23,7 @@ function emptyFeatureEffectTotals() {
20
23
  initiative_bonus: 0,
21
24
  initiative_terms: [],
22
25
  skill_bonus_terms: [],
26
+ save_bonus_terms: [],
23
27
  half_proficiency_terms: [],
24
28
  hp_per_level_bonus: 0,
25
29
  hp_per_level_terms: [],
@@ -41,7 +45,8 @@ function emptyFeatureEffectTotals() {
41
45
  critRange: 20,
42
46
  extraAttack: 0,
43
47
  attackNotes: [],
44
- damageBonuses: []
48
+ damageBonuses: [],
49
+ spellDamageBonuses: []
45
50
  };
46
51
  }
47
52
  function pushUnique(list, value) {
@@ -86,14 +91,20 @@ function assembleEffectFeatures(resolved) {
86
91
  for (const a of aliases) activeBuffs.add(a);
87
92
  const buffFeatures = [];
88
93
  const pushBoon = (item, pool) => {
89
- var _a2, _b2, _c2, _d2, _e2, _f2, _g2;
94
+ var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k;
90
95
  const e = item.entity;
91
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
+ });
92
102
  buffFeatures.push({
93
- 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 },
94
104
  // source is inert for the fold; attribute to the pool's owning class
95
105
  // (never a hardcoded class) so the generic engine carries no homebrew name.
96
- 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 } : {}
97
108
  });
98
109
  };
99
110
  for (const pool of (_g = resolved.pools) != null ? _g : []) {
@@ -118,7 +129,7 @@ function computeFeatureEffects(features, opts) {
118
129
  if (!foldsNow(rf, (_a = opts == null ? void 0 : opts.activeBuffs) != null ? _a : /* @__PURE__ */ new Set())) continue;
119
130
  const level = (_b = opts == null ? void 0 : opts.levelFor) == null ? void 0 : _b.call(opts, rf.source);
120
131
  for (const eff of selfEffectsOf(rf.feature)) {
121
- applyEffect(out, eff, (_c = rf.feature.name) != null ? _c : "Feature", level);
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);
122
133
  }
123
134
  }
124
135
  return out;
@@ -182,13 +193,23 @@ function scaled(base, key, scalesAt, level) {
182
193
  }
183
194
  return best;
184
195
  }
196
+ function columnScaledRider(eff, rf, level, tableDiceFor) {
197
+ if (eff.kind !== "damage-bonus" || level === void 0 || !tableDiceFor) return eff;
198
+ const src = rf.source;
199
+ const column = rf.feature.name;
200
+ if (src.kind !== "class" && src.kind !== "subclass" || !column) return eff;
201
+ const atGain = tableDiceFor(src, column, src.level);
202
+ if (!atGain || atGain.toLowerCase() !== eff.amount.trim().toLowerCase()) return eff;
203
+ const now = tableDiceFor(src, column, level);
204
+ return now ? { ...eff, amount: now } : eff;
205
+ }
185
206
  function riderScope(appliesTo) {
186
207
  if (appliesTo === void 0 || appliesTo === "all" || appliesTo === "spell") return void 0;
187
208
  const words = Array.isArray(appliesTo) ? appliesTo : [appliesTo];
188
209
  return words.includes("weapon") ? void 0 : [...words];
189
210
  }
190
- function applyEffect(out, eff, label, level) {
191
- var _a, _b, _c, _d, _e, _f, _g, _h;
211
+ function applyEffect(out, eff, label, level, owner, targets) {
212
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
192
213
  switch (eff.kind) {
193
214
  case "initiative-bonus":
194
215
  out.initiative_bonus += eff.value;
@@ -202,10 +223,19 @@ function applyEffect(out, eff, label, level) {
202
223
  label
203
224
  });
204
225
  break;
226
+ case "save-bonus":
227
+ out.save_bonus_terms.push({
228
+ saves: [...(_b = eff.saves) != null ? _b : []],
229
+ ability: eff.ability,
230
+ minimum: (_c = eff.minimum) != null ? _c : 0,
231
+ ...eff.inactive_while ? { inactiveWhile: eff.inactive_while } : {},
232
+ label
233
+ });
234
+ break;
205
235
  case "half-proficiency":
206
236
  out.half_proficiency_terms.push({
207
- skills: ((_b = eff.skills) != null ? _b : []).map(toProfSlug),
208
- round: (_c = eff.round) != null ? _c : "down",
237
+ skills: ((_d = eff.skills) != null ? _d : []).map(toProfSlug),
238
+ round: (_e = eff.round) != null ? _e : "down",
209
239
  label
210
240
  });
211
241
  break;
@@ -272,7 +302,7 @@ function applyEffect(out, eff, label, level) {
272
302
  }
273
303
  case "roll-modifier": {
274
304
  const rolls = eff.roll === "any" ? ["ability-check", "saving-throw", "attack"] : [eff.roll];
275
- const scopes = (_d = normalizeRollScope(eff.scope, rolls[0])) != null ? _d : [eff.scope];
305
+ const scopes = (_f = normalizeRollScope(eff.scope, rolls[0])) != null ? _f : [eff.scope];
276
306
  for (const roll of rolls) for (const scope of scopes)
277
307
  out.rollModifiers.push({ mode: eff.mode, roll, scope, condition: eff.condition, label });
278
308
  break;
@@ -282,13 +312,13 @@ function applyEffect(out, eff, label, level) {
282
312
  ability: eff.ability === "any" ? void 0 : eff.ability,
283
313
  on_success: eff.on_success,
284
314
  on_failure: eff.on_failure,
285
- appliesTo: (_e = eff.applies_to) != null ? _e : void 0,
315
+ appliesTo: (_g = eff.applies_to) != null ? _g : void 0,
286
316
  condition: eff.condition,
287
317
  label
288
318
  });
289
319
  break;
290
320
  case "crit-range":
291
- if (((_f = eff.applies_to) != null ? _f : "weapon") !== "spell") out.critRange = Math.min(out.critRange, eff.min_roll);
321
+ if (((_h = eff.applies_to) != null ? _h : "weapon") !== "spell") out.critRange = Math.min(out.critRange, eff.min_roll);
292
322
  break;
293
323
  case "extra-attack":
294
324
  out.extraAttack = Math.max(out.extraAttack, scaled(eff.count, "count", eff.scales_at, level));
@@ -300,20 +330,33 @@ function applyEffect(out, eff, label, level) {
300
330
  if (eff.flag === "no-ranged-in-melee-disadvantage") out.attackNotes.push("No disadvantage firing in melee");
301
331
  break;
302
332
  case "damage-bonus":
303
- if (((_g = eff.applies_to) != null ? _g : "weapon") !== "spell") {
333
+ if (((_i = eff.applies_to) != null ? _i : "weapon") !== "spell") {
304
334
  const scope = riderScope(eff.applies_to);
305
335
  out.damageBonuses.push({
306
336
  amount: eff.amount,
307
337
  damage_type: eff.damage_type,
308
338
  source: label,
309
339
  ...eff.condition ? { condition: eff.condition } : {},
310
- ...scope ? { scope } : {}
340
+ ...scope ? { scope } : {},
341
+ ...owner ? { owner } : {}
342
+ });
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 } : {}
311
354
  });
312
355
  }
313
356
  break;
314
357
  case "ability-score-increase":
315
358
  if (eff.abilities === "chosen") break;
316
- for (const ab of eff.abilities) out.ability_bonus[ab] = ((_h = out.ability_bonus[ab]) != null ? _h : 0) + eff.amount;
359
+ for (const ab of eff.abilities) out.ability_bonus[ab] = ((_k = out.ability_bonus[ab]) != null ? _k : 0) + eff.amount;
317
360
  break;
318
361
  default:
319
362
  break;
@@ -59,6 +59,18 @@ function normalizeSpellComponents(value) {
59
59
  if (c.r === true) parts.push("R");
60
60
  return parts.join(", ");
61
61
  }
62
+ function spellMaterialOf(value) {
63
+ if (value == null || typeof value !== "object" || Array.isArray(value)) return void 0;
64
+ const m = value.m;
65
+ if (m == null || typeof m !== "object" || Array.isArray(m)) return void 0;
66
+ const { text, cost, consume } = m;
67
+ if (typeof text !== "string") return void 0;
68
+ return {
69
+ text,
70
+ ...typeof cost === "number" ? { cost } : {},
71
+ ...typeof consume === "boolean" || typeof consume === "string" ? { consume } : {}
72
+ };
73
+ }
62
74
  function normalizeSpellDurationEntry(entry) {
63
75
  if (entry == null || typeof entry !== "object") return toStringSafe(entry);
64
76
  const e = entry;
@@ -84,9 +96,11 @@ function normalizeSpellDuration(value) {
84
96
  return value.map((entry) => normalizeSpellDurationEntry(entry)).join("; ");
85
97
  }
86
98
  function mirrorSpellShapes(entity) {
99
+ const material = spellMaterialOf(entity.components);
87
100
  return {
88
101
  ...entity,
89
102
  ...entity.components != null ? { components: normalizeSpellComponents(entity.components) } : {},
103
+ ...material ? { material } : {},
90
104
  ...entity.duration != null ? { duration: normalizeSpellDuration(entity.duration) } : {}
91
105
  };
92
106
  }
@@ -116,6 +130,8 @@ function parseSpell(source) {
116
130
  if (raw.range != null) spell.range = toStringSafe(raw.range);
117
131
  if (raw.components != null) spell.components = normalizeSpellComponents(raw.components);
118
132
  if (raw.duration != null) spell.duration = normalizeSpellDuration(raw.duration);
133
+ const material = spellMaterialOf(raw.components);
134
+ if (material) spell.material = material;
119
135
  if (raw.concentration != null) spell.concentration = Boolean(raw.concentration);
120
136
  if (raw.ritual != null) spell.ritual = Boolean(raw.ritual);
121
137
  if (raw.classes) spell.classes = raw.classes.map(String);
@@ -158,6 +174,7 @@ function parseSpell(source) {
158
174
 
159
175
  export {
160
176
  normalizeSpellComponents,
177
+ spellMaterialOf,
161
178
  normalizeSpellDuration,
162
179
  mirrorSpellShapes,
163
180
  parseSpell
@@ -1,16 +1,19 @@
1
+ import {
2
+ qualifiedGrantCovers
3
+ } from "./chunk-3LWMFYKS.js";
1
4
  import {
2
5
  classifyWeaponRange
3
6
  } from "./chunk-YDGMBMYI.js";
7
+ import {
8
+ resolveContainment
9
+ } from "./chunk-EJP263OP.js";
4
10
  import {
5
11
  normKey
6
- } from "./chunk-RLGQ5PZZ.js";
12
+ } from "./chunk-5JXFJ5AU.js";
7
13
  import {
8
14
  WEAPON_DEFAULT_ACTION_COST,
9
15
  resolveEntryEntity
10
- } from "./chunk-T5GZBKIQ.js";
11
- import {
12
- resolveContainment
13
- } from "./chunk-EJP263OP.js";
16
+ } from "./chunk-NXUY5XQN.js";
14
17
  import {
15
18
  readNumericBonus
16
19
  } from "./chunk-HHBD4SQ6.js";
@@ -61,6 +64,7 @@ function emptyAppliedBonuses() {
61
64
  ability_bonuses: {},
62
65
  ability_statics: {},
63
66
  save_bonus: 0,
67
+ save_terms: [],
64
68
  speed_bonuses: { walk: 0, fly: null, swim: 0, climb: 0 },
65
69
  spell_attack: 0,
66
70
  spell_save_dc: 0,
@@ -103,7 +107,7 @@ function pushApply(outcome, field, source, pool, onApply) {
103
107
  }
104
108
  }
105
109
  function computeAppliedBonuses(resolved, _profs, registry, warnings) {
106
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
110
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
107
111
  const out = emptyAppliedBonuses();
108
112
  const equipment = (_a = resolved.definition.equipment) != null ? _a : [];
109
113
  const ctx = buildConditionContext(resolved, {}, registry);
@@ -135,13 +139,15 @@ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
135
139
  }
136
140
  const b = item.bonuses;
137
141
  if (!b) continue;
142
+ const saveSource = (_q = (_p = entry.overrides) == null ? void 0 : _p.name) != null ? _q : item.name;
138
143
  pushApply(
139
144
  readNumericBonus(b.saving_throws, ctx),
140
145
  "saving_throws",
141
- item.name,
146
+ saveSource,
142
147
  out.informational,
143
148
  (v) => {
144
149
  out.save_bonus += v;
150
+ out.save_terms.push({ source: saveSource, amount: v });
145
151
  }
146
152
  );
147
153
  pushApply(
@@ -162,7 +168,7 @@ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
162
168
  out.spell_save_dc += v;
163
169
  }
164
170
  );
165
- if ((_p = b.ability_scores) == null ? void 0 : _p.bonus) {
171
+ if ((_r = b.ability_scores) == null ? void 0 : _r.bonus) {
166
172
  for (const [k, v] of Object.entries(b.ability_scores.bonus)) {
167
173
  if (!isAbilityKey(k)) continue;
168
174
  pushApply(
@@ -177,7 +183,7 @@ function computeAppliedBonuses(resolved, _profs, registry, warnings) {
177
183
  );
178
184
  }
179
185
  }
180
- if ((_q = b.ability_scores) == null ? void 0 : _q.static) {
186
+ if ((_s = b.ability_scores) == null ? void 0 : _s.static) {
181
187
  for (const [k, n] of Object.entries(b.ability_scores.static)) {
182
188
  if (!isAbilityKey(k) || typeof n !== "number") continue;
183
189
  const prev = out.ability_statics[k];
@@ -411,6 +417,7 @@ function attackAbility(weapon, mods, weaponAbilities) {
411
417
  function isWeaponSlugProficient(weapon, weaponSlug, profs) {
412
418
  const wp = profs.weapons;
413
419
  if (wp.specific.some((e) => e === weaponSlug || normKey(e) === normKey(weapon.name))) return true;
420
+ if (wp.specific.some((e) => qualifiedGrantCovers(e, weapon))) return true;
414
421
  const cat = weapon.category;
415
422
  if (typeof cat === "string") {
416
423
  if (/^simple\b/.test(cat) && wp.categories.includes("simple")) return true;
@@ -8,10 +8,10 @@ import {
8
8
  } from "./chunk-ZJXSXVFP.js";
9
9
  import {
10
10
  choiceSchema
11
- } from "./chunk-BRGLDWUP.js";
11
+ } from "./chunk-OOL4KMG2.js";
12
12
  import {
13
13
  featureEffectSchema
14
- } from "./chunk-ZMJQUZQH.js";
14
+ } from "./chunk-JXWNXIIZ.js";
15
15
 
16
16
  // src/schemas/feature-schema.ts
17
17
  import { z as z2 } from "zod";
@@ -24,11 +24,9 @@ function derivePoolLayout(entries) {
24
24
  }
25
25
  return tied ? void 0 : best;
26
26
  }
27
- var DEFAULT_POOL_ROWS = "blocks";
28
27
  function poolPresentation(authored, derived) {
29
28
  const head = authored === "point-pool" ? "points" : authored === "dice-pool" ? "dice" : derived === "point-pool" ? "points" : "dice";
30
- const rows = authored === "spell-like" ? "spell-like" : DEFAULT_POOL_ROWS;
31
- return { head, rows };
29
+ return { head };
32
30
  }
33
31
  var RENDERING_HINT_AFFORDANCE = {
34
32
  "granted-die-to-ally": "granted-die"
@@ -49,7 +47,6 @@ function formatAffordanceCaption(affordance, values) {
49
47
  export {
50
48
  RENDERING_HINT_LAYOUT,
51
49
  derivePoolLayout,
52
- DEFAULT_POOL_ROWS,
53
50
  poolPresentation,
54
51
  RENDERING_HINT_AFFORDANCE,
55
52
  deriveEntryAffordance,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseRace
3
- } from "./chunk-R2V5LN3I.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-EL6QPF6I.js";
4
+ } from "./chunk-VP5TIXDK.js";
5
5
 
6
6
  // src/monster/monster.parser.ts
7
7
  import { parseYaml } from "@archivist-gg/core";