@archivist-gg/dnd5e 0.3.13 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) 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-ZMJQUZQH.js → chunk-2JSQE3DD.js} +11 -0
  5. package/dist/{chunk-Q6UOHCCT.js → chunk-2UDSN6VW.js} +1 -1
  6. package/dist/chunk-3LWMFYKS.js +57 -0
  7. package/dist/{chunk-MUIU7RZ3.js → chunk-3MGQFFNE.js} +1 -1
  8. package/dist/{chunk-T5GZBKIQ.js → chunk-3VZZDKRA.js} +114 -27
  9. package/dist/{chunk-BRGLDWUP.js → chunk-5MXFW4O3.js} +7 -3
  10. package/dist/{chunk-RQNJXADQ.js → chunk-6EI737IO.js} +1 -1
  11. package/dist/{chunk-EL6QPF6I.js → chunk-7ATNGKBS.js} +1 -1
  12. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  13. package/dist/{chunk-4SLOJXJ4.js → chunk-7QN7XSAK.js} +1 -1
  14. package/dist/{chunk-MC5YJ4PB.js → chunk-AZRGFUSR.js} +1 -1
  15. package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
  16. package/dist/{chunk-JNNNBEUH.js → chunk-DOXSFYLH.js} +7 -2
  17. package/dist/{chunk-X7VZY4ON.js → chunk-FANQVQ3D.js} +2 -2
  18. package/dist/{chunk-PX25R3DK.js → chunk-G6PBI2BU.js} +1 -1
  19. package/dist/chunk-HTYMW5CH.js +228 -0
  20. package/dist/{chunk-VDPRBUU5.js → chunk-JCMUVZIK.js} +1 -1
  21. package/dist/{chunk-Q5XTEYHD.js → chunk-JSM6JFZR.js} +15 -8
  22. package/dist/chunk-KR6JF3BF.js +14 -0
  23. package/dist/{chunk-J53ERF6K.js → chunk-LF5AGJA4.js} +1 -1
  24. package/dist/{chunk-XJRNEQPD.js → chunk-MBX25B6D.js} +1 -1
  25. package/dist/{chunk-5KGV6TF7.js → chunk-OFCSP6MT.js} +1 -1
  26. package/dist/{chunk-QEO7LYTG.js → chunk-ONTGZR5T.js} +127 -71
  27. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  28. package/dist/{chunk-LVX6TQ3R.js → chunk-PKX6TGRE.js} +2 -2
  29. package/dist/{chunk-QSBJ6NEM.js → chunk-PQZ4LM4Z.js} +1 -1
  30. package/dist/chunk-PXVNAY7T.js +84 -0
  31. package/dist/chunk-QH36K2PU.js +158 -0
  32. package/dist/{chunk-M2OWVAZG.js → chunk-QZSWN2KT.js} +1 -1
  33. package/dist/{chunk-6ZY3U6BQ.js → chunk-RYLXAYIQ.js} +1 -1
  34. package/dist/{chunk-UE6LKRXQ.js → chunk-T6QIF5XR.js} +16 -0
  35. package/dist/{chunk-4BZ47YJ4.js → chunk-TULQ4FFX.js} +32 -11
  36. package/dist/{chunk-PLOSP4XQ.js → chunk-UUBJCNQU.js} +11 -2
  37. package/dist/{chunk-DPM6V7YX.js → chunk-VRV3NNTA.js} +1 -1
  38. package/dist/{chunk-R2V5LN3I.js → chunk-Y5VVP73C.js} +1 -1
  39. package/dist/{chunk-FPGXFD2M.js → chunk-YD2VAHA6.js} +2 -2
  40. package/dist/{chunk-G7ED5H2G.js → chunk-YKL6SHRJ.js} +1 -1
  41. package/dist/class/class.codec.js +6 -6
  42. package/dist/class/class.parser.js +5 -5
  43. package/dist/class/class.schema.d.ts +8 -0
  44. package/dist/class/class.schema.js +4 -4
  45. package/dist/class/class.standard-array.d.ts +16 -0
  46. package/dist/class/class.standard-array.js +20 -0
  47. package/dist/class/class.types.d.ts +3 -0
  48. package/dist/dnd/amount-text.d.ts +38 -0
  49. package/dist/dnd/amount-text.js +13 -0
  50. package/dist/dnd/resource-words.d.ts +4 -0
  51. package/dist/dnd/resource-words.js +30 -0
  52. package/dist/feat/feat.codec.js +5 -5
  53. package/dist/feat/feat.parser.js +4 -4
  54. package/dist/feat/feat.schema.d.ts +25 -0
  55. package/dist/feat/feat.schema.js +3 -3
  56. package/dist/index.js +332 -70
  57. package/dist/monster/monster.parser.js +5 -5
  58. package/dist/monster/monster.schema.js +4 -4
  59. package/dist/optional-feature/optional-feature.codec.js +5 -4
  60. package/dist/optional-feature/optional-feature.parser.js +4 -3
  61. package/dist/optional-feature/optional-feature.schema.d.ts +26 -0
  62. package/dist/optional-feature/optional-feature.schema.js +3 -2
  63. package/dist/pc/pc.additional-spells.js +2 -2
  64. package/dist/pc/pc.amount-bindings.d.ts +32 -0
  65. package/dist/pc/pc.amount-bindings.js +23 -0
  66. package/dist/pc/pc.decision-engine.d.ts +3 -0
  67. package/dist/pc/pc.decision-engine.js +14 -13
  68. package/dist/pc/pc.entry-overrides.js +3 -3
  69. package/dist/pc/pc.equipment.js +6 -5
  70. package/dist/pc/pc.feature-effects.d.ts +12 -0
  71. package/dist/pc/pc.feature-effects.js +1 -1
  72. package/dist/pc/pc.infusions.d.ts +9 -1
  73. package/dist/pc/pc.infusions.js +3 -3
  74. package/dist/pc/pc.member-choices.d.ts +90 -0
  75. package/dist/pc/pc.member-choices.js +32 -0
  76. package/dist/pc/pc.parser.js +1 -1
  77. package/dist/pc/pc.pools.d.ts +7 -0
  78. package/dist/pc/pc.pools.js +5 -3
  79. package/dist/pc/pc.proficiencies.js +13 -12
  80. package/dist/pc/pc.proficiency-query.js +6 -0
  81. package/dist/pc/pc.recalc.js +75 -58
  82. package/dist/pc/pc.resolver.d.ts +2 -1
  83. package/dist/pc/pc.resolver.js +13 -12
  84. package/dist/pc/pc.rest.js +4 -4
  85. package/dist/pc/pc.schema.d.ts +29 -2
  86. package/dist/pc/pc.schema.js +1 -1
  87. package/dist/pc/pc.types.d.ts +45 -2
  88. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  89. package/dist/pc/pool-layout.d.ts +3 -9
  90. package/dist/pc/pool-layout.js +1 -3
  91. package/dist/pc/resource-equivalents.d.ts +44 -0
  92. package/dist/pc/spell-source.js +4 -9
  93. package/dist/race/race.codec.js +6 -6
  94. package/dist/race/race.parser.js +5 -5
  95. package/dist/race/race.schema.js +4 -4
  96. package/dist/schemas/choice-schema.js +2 -2
  97. package/dist/schemas/feature-effect-schema.d.ts +25 -0
  98. package/dist/schemas/feature-effect-schema.js +1 -1
  99. package/dist/schemas/feature-schema.js +3 -3
  100. package/dist/spell/spell.codec.js +2 -2
  101. package/dist/spell/spell.parser.d.ts +6 -1
  102. package/dist/spell/spell.parser.js +5 -3
  103. package/dist/spell/spell.types.d.ts +10 -0
  104. package/dist/subclass/subclass.codec.js +6 -6
  105. package/dist/subclass/subclass.parser.js +5 -5
  106. package/dist/subclass/subclass.schema.js +4 -4
  107. package/dist/types/choice.d.ts +5 -0
  108. package/dist/types/feature-effect.d.ts +6 -0
  109. package/dist/types/optional-feature.types.d.ts +5 -0
  110. package/dist/types/selection-pool.d.ts +2 -2
  111. 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-2UDSN6VW.js";
4
+ import "../chunk-6EI737IO.js";
5
+ import "../chunk-3MGQFFNE.js";
6
6
  import "../chunk-IN4M4X7P.js";
7
- import "../chunk-BRGLDWUP.js";
8
- import "../chunk-ZMJQUZQH.js";
7
+ import "../chunk-5MXFW4O3.js";
8
+ import "../chunk-2JSQE3DD.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-6EI737IO.js";
4
+ import "../chunk-3MGQFFNE.js";
5
5
  import "../chunk-IN4M4X7P.js";
6
- import "../chunk-BRGLDWUP.js";
7
- import "../chunk-ZMJQUZQH.js";
6
+ import "../chunk-5MXFW4O3.js";
7
+ import "../chunk-2JSQE3DD.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-3MGQFFNE.js";
5
5
  import "../chunk-IN4M4X7P.js";
6
- import "../chunk-BRGLDWUP.js";
7
- import "../chunk-ZMJQUZQH.js";
6
+ import "../chunk-5MXFW4O3.js";
7
+ import "../chunk-2JSQE3DD.js";
8
8
  import "../chunk-UA5KS2PL.js";
9
9
  export {
10
10
  backgroundEntitySchema,
@@ -7,6 +7,17 @@ var weaponScopeEnum = z.enum(["weapon", "melee", "ranged", "unarmed", "finesse",
7
7
  var featureEffectSchema = z.discriminatedUnion("kind", [
8
8
  z.object({ kind: z.literal("initiative-bonus"), value: z.number().int(), condition: conditionField, subject: subjectField }),
9
9
  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 }),
10
+ // SL (2026-09-27): the `skill-bonus` twin for saving throws (Aura of Protection). `saves` absent = every save; `inactive_while` turns
11
+ // the bonus off while the character is incapacitated (any condition that disables actions) or unconscious.
12
+ z.object({
13
+ kind: z.literal("save-bonus"),
14
+ ability: abilityEnum,
15
+ minimum: z.number().int().optional(),
16
+ saves: z.array(abilityEnum).nonempty().optional(),
17
+ inactive_while: z.enum(["incapacitated", "unconscious"]).optional(),
18
+ condition: conditionField,
19
+ subject: subjectField
20
+ }),
10
21
  // Jack of All Trades / Remarkable Athlete: half the proficiency bonus, and ONLY on a skill the character is
11
22
  // not already proficient in — the "doesn't otherwise use your Proficiency Bonus" clause is the whole mechanic,
12
23
  // so it is the kind's semantics, not a field. `skills` omitted = every skill (Jack of All Trades); a list
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBackground
3
- } from "./chunk-RQNJXADQ.js";
3
+ } from "./chunk-6EI737IO.js";
4
4
 
5
5
  // src/background/background.codec.ts
6
6
  import yaml from "js-yaml";
@@ -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
+ };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-IN4M4X7P.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-BRGLDWUP.js";
6
+ } from "./chunk-5MXFW4O3.js";
7
7
  import {
8
8
  additionalSpellsEntrySchema,
9
9
  imageField
@@ -3,14 +3,10 @@ import {
3
3
  } from "./chunk-W6GC6OKK.js";
4
4
  import {
5
5
  derivePoolLayout
6
- } from "./chunk-HJQNK3J5.js";
6
+ } from "./chunk-B3DP42OM.js";
7
7
  import {
8
8
  readTableColumn
9
9
  } from "./chunk-IUAAXXNO.js";
10
- import {
11
- findItemAction,
12
- resolveItemAction
13
- } from "./chunk-ZUQW5VBZ.js";
14
10
  import {
15
11
  requiresAttunement
16
12
  } from "./chunk-IHE6KVC3.js";
@@ -22,6 +18,10 @@ import {
22
18
  resolveEntityForEntry,
23
19
  slotAcTerms
24
20
  } from "./chunk-LHZXYFBU.js";
21
+ import {
22
+ findItemAction,
23
+ resolveItemAction
24
+ } from "./chunk-ZUQW5VBZ.js";
25
25
  import {
26
26
  warnOnce
27
27
  } from "./chunk-LVQLWRCU.js";
@@ -41,29 +41,93 @@ import {
41
41
  isCanonicalDamageType
42
42
  } from "./chunk-PDJSUH24.js";
43
43
 
44
+ // src/pc/resource-equivalents.ts
45
+ var RESOURCE_EQUIVALENTS = [
46
+ { name: "Superiority Dice", ids: ["fighter:superiority-dice", "fighter-2024:superiority-dice"] },
47
+ { name: "Sorcery Points", ids: ["sorcerer:sorcery-point", "sorcerer:sorcery-points", "sorcerer-2024:sorcery-point"] },
48
+ { name: "Ki / Focus Points", ids: ["monk:ki", "monk:ki-points", "monk:focus-point", "monk:focus-points"] },
49
+ { name: "Channel Divinity (Cleric)", ids: ["cleric:channel-divinity", "cleric-2024:channel-divinity", "channel-divinity"] },
50
+ { name: "Channel Divinity (Paladin)", ids: ["paladin:channel-divinity", "paladin-2024:channel-divinity", "channel-divinity"] },
51
+ { name: "Bardic Inspiration", ids: ["bard:bardic-inspiration", "bard:bardic-die"] },
52
+ { name: "Rage", ids: ["barbarian:rage", "barbarian:rages", "barbarian-2024:rages"] },
53
+ { name: "Wild Shape", ids: ["druid:wild-shape", "druid-2024:wild-shape"] },
54
+ { name: "Soul Trinkets", ids: ["rogue:soul-trinket", "rogue-2024:soul-trinket"] }
55
+ ];
56
+ function equivalentResourceIds(id, groups = RESOURCE_EQUIVALENTS) {
57
+ const out = [];
58
+ for (const g of groups) {
59
+ if (!g.ids.includes(id)) continue;
60
+ for (const other of g.ids) if (other !== id && !out.includes(other)) out.push(other);
61
+ }
62
+ return out;
63
+ }
64
+ function ownedByClass(res, rc) {
65
+ var _a, _b;
66
+ const s = (_a = res == null ? void 0 : res.owner) == null ? void 0 : _a.source;
67
+ if (!s) return false;
68
+ if (s.kind === "class") return s.slug === ((_b = rc.entity) == null ? void 0 : _b.slug);
69
+ if (s.kind === "subclass") return !!rc.subclass && s.slug === rc.subclass.slug;
70
+ return false;
71
+ }
72
+ function payingResourceId(id, index, owner, groups = RESOURCE_EQUIVALENTS) {
73
+ const candidates = [id, ...equivalentResourceIds(id, groups)].filter((c) => index.has(c));
74
+ if (candidates.length === 0) return void 0;
75
+ if (owner) {
76
+ const own = candidates.find((c) => ownedByClass(index.get(c), owner));
77
+ if (own) return own;
78
+ }
79
+ return candidates[0];
80
+ }
81
+ function classOfSource(source, classes) {
82
+ var _a, _b;
83
+ if (source.kind === "class") return (_a = classes.find((c) => {
84
+ var _a2;
85
+ return ((_a2 = c.entity) == null ? void 0 : _a2.slug) === source.slug;
86
+ })) != null ? _a : null;
87
+ if (source.kind === "subclass") return (_b = classes.find((c) => {
88
+ var _a2;
89
+ return ((_a2 = c.subclass) == null ? void 0 : _a2.slug) === source.slug;
90
+ })) != null ? _b : null;
91
+ return null;
92
+ }
93
+ function bindFeatureConsumers(features, index, classes) {
94
+ return features.map((rf) => {
95
+ const consumes = rf.feature.consumes;
96
+ const id = consumes == null ? void 0 : consumes.resource;
97
+ if (!consumes || typeof id !== "string" || id.length === 0) return rf;
98
+ const payer = payingResourceId(id, index, classOfSource(rf.source, classes));
99
+ if (payer === void 0 || payer === id) return rf;
100
+ return { ...rf, feature: { ...rf.feature, consumes: { ...consumes, resource: payer } } };
101
+ });
102
+ }
103
+
44
104
  // src/pc/pc.pools.ts
45
105
  function levelPrereqMax(d) {
46
106
  var _a;
47
107
  return ((_a = d.prerequisites) != null ? _a : []).filter((p) => p.kind === "level").reduce((m, p) => Math.max(m, p.min), 0);
48
108
  }
49
- function resolvePool(rc, classIndex, pool, registry, index, edition) {
50
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
51
- const entity = rc.entity;
52
- const level = rc.level;
53
- const ownerBare = bareEntitySlug(entity.slug);
54
- const ownerPrefix = entity.slug.split("_")[0];
55
- const col = pool.count.column;
56
- const count = (_a = readTableColumn(entity.table, level, [col])) != null ? _a : 0;
109
+ function poolAnchorLevel(entity, pool) {
57
110
  let anchorLevel = 1;
58
111
  let columnSeen = false;
59
112
  for (let l = 1; l <= 20; l++) {
60
- const c = readTableColumn(entity.table, l, [col]);
113
+ const c = readTableColumn(entity.table, l, [pool.count.column]);
61
114
  if (c != null) columnSeen = true;
62
115
  if (c != null && c >= 1) {
63
116
  anchorLevel = l;
64
117
  break;
65
118
  }
66
119
  }
120
+ return { anchorLevel, columnSeen };
121
+ }
122
+ function resolvePool(rc, classIndex, pool, registry, index, edition) {
123
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
124
+ const entity = rc.entity;
125
+ const level = rc.level;
126
+ const ownerBare = bareEntitySlug(entity.slug);
127
+ const ownerPrefix = entity.slug.split("_")[0];
128
+ const col = pool.count.column;
129
+ const count = (_a = readTableColumn(entity.table, level, [col])) != null ? _a : 0;
130
+ const { anchorLevel, columnSeen } = poolAnchorLevel(entity, pool);
67
131
  if (!columnSeen) {
68
132
  warnOnce(
69
133
  `pool-column:${pool.id}`,
@@ -87,10 +151,10 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
87
151
  if (group) group.push(entry);
88
152
  else candidates.set(bare, [entry]);
89
153
  }
90
- const available = [];
154
+ const collapsed = [];
91
155
  for (const [bare, group] of candidates) {
92
156
  if (group.length === 1) {
93
- available.push(group[0]);
157
+ collapsed.push(group[0]);
94
158
  continue;
95
159
  }
96
160
  const owned = group.find((e) => e.slug.split("_")[0] === ownerPrefix);
@@ -100,7 +164,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
100
164
  `Archivist: pool "${pool.id}" holds ${group.length} cross-edition entries for "${bare}" and none shares the owner's compendium prefix "${ownerPrefix}"; keeping "${group[0].slug}"`
101
165
  );
102
166
  }
103
- available.push(owned != null ? owned : group[0]);
167
+ collapsed.push(owned != null ? owned : group[0]);
104
168
  }
105
169
  const grantDecls = [
106
170
  ...(_d = (_c = rc.entity) == null ? void 0 : _c.pool_grants) != null ? _d : [],
@@ -114,6 +178,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
114
178
  grantedBare.add(key);
115
179
  return true;
116
180
  });
181
+ const available = collapsed.filter((e) => !grantedBare.has(bareEntitySlug(e.slug)));
117
182
  const bySlugAvailable = new Map(available.map((e) => [e.slug, e]));
118
183
  const byBareAvailable = new Map(available.map((e) => [bareEntitySlug(e.slug), e]));
119
184
  const resolveStored = (s) => {
@@ -152,8 +217,9 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
152
217
  const votes = /* @__PURE__ */ new Map();
153
218
  for (const e of members) {
154
219
  const id = (_h = e.entity.consumes) == null ? void 0 : _h.resource;
155
- if (!id || !index.has(id)) continue;
156
- votes.set(id, ((_i = votes.get(id)) != null ? _i : 0) + 1);
220
+ const payer = id ? payingResourceId(id, index, rc) : void 0;
221
+ if (!payer) continue;
222
+ votes.set(payer, ((_i = votes.get(payer)) != null ? _i : 0) + 1);
157
223
  }
158
224
  let resource;
159
225
  let bestN = 0;
@@ -161,7 +227,7 @@ function resolvePool(rc, classIndex, pool, registry, index, edition) {
161
227
  resource = id;
162
228
  bestN = n;
163
229
  }
164
- const bind = resource ? poolRebinder(rc, pool.id, resource, index) : (e) => e;
230
+ const bind = poolRebinder(rc, pool.id, resource, index);
165
231
  return {
166
232
  id: pool.id,
167
233
  label: pool.label,
@@ -184,14 +250,20 @@ function poolRebinder(rc, poolId, vote, index) {
184
250
  return (e) => {
185
251
  const consumes = e.entity.consumes;
186
252
  const id = consumes == null ? void 0 : consumes.resource;
187
- if (!consumes || typeof id !== "string" || id.length === 0 || index.has(id) || declared.has(id)) return e;
253
+ if (!consumes || typeof id !== "string" || id.length === 0) return e;
254
+ const payer = payingResourceId(id, index, rc);
255
+ if (payer === id) return e;
256
+ const target = payer != null ? payer : vote !== void 0 && !declared.has(id) ? vote : void 0;
257
+ if (target === void 0) return e;
188
258
  const done = copies.get(e.key);
189
259
  if (done) return done;
190
- warnOnce(
191
- `pool-rebind:${poolId}:${id}`,
192
- `Archivist: pool "${poolId}" members consume "${id}", which this character does not own; bound to the pool's resource "${vote}"`
193
- );
194
- const copy = { ...e, entity: { ...e.entity, consumes: { ...consumes, resource: vote } } };
260
+ if (payer === void 0) {
261
+ warnOnce(
262
+ `pool-rebind:${poolId}:${id}`,
263
+ `Archivist: pool "${poolId}" members consume "${id}", which this character does not own; bound to the pool's resource "${target}"`
264
+ );
265
+ }
266
+ const copy = { ...e, entity: { ...e.entity, consumes: { ...consumes, resource: target } } };
195
267
  copies.set(e.key, copy);
196
268
  return copy;
197
269
  };
@@ -334,10 +406,23 @@ function resolveOptionRows(note, registry, opts = {}) {
334
406
  ...row.attunement !== void 0 ? { attunement: row.attunement } : {},
335
407
  arm: row.creates.item ? "item" : row.creates.variant ? "variant" : "filter",
336
408
  candidates,
337
- availability: anyVisible ? "installed" : installed ? "hidden-only" : "not-installed"
409
+ availability: anyVisible ? "installed" : installed ? "hidden-only" : "not-installed",
410
+ ...rowItemWords(row, candidates[0] ? registry.getByTypeAndSlug("item", candidates[0]) : rowItems[0])
338
411
  };
339
412
  });
340
413
  }
414
+ function rowItemWords(row, item) {
415
+ var _a, _b, _c, _d;
416
+ const f = row.creates.filter;
417
+ if (f) return { type: ((_a = f.types) == null ? void 0 : _a[0]) ? lc(f.types[0]) : null, rarity: f.rarity[0] ? lc(f.rarity[0]) : null, charges: null };
418
+ const data = (_b = item == null ? void 0 : item.data) != null ? _b : {};
419
+ const max = (_c = data.charges) == null ? void 0 : _c.max;
420
+ return {
421
+ type: lc(data.type) || null,
422
+ rarity: lc(data.rarity) || null,
423
+ charges: typeof max === "number" && max > 0 ? { max, ...typeof ((_d = data.charges) == null ? void 0 : _d.recharge) === "string" ? { recharge: data.charges.recharge } : {} } : null
424
+ };
425
+ }
341
426
  function resolveCopyOption(note, param, ctx) {
342
427
  var _a, _b, _c;
343
428
  const rows = (_a = note.options) != null ? _a : [];
@@ -920,7 +1005,9 @@ export {
920
1005
  infusionsEndedByClassEdit,
921
1006
  reconcileClassEdit,
922
1007
  eligibleTargets,
1008
+ bindFeatureConsumers,
923
1009
  levelPrereqMax,
1010
+ poolAnchorLevel,
924
1011
  resolvePool,
925
1012
  resolveAllPools,
926
1013
  strandedPicks
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featureEffectSchema
3
- } from "./chunk-ZMJQUZQH.js";
3
+ } from "./chunk-2JSQE3DD.js";
4
4
 
5
5
  // src/schemas/choice-schema.ts
6
6
  import { z } from "zod";
@@ -16,7 +16,9 @@ var entityFilterSchema = z.object({
16
16
  mundane: z.boolean().optional(),
17
17
  list: z.string().min(1).optional(),
18
18
  level: z.number().int().min(0).max(9).optional(),
19
- edition: z.string().min(1).optional()
19
+ edition: z.string().min(1).optional(),
20
+ // D2b-A (design §2.2 step 1): Pact of the Tome's "level 1 ritual spells".
21
+ ritual: z.boolean().optional()
20
22
  }).strict();
21
23
  var inlineOptionSchema = z.lazy(() => z.object({
22
24
  value: z.string().min(1),
@@ -40,7 +42,9 @@ var choiceSchema = z.lazy(() => z.discriminatedUnion("kind", [
40
42
  count: z.number().int().positive().optional(),
41
43
  entity_type: z.string().min(1),
42
44
  from: z.array(z.string().min(1)).nonempty().optional(),
43
- where: entityFilterSchema.optional()
45
+ where: entityFilterSchema.optional(),
46
+ // D2b-A: a pick that names a target (a blast's cantrip), never a grant.
47
+ target: z.literal(true).optional()
44
48
  }).strict(),
45
49
  z.object({
46
50
  kind: z.literal("select-proficiency"),
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backgroundEntitySchema
3
- } from "./chunk-MUIU7RZ3.js";
3
+ } from "./chunk-3MGQFFNE.js";
4
4
 
5
5
  // src/background/background.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featureObjectSchema
3
- } from "./chunk-FPGXFD2M.js";
3
+ } from "./chunk-YD2VAHA6.js";
4
4
  import {
5
5
  imageField
6
6
  } from "./chunk-UA5KS2PL.js";
@@ -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,6 +1,6 @@
1
1
  import {
2
2
  featEntitySchema
3
- } from "./chunk-LVX6TQ3R.js";
3
+ } from "./chunk-PKX6TGRE.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
  parseRace
3
- } from "./chunk-R2V5LN3I.js";
3
+ } from "./chunk-Y5VVP73C.js";
4
4
 
5
5
  // src/race/race.codec.ts
6
6
  import yaml from "js-yaml";
@@ -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,
@@ -6,9 +6,12 @@ import {
6
6
  resetTriggerEnum,
7
7
  resourceConsumptionSchema
8
8
  } from "./chunk-ZJXSXVFP.js";
9
+ import {
10
+ choiceSchema
11
+ } from "./chunk-5MXFW4O3.js";
9
12
  import {
10
13
  featureEffectSchema
11
- } from "./chunk-ZMJQUZQH.js";
14
+ } from "./chunk-2JSQE3DD.js";
12
15
  import {
13
16
  additionalSpellsEntrySchema,
14
17
  imageField,
@@ -138,7 +141,9 @@ var optionalFeatureEntitySchema = z.object({
138
141
  repeatable: z.literal(true).optional(),
139
142
  item_choice: z.enum(["learn", "create"]).optional(),
140
143
  options: z.array(optionRowSchema).optional(),
141
- infuse: infuseBlockSchema.optional()
144
+ infuse: infuseBlockSchema.optional(),
145
+ // D2b-A (design §2.2 step 1): a pool member's own choices, the vocabulary features and feats use.
146
+ choices: z.array(choiceSchema).optional()
142
147
  }).superRefine((d, ctx) => {
143
148
  var _a;
144
149
  if (d.repeatable && (!d.options || !d.item_choice)) ctx.addIssue({ code: "custom", message: "a repeatable note needs options and item_choice" });
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  featureSchema
3
- } from "./chunk-FPGXFD2M.js";
3
+ } from "./chunk-YD2VAHA6.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-BRGLDWUP.js";
6
+ } from "./chunk-5MXFW4O3.js";
7
7
  import {
8
8
  additionalSpellsEntrySchema,
9
9
  imageField
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseOptionalFeature
3
- } from "./chunk-QSBJ6NEM.js";
3
+ } from "./chunk-PQZ4LM4Z.js";
4
4
 
5
5
  // src/optional-feature/optional-feature.codec.ts
6
6
  import yaml from "js-yaml";