@archivist-gg/dnd5e 0.3.11 → 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 (192) hide show
  1. package/LICENSES/SRD.md +38 -3
  2. package/dist/armor/armor.codec.js +3 -3
  3. package/dist/armor/armor.parser.js +2 -2
  4. package/dist/armor/armor.schema.d.ts +1 -0
  5. package/dist/armor/armor.schema.js +1 -1
  6. package/dist/armor/armor.types.d.ts +2 -0
  7. package/dist/background/background.codec.js +5 -5
  8. package/dist/background/background.parser.js +4 -4
  9. package/dist/background/background.schema.js +3 -3
  10. package/dist/{chunk-XTRDCSJB.js → chunk-2JSQE3DD.js} +15 -1
  11. package/dist/chunk-2LULMQTF.js +14 -0
  12. package/dist/{chunk-MAH6DCRI.js → chunk-2UDSN6VW.js} +1 -1
  13. package/dist/chunk-3LWMFYKS.js +57 -0
  14. package/dist/{chunk-JCEV7KYN.js → chunk-3MGQFFNE.js} +1 -1
  15. package/dist/chunk-3VZZDKRA.js +1014 -0
  16. package/dist/{chunk-2DFENQIJ.js → chunk-5GKJAUMX.js} +2 -0
  17. package/dist/{chunk-2M34FLAY.js → chunk-5MXFW4O3.js} +9 -3
  18. package/dist/{chunk-4DOMDEBZ.js → chunk-6EI737IO.js} +1 -1
  19. package/dist/{chunk-G247QXWW.js → chunk-7ATNGKBS.js} +1 -1
  20. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  21. package/dist/{chunk-T2SNEJOE.js → chunk-7QN7XSAK.js} +1 -1
  22. package/dist/{chunk-DNAJRMNN.js → chunk-A54HHCWW.js} +6 -7
  23. package/dist/{chunk-7YU4NPKR.js → chunk-AZRGFUSR.js} +1 -1
  24. package/dist/{chunk-PP2UHLBA.js → chunk-B3DP42OM.js} +5 -0
  25. package/dist/chunk-CM5CWO4K.js +735 -0
  26. package/dist/{chunk-MQ747UCR.js → chunk-DOXSFYLH.js} +70 -2
  27. package/dist/chunk-EJP263OP.js +186 -0
  28. package/dist/{chunk-SMLXZUBH.js → chunk-EYX4JMAU.js} +4 -1
  29. package/dist/{chunk-V6LJFP4R.js → chunk-FANQVQ3D.js} +2 -2
  30. package/dist/{chunk-AW3EHF3L.js → chunk-FC4IGH5Y.js} +1 -1
  31. package/dist/{chunk-23LZLBJK.js → chunk-G6MHJDVT.js} +6 -2
  32. package/dist/{chunk-JANNRGJC.js → chunk-G6PBI2BU.js} +1 -1
  33. package/dist/chunk-H5PTKYXL.js +101 -0
  34. package/dist/{chunk-LWFTHJIE.js → chunk-HHBD4SQ6.js} +1 -1
  35. package/dist/{chunk-JC5UNIVM.js → chunk-HS6FIOLP.js} +2 -0
  36. package/dist/chunk-HTYMW5CH.js +228 -0
  37. package/dist/chunk-IHE6KVC3.js +13 -0
  38. package/dist/{chunk-QHMWA3SZ.js → chunk-JCMUVZIK.js} +1 -1
  39. package/dist/{chunk-V373TRJE.js → chunk-JSM6JFZR.js} +198 -125
  40. package/dist/{chunk-LDDCNPHD.js → chunk-K5NJBTE2.js} +1 -1
  41. package/dist/chunk-KR6JF3BF.js +14 -0
  42. package/dist/{chunk-4ISR76DH.js → chunk-KSTV7J2U.js} +9 -3
  43. package/dist/chunk-LBEXLN6I.js +91 -0
  44. package/dist/{chunk-BLRW2UU6.js → chunk-LF5AGJA4.js} +2 -2
  45. package/dist/{chunk-UBBKSYBY.js → chunk-LHZXYFBU.js} +30 -1
  46. package/dist/{chunk-BIA32MQ5.js → chunk-MBX25B6D.js} +1 -1
  47. package/dist/{chunk-R2ZV233X.js → chunk-MYY2C5MB.js} +3 -3
  48. package/dist/{chunk-J77CKYV3.js → chunk-OFCSP6MT.js} +1 -1
  49. package/dist/{chunk-2INMRR3Q.js → chunk-ONTGZR5T.js} +326 -149
  50. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  51. package/dist/{chunk-U3BABVJF.js → chunk-PKX6TGRE.js} +2 -2
  52. package/dist/{chunk-PJMG5XV3.js → chunk-PQZ4LM4Z.js} +1 -1
  53. package/dist/chunk-PXVNAY7T.js +84 -0
  54. package/dist/chunk-QH36K2PU.js +158 -0
  55. package/dist/{chunk-R747YKS7.js → chunk-QZSWN2KT.js} +1 -1
  56. package/dist/{chunk-CHUALUIX.js → chunk-RYLXAYIQ.js} +1 -1
  57. package/dist/{chunk-GFZ7PITJ.js → chunk-S3YUXDSC.js} +6 -1
  58. package/dist/{chunk-PZCE7DJM.js → chunk-T6QIF5XR.js} +31 -1
  59. package/dist/{chunk-CBEKVAX4.js → chunk-TULQ4FFX.js} +41 -13
  60. package/dist/{chunk-S56D6B6C.js → chunk-UUBJCNQU.js} +12 -3
  61. package/dist/{chunk-5N2GW72F.js → chunk-UZXG6QSY.js} +2 -1
  62. package/dist/{chunk-4XTNPNQB.js → chunk-VPYBK2MG.js} +1 -1
  63. package/dist/{chunk-3HX5VLWP.js → chunk-VRV3NNTA.js} +2 -2
  64. package/dist/{chunk-HZA7OE7N.js → chunk-XCFC7LGF.js} +2 -1
  65. package/dist/{chunk-BL3VELKP.js → chunk-Y5VVP73C.js} +1 -1
  66. package/dist/{chunk-PI4LLJG4.js → chunk-YD2VAHA6.js} +2 -2
  67. package/dist/{chunk-UIRMA5KM.js → chunk-YDGMBMYI.js} +1 -1
  68. package/dist/{chunk-NWDF5HG3.js → chunk-YKL6SHRJ.js} +1 -1
  69. package/dist/{chunk-6X4KUDMU.js → chunk-ZCTUYGNV.js} +7 -2
  70. package/dist/chunk-ZUQW5VBZ.js +89 -0
  71. package/dist/class/class.codec.js +7 -7
  72. package/dist/class/class.parser.js +6 -6
  73. package/dist/class/class.schema.d.ts +18 -0
  74. package/dist/class/class.schema.js +5 -5
  75. package/dist/class/class.standard-array.d.ts +16 -0
  76. package/dist/class/class.standard-array.js +20 -0
  77. package/dist/class/class.types.d.ts +3 -0
  78. package/dist/dnd/amount-text.d.ts +38 -0
  79. package/dist/dnd/amount-text.js +13 -0
  80. package/dist/dnd/resource-words.d.ts +4 -0
  81. package/dist/dnd/resource-words.js +30 -0
  82. package/dist/entities/base-item-resolver.js +2 -2
  83. package/dist/entities/slug.d.ts +18 -8
  84. package/dist/entities/slug.js +3 -1
  85. package/dist/feat/feat.codec.js +5 -5
  86. package/dist/feat/feat.parser.js +4 -4
  87. package/dist/feat/feat.schema.d.ts +41 -3
  88. package/dist/feat/feat.schema.js +3 -3
  89. package/dist/index.js +7513 -4177
  90. package/dist/item/item.actions-map.d.ts +40 -5
  91. package/dist/item/item.actions-map.js +5 -84
  92. package/dist/item/item.attunement.js +3 -9
  93. package/dist/item/item.bonuses.js +5 -2
  94. package/dist/item/item.codec.js +3 -3
  95. package/dist/item/item.conditions.js +4 -1
  96. package/dist/item/item.conditions.types.d.ts +4 -0
  97. package/dist/item/item.cost.d.ts +3 -0
  98. package/dist/item/item.cost.js +29 -0
  99. package/dist/item/item.icon.d.ts +15 -0
  100. package/dist/item/item.icon.js +211 -0
  101. package/dist/item/item.parser.js +2 -2
  102. package/dist/item/item.schema.d.ts +16 -1
  103. package/dist/item/item.schema.js +3 -1
  104. package/dist/item/item.types.d.ts +9 -2
  105. package/dist/monster/monster.parser.js +5 -5
  106. package/dist/monster/monster.schema.js +4 -4
  107. package/dist/optional-feature/optional-feature.codec.js +5 -4
  108. package/dist/optional-feature/optional-feature.parser.js +4 -3
  109. package/dist/optional-feature/optional-feature.schema.d.ts +255 -3
  110. package/dist/optional-feature/optional-feature.schema.js +7 -2
  111. package/dist/pc/pc.additional-spells.js +3 -3
  112. package/dist/pc/pc.amount-bindings.d.ts +32 -0
  113. package/dist/pc/pc.amount-bindings.js +23 -0
  114. package/dist/pc/pc.asi-flatten.d.ts +62 -1
  115. package/dist/pc/pc.asi-flatten.js +16 -0
  116. package/dist/pc/pc.conditions.js +1 -1
  117. package/dist/pc/pc.containment.d.ts +45 -0
  118. package/dist/pc/pc.containment.js +24 -0
  119. package/dist/pc/pc.decision-engine.d.ts +24 -3
  120. package/dist/pc/pc.decision-engine.js +26 -16
  121. package/dist/pc/pc.entry-overrides.d.ts +79 -0
  122. package/dist/pc/pc.entry-overrides.js +40 -0
  123. package/dist/pc/pc.equipment.d.ts +2 -2
  124. package/dist/pc/pc.equipment.js +19 -7
  125. package/dist/pc/pc.feature-effects.d.ts +12 -0
  126. package/dist/pc/pc.feature-effects.js +2 -2
  127. package/dist/pc/pc.infusions.d.ts +228 -0
  128. package/dist/pc/pc.infusions.js +64 -0
  129. package/dist/pc/pc.member-choices.d.ts +90 -0
  130. package/dist/pc/pc.member-choices.js +32 -0
  131. package/dist/pc/pc.parser.js +18 -1
  132. package/dist/pc/pc.pools.d.ts +8 -1
  133. package/dist/pc/pc.pools.js +13 -3
  134. package/dist/pc/pc.proficiencies.js +23 -15
  135. package/dist/pc/pc.proficiency-query.js +6 -0
  136. package/dist/pc/pc.recalc.d.ts +39 -36
  137. package/dist/pc/pc.recalc.js +115 -70
  138. package/dist/pc/pc.resolver.d.ts +2 -1
  139. package/dist/pc/pc.resolver.js +22 -14
  140. package/dist/pc/pc.resource-seed.js +1 -1
  141. package/dist/pc/pc.resources.d.ts +6 -1
  142. package/dist/pc/pc.resources.js +3 -1
  143. package/dist/pc/pc.rest.d.ts +21 -1
  144. package/dist/pc/pc.rest.js +52 -19
  145. package/dist/pc/pc.schema.d.ts +43 -2
  146. package/dist/pc/pc.schema.js +1 -1
  147. package/dist/pc/pc.slotting.d.ts +36 -3
  148. package/dist/pc/pc.slotting.js +13 -5
  149. package/dist/pc/pc.tool-picks.d.ts +51 -0
  150. package/dist/pc/pc.tool-picks.js +18 -0
  151. package/dist/pc/pc.types.d.ts +115 -14
  152. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  153. package/dist/pc/pool-layout.d.ts +8 -0
  154. package/dist/pc/pool-layout.js +5 -3
  155. package/dist/pc/resource-equivalents.d.ts +44 -0
  156. package/dist/pc/spell-source.js +4 -9
  157. package/dist/race/race.codec.js +6 -6
  158. package/dist/race/race.parser.js +5 -5
  159. package/dist/race/race.schema.js +4 -4
  160. package/dist/schemas/attack-schema.d.ts +1 -1
  161. package/dist/schemas/choice-schema.js +2 -2
  162. package/dist/schemas/feature-effect-schema.d.ts +41 -3
  163. package/dist/schemas/feature-effect-schema.js +1 -1
  164. package/dist/schemas/feature-schema.js +3 -3
  165. package/dist/schemas/selection-pool-schema.d.ts +10 -0
  166. package/dist/schemas/selection-pool-schema.js +1 -1
  167. package/dist/spell/spell.codec.js +2 -2
  168. package/dist/spell/spell.parser.d.ts +6 -1
  169. package/dist/spell/spell.parser.js +5 -3
  170. package/dist/spell/spell.types.d.ts +10 -0
  171. package/dist/srd/retired-items.d.ts +14 -0
  172. package/dist/srd/retired-items.js +11 -0
  173. package/dist/subclass/subclass.codec.js +7 -7
  174. package/dist/subclass/subclass.parser.js +6 -6
  175. package/dist/subclass/subclass.schema.d.ts +10 -0
  176. package/dist/subclass/subclass.schema.js +5 -5
  177. package/dist/types/choice.d.ts +13 -0
  178. package/dist/types/equipment-grant.d.ts +18 -1
  179. package/dist/types/equipment-grant.js +12 -0
  180. package/dist/types/feature-effect.d.ts +12 -1
  181. package/dist/types/optional-feature.types.d.ts +84 -1
  182. package/dist/types/selection-pool.d.ts +18 -3
  183. package/dist/weapon/weapon.classify.d.ts +10 -8
  184. package/dist/weapon/weapon.classify.js +2 -2
  185. package/dist/weapon/weapon.codec.js +3 -3
  186. package/dist/weapon/weapon.parser.js +2 -2
  187. package/dist/weapon/weapon.schema.d.ts +1 -0
  188. package/dist/weapon/weapon.schema.js +1 -1
  189. package/dist/weapon/weapon.types.d.ts +2 -0
  190. package/package.json +1 -1
  191. package/dist/chunk-O4SRD3J5.js +0 -165
  192. package/dist/chunk-WXWPK3LI.js +0 -55
@@ -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-XTRDCSJB.js";
14
+ } from "./chunk-2JSQE3DD.js";
12
15
  import {
13
16
  additionalSpellsEntrySchema,
14
17
  imageField,
@@ -46,6 +49,58 @@ var usesSchema = z.object({
46
49
  restores: z.enum(["uses", "spell-slots"]).optional()
47
50
  })).optional()
48
51
  });
52
+ var filterSpecSchema = z.object({
53
+ rarity: z.array(z.string().min(1)).min(1),
54
+ types: z.array(z.string().min(1)).optional(),
55
+ exclude_types: z.array(z.string().min(1)).optional(),
56
+ cursed: z.literal(false).optional()
57
+ }).strict();
58
+ var optionCreatesSchema = z.object({
59
+ item: z.string().regex(wikilinkRegex).optional(),
60
+ variant: z.object({ candidates: z.array(z.string().regex(wikilinkRegex)).min(1) }).strict().optional(),
61
+ filter: filterSpecSchema.optional()
62
+ }).strict().refine((c) => [c.item, c.variant, c.filter].filter((a) => a !== void 0).length === 1, { message: "creates needs exactly one of item, variant, filter" });
63
+ var optionRowSchema = z.object({
64
+ key: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an option key is lower-case, with no '#' and no whitespace"),
65
+ name: z.string().min(1),
66
+ min_level: z.number().int().min(1).max(20),
67
+ attunement: z.boolean().optional(),
68
+ creates: optionCreatesSchema
69
+ }).strict();
70
+ var infuseItemSchema = z.object({
71
+ bonuses: z.object({
72
+ ac: z.number().int().optional(),
73
+ weapon_attack: z.number().int().optional(),
74
+ weapon_damage: z.number().int().optional(),
75
+ spell_attack: z.number().int().optional(),
76
+ saving_throws: z.number().int().optional(),
77
+ speed: z.object({ walk: z.number().int().optional(), fly: z.number().int().optional(), swim: z.number().int().optional(), climb: z.number().int().optional() }).strict().optional()
78
+ }).strict().optional(),
79
+ resist: z.array(z.string().min(1)).optional(),
80
+ charges: z.object({ max: z.number().int().min(1), dice: z.string().optional(), recharge: z.string().optional(), recharge_amount: z.string().optional() }).strict().optional(),
81
+ action: actionCostEnum.optional(),
82
+ // ART-c1 (R-3 a): the Actions row's range, as data (B-8.4 draws "Self").
83
+ range: z.string().min(1).optional()
84
+ }).strict();
85
+ var infuseBlockSchema = z.object({
86
+ targets: z.array(z.object({
87
+ kind: z.enum(["armor", "shield", "weapon", "gear"]),
88
+ category: z.array(z.string().min(1)).min(1).optional(),
89
+ property: z.string().min(1).optional(),
90
+ name_any: z.array(z.string().min(1)).min(1).optional()
91
+ }).strict()).min(1),
92
+ targets_label: z.string().min(1).optional(),
93
+ new_object: z.string().min(1).optional(),
94
+ rarity: z.string().min(1).optional(),
95
+ attunement: z.object({ required: z.literal(true) }).strict().optional(),
96
+ // ART-c1 (R-3 b): the damage types the choice offers (v1 U-4 draws the TCE ten); absent keeps dnd5e's canonical list.
97
+ choice: z.object({ kind: z.literal("damage-type"), options: z.array(z.string().min(1)).min(1).optional() }).strict().optional(),
98
+ item: infuseItemSchema.optional(),
99
+ scales_at: z.array(z.object({ level: z.number().int().min(1).max(20), item: infuseItemSchema }).strict()).optional()
100
+ }).strict().refine((b) => {
101
+ var _a;
102
+ return ((_a = b.scales_at) != null ? _a : []).every((r, i, a) => i === 0 || a[i - 1].level < r.level);
103
+ }, { message: "scales_at levels ascend" });
49
104
  var optionalFeatureEntitySchema = z.object({
50
105
  slug: z.string().min(1),
51
106
  name: z.string().min(1),
@@ -82,9 +137,22 @@ var optionalFeatureEntitySchema = z.object({
82
137
  // 1
83
138
  optionalfeature_progression: z.array(progressionSchema).optional(),
84
139
  // 1
85
- image: imageField
140
+ image: imageField,
141
+ repeatable: z.literal(true).optional(),
142
+ item_choice: z.enum(["learn", "create"]).optional(),
143
+ options: z.array(optionRowSchema).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()
147
+ }).superRefine((d, ctx) => {
148
+ var _a;
149
+ if (d.repeatable && (!d.options || !d.item_choice)) ctx.addIssue({ code: "custom", message: "a repeatable note needs options and item_choice" });
150
+ const keys = ((_a = d.options) != null ? _a : []).map((o) => o.key);
151
+ if (new Set(keys).size !== keys.length) ctx.addIssue({ code: "custom", message: "option keys are unique" });
86
152
  });
87
153
 
88
154
  export {
155
+ optionRowSchema,
156
+ infuseBlockSchema,
89
157
  optionalFeatureEntitySchema
90
158
  };
@@ -0,0 +1,186 @@
1
+ import {
2
+ resolveBaseItem
3
+ } from "./chunk-CM5CWO4K.js";
4
+ import {
5
+ bareEntitySlug,
6
+ slugify,
7
+ wikilinkTailSlug
8
+ } from "./chunk-ZCTUYGNV.js";
9
+
10
+ // src/pc/pc.containment.ts
11
+ function nonEmptyRef(v) {
12
+ return typeof v === "string" && v.length > 0 ? v : null;
13
+ }
14
+ function isContainerItem(entity) {
15
+ if (!entity || typeof entity !== "object") return false;
16
+ const c = entity.container;
17
+ if (!c || typeof c !== "object") return false;
18
+ const container = c;
19
+ if (typeof container.holds_items === "boolean") return container.holds_items;
20
+ if (typeof container.capacity_weight === "number" && container.capacity_weight > 0) return true;
21
+ const cap = container.capacity;
22
+ if (!cap || typeof cap !== "object") return false;
23
+ const k = cap;
24
+ if (Array.isArray(k.weight) && k.weight.length > 0) return true;
25
+ if (Array.isArray(k.item) && k.item.length > 0) return true;
26
+ return k.weightless === true;
27
+ }
28
+ function idOwners(equipment) {
29
+ const ownerOfId = /* @__PURE__ */ new Map();
30
+ const duplicateIds = [];
31
+ equipment.forEach((entry, i) => {
32
+ const id = nonEmptyRef(entry.id);
33
+ if (id === null) return;
34
+ if (ownerOfId.has(id)) duplicateIds.push(i);
35
+ else ownerOfId.set(id, i);
36
+ });
37
+ return { ownerOfId, duplicateIds };
38
+ }
39
+ function entityOfEntry(entry, registry) {
40
+ var _a, _b;
41
+ if (!registry) return null;
42
+ return (_b = (_a = resolveBaseItem(entry.item, registry)) == null ? void 0 : _a.data) != null ? _b : null;
43
+ }
44
+ function resolveContainment(equipment, registry) {
45
+ const n = equipment.length;
46
+ const { ownerOfId, duplicateIds } = idOwners(equipment);
47
+ const entities = equipment.map((entry) => entityOfEntry(entry, registry));
48
+ const isContainer = entities.map((entity) => isContainerItem(entity));
49
+ const parentOf = new Array(n).fill(null);
50
+ const broken = [];
51
+ const closesCycle = (child, owner) => {
52
+ for (let cur = owner; cur !== null; cur = parentOf[cur]) if (cur === child) return true;
53
+ return false;
54
+ };
55
+ for (let i = 0; i < n; i++) {
56
+ const target = nonEmptyRef(equipment[i].in);
57
+ if (target === null) continue;
58
+ const owner = ownerOfId.get(target);
59
+ let reason = null;
60
+ if (equipment[i].equipped === true) reason = "equipped";
61
+ else if (owner === void 0) reason = "missing";
62
+ else if (owner === i) reason = "self";
63
+ else if (entities[owner] === null) reason = "unresolved";
64
+ else if (!isContainer[owner]) reason = "not-container";
65
+ else if (closesCycle(i, owner)) reason = "cycle";
66
+ if (reason !== null) {
67
+ broken.push({ index: i, reason });
68
+ continue;
69
+ }
70
+ parentOf[i] = owner;
71
+ }
72
+ const childrenOf = Array.from({ length: n }, () => []);
73
+ parentOf.forEach((p, i) => {
74
+ if (p !== null) childrenOf[p].push(i);
75
+ });
76
+ return { parentOf, childrenOf, isContainer, ownerOfId, duplicateIds, broken };
77
+ }
78
+ function mintBase(entry, registry) {
79
+ var _a, _b, _c;
80
+ const found = registry ? resolveBaseItem(entry.item, registry) : null;
81
+ const byName = found ? slugify((_a = found.name) != null ? _a : "") : "";
82
+ if (byName) return byName;
83
+ const inner = (_c = (_b = /^\[\[(.+?)\]\]$/.exec(entry.item.trim())) == null ? void 0 : _b[1]) != null ? _c : entry.item.trim();
84
+ const ref = inner.split("|")[0].trim();
85
+ const byRef = ref.includes("/") ? wikilinkTailSlug(ref) : slugify(bareEntitySlug(ref));
86
+ return byRef || "item";
87
+ }
88
+ function ensureEntryId(equipment, index, registry) {
89
+ const { ownerOfId } = idOwners(equipment);
90
+ const own = nonEmptyRef(equipment[index].id);
91
+ if (own !== null && ownerOfId.get(own) === index) return own;
92
+ const taken = /* @__PURE__ */ new Set();
93
+ for (const entry of equipment) {
94
+ const a = nonEmptyRef(entry.id);
95
+ if (a !== null) taken.add(a);
96
+ const b = nonEmptyRef(entry.in);
97
+ if (b !== null) taken.add(b);
98
+ }
99
+ const base = mintBase(equipment[index], registry);
100
+ let id = base;
101
+ for (let k = 2; taken.has(id); k++) id = `${base}-${k}`;
102
+ equipment[index].id = id;
103
+ return id;
104
+ }
105
+ function canMoveInto(equipment, index, target, registry) {
106
+ var _a;
107
+ const inRange = (i) => Number.isInteger(i) && i >= 0 && i < equipment.length;
108
+ if (!inRange(index) || target !== null && !inRange(target)) return { ok: false, reason: "out-of-range" };
109
+ if (target === null) return { ok: true };
110
+ if (equipment[index].equipped === true) return { ok: false, reason: "equipped" };
111
+ if (target === index) return { ok: false, reason: "self" };
112
+ const view = resolveContainment(equipment, registry);
113
+ const unresolvedLink = new Set(view.broken.filter((b) => b.reason === "unresolved").map((b) => b.index));
114
+ const seen = /* @__PURE__ */ new Set();
115
+ for (let cur = target; cur !== null && !seen.has(cur); ) {
116
+ if (cur === index) return { ok: false, reason: "descendant" };
117
+ seen.add(cur);
118
+ const up = view.parentOf[cur];
119
+ if (up !== null) {
120
+ cur = up;
121
+ continue;
122
+ }
123
+ if (unresolvedLink.has(cur)) {
124
+ const raw = nonEmptyRef(equipment[cur].in);
125
+ cur = raw === null ? null : (_a = view.ownerOfId.get(raw)) != null ? _a : null;
126
+ continue;
127
+ }
128
+ cur = null;
129
+ }
130
+ const entity = entityOfEntry(equipment[target], registry);
131
+ if (entity === null) return { ok: false, reason: "unresolved" };
132
+ if (!isContainerItem(entity)) return { ok: false, reason: "not-container" };
133
+ return { ok: true };
134
+ }
135
+ function moveInto(equipment, index, target, registry) {
136
+ const check = canMoveInto(equipment, index, target, registry);
137
+ if (!check.ok) return check;
138
+ const entry = equipment[index];
139
+ if (target === null) {
140
+ const had = entry.in !== void 0;
141
+ delete entry.in;
142
+ return { ok: true, changed: had };
143
+ }
144
+ const wasHonoured = resolveContainment(equipment, registry).parentOf[index] === target;
145
+ const idBefore = equipment[target].id;
146
+ const id = ensureEntryId(equipment, target, registry);
147
+ entry.in = id;
148
+ return { ok: true, changed: idBefore !== id || !wasHonoured };
149
+ }
150
+ function repairContainment(equipment, registry) {
151
+ if (!registry) return 0;
152
+ let removed = 0;
153
+ for (const entry of equipment) {
154
+ if (entry.id === "") {
155
+ delete entry.id;
156
+ removed++;
157
+ }
158
+ if (entry.in === "") {
159
+ delete entry.in;
160
+ removed++;
161
+ }
162
+ }
163
+ const view = resolveContainment(equipment, registry);
164
+ for (const b of view.broken) {
165
+ if (b.reason === "unresolved") continue;
166
+ delete equipment[b.index].in;
167
+ removed++;
168
+ }
169
+ for (const i of view.duplicateIds) {
170
+ delete equipment[i].id;
171
+ removed++;
172
+ }
173
+ return removed;
174
+ }
175
+
176
+ export {
177
+ nonEmptyRef,
178
+ isContainerItem,
179
+ idOwners,
180
+ entityOfEntry,
181
+ resolveContainment,
182
+ ensureEntryId,
183
+ canMoveInto,
184
+ moveInto,
185
+ repairContainment
186
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  itemEntitySchema
3
- } from "./chunk-GFZ7PITJ.js";
3
+ } from "./chunk-S3YUXDSC.js";
4
4
 
5
5
  // src/item/item.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -25,6 +25,8 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
25
25
  "sentient",
26
26
  "focus",
27
27
  "tier",
28
+ "tattoo",
29
+ // declared I2a; the converter emits it (27 carriers)
28
30
  "damage",
29
31
  "weapon_category",
30
32
  "armor_category",
@@ -46,6 +48,7 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
46
48
  // copied into `raw` as well, so the two lists must move together.
47
49
  "modify_speed",
48
50
  "rendering_hint",
51
+ "icon",
49
52
  "has_fluff",
50
53
  "has_fluff_images",
51
54
  "image",
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  featureSchema
3
- } from "./chunk-PI4LLJG4.js";
3
+ } from "./chunk-YD2VAHA6.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-2M34FLAY.js";
6
+ } from "./chunk-5MXFW4O3.js";
7
7
  import {
8
8
  additionalSpellsEntrySchema,
9
9
  imageField
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseArmor
3
- } from "./chunk-5N2GW72F.js";
3
+ } from "./chunk-UZXG6QSY.js";
4
4
 
5
5
  // src/armor/armor.codec.ts
6
6
  import yaml from "js-yaml";
@@ -11,8 +11,12 @@ var selectionPoolSchema = z.object({
11
11
  available_to: z.literal("self")
12
12
  })
13
13
  }),
14
- count: z.object({ column: z.string().min(1) }),
15
- replaceable: z.boolean().optional()
14
+ // ART-a (spec §4.5): `count.label`, `active` and `head_resource` carry NO zod default: the defaults are applied at
15
+ // read by `poolCountLabel` / `poolActiveWords`, so a parse never adds a key (the importer's round trip).
16
+ count: z.object({ column: z.string().min(1), label: z.string().min(1).optional() }),
17
+ replaceable: z.boolean().optional(),
18
+ active: z.object({ column: z.string().min(1), label: z.string().min(1), verb: z.string().min(1), counter_label: z.string().min(1).optional(), create_label: z.string().min(1).optional(), create_verb: z.string().min(1).optional() }).optional(),
19
+ head_resource: z.string().min(1).optional()
16
20
  });
17
21
  var poolGrantSchema = z.object({
18
22
  pool: z.string().min(1),
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseOptionalFeature
3
- } from "./chunk-PJMG5XV3.js";
3
+ } from "./chunk-PQZ4LM4Z.js";
4
4
 
5
5
  // src/optional-feature/optional-feature.codec.ts
6
6
  import yaml from "js-yaml";
@@ -0,0 +1,101 @@
1
+ import {
2
+ ALL_TOOLS,
3
+ ARTISANS_TOOLS,
4
+ GAMING_SETS,
5
+ MUSICAL_INSTRUMENTS
6
+ } from "./chunk-KU3YLF6D.js";
7
+ import {
8
+ toProfSlug
9
+ } from "./chunk-RLGQ5PZZ.js";
10
+
11
+ // src/pc/pc.tool-picks.ts
12
+ var TOOL_CATEGORIES = {
13
+ "musical-instrument": { pool: MUSICAL_INSTRUMENTS, itemType: "instrument", label: "a musical instrument" },
14
+ "artisan's-tools": { pool: ARTISANS_TOOLS, itemType: "artisan's tools", label: "artisan's tools" },
15
+ "gaming-set": { pool: GAMING_SETS, itemType: "gaming set", label: "a gaming set" }
16
+ };
17
+ function categoryWords(token) {
18
+ return token.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/['’ʼ]/g, "").split(/[\s_-]+/).filter(Boolean).map((w) => w.length > 3 && w.endsWith("s") && !w.endsWith("ss") ? w.slice(0, -1) : w);
19
+ }
20
+ var CATEGORY_BY_WORDS = /* @__PURE__ */ new Map([
21
+ ["musical instrument", "musical-instrument"],
22
+ ["artisan tool", "artisan's-tools"],
23
+ ["gaming set", "gaming-set"]
24
+ ]);
25
+ function toolCategoriesOf(token) {
26
+ const parts = [[]];
27
+ for (const w of categoryWords(token)) {
28
+ if (w === "or") parts.push([]);
29
+ else parts[parts.length - 1].push(w);
30
+ }
31
+ const out = [];
32
+ for (const p of parts) {
33
+ const c = CATEGORY_BY_WORDS.get(p.filter((w) => w !== "any" && w !== "one").join(" "));
34
+ if (!c) return void 0;
35
+ if (!out.includes(c)) out.push(c);
36
+ }
37
+ return out;
38
+ }
39
+ function expandToolPool(from) {
40
+ const out = [];
41
+ const seen = /* @__PURE__ */ new Set();
42
+ const add = (v) => {
43
+ const k = toProfSlug(v);
44
+ if (seen.has(k)) return;
45
+ seen.add(k);
46
+ out.push(v);
47
+ };
48
+ for (const v of from) {
49
+ const cats = toolCategoriesOf(v);
50
+ if (cats) for (const c of cats) for (const t of TOOL_CATEGORIES[c].pool) add(t);
51
+ else add(v);
52
+ }
53
+ return out;
54
+ }
55
+ var EMPTY = { decisions: [], grants: [] };
56
+ var poolOf = (from) => Array.isArray(from) ? from.filter((v) => typeof v === "string" && v.length > 0) : typeof from === "string" && from.length > 0 ? [from] : [];
57
+ var countOf = (count) => {
58
+ const n = Number(count != null ? count : 1);
59
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;
60
+ };
61
+ var KNOWN_TOOLS = new Set(ALL_TOOLS.map(toProfSlug));
62
+ var namesToolsOutright = (from) => from.every((v) => !toolCategoriesOf(v) && KNOWN_TOOLS.has(toProfSlug(v)));
63
+ var authorsToolPick = (choices) => Array.isArray(choices) && choices.some((c) => !!c && typeof c === "object" && c.kind === "select-proficiency" && c.domain === "tool");
64
+ function toolPick(count, from, id, allowDecision) {
65
+ const authored = poolOf(from);
66
+ const pool = expandToolPool(authored);
67
+ const n = countOf(count);
68
+ if (pool.length === 0) return EMPTY;
69
+ if (n >= pool.length && namesToolsOutright(authored)) return { decisions: [], grants: pool };
70
+ if (!allowDecision) return EMPTY;
71
+ return { decisions: [{ kind: "select-proficiency", id, count: n, domain: "tool", from: pool }], grants: [] };
72
+ }
73
+ function classToolPicks(entity) {
74
+ var _a;
75
+ const tools = (_a = entity == null ? void 0 : entity.proficiencies) == null ? void 0 : _a.tools;
76
+ const choice = tools == null ? void 0 : tools.choice;
77
+ if (!choice || typeof choice !== "object") return EMPTY;
78
+ return toolPick(choice.count, choice.from, "tool", !authorsToolPick(entity == null ? void 0 : entity.choices));
79
+ }
80
+ function backgroundToolPicks(bg) {
81
+ const entries = Array.isArray(bg == null ? void 0 : bg.tool_proficiencies) ? bg.tool_proficiencies : [];
82
+ const allowDecision = !authorsToolPick(bg == null ? void 0 : bg.choices);
83
+ const out = { decisions: [], grants: [] };
84
+ for (const e of entries) {
85
+ if (!e || typeof e !== "object" || e.kind !== "choice") continue;
86
+ const id = out.decisions.length === 0 ? "tool" : `tool-${out.decisions.length + 1}`;
87
+ const r = toolPick(e.count, e.from, id, allowDecision);
88
+ out.decisions.push(...r.decisions);
89
+ out.grants.push(...r.grants);
90
+ }
91
+ return out;
92
+ }
93
+
94
+ export {
95
+ TOOL_CATEGORIES,
96
+ categoryWords,
97
+ toolCategoriesOf,
98
+ expandToolPool,
99
+ classToolPicks,
100
+ backgroundToolPicks
101
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  evaluateConditions
3
- } from "./chunk-DNAJRMNN.js";
3
+ } from "./chunk-A54HHCWW.js";
4
4
 
5
5
  // src/item/item.bonuses.ts
6
6
  function isConditionalBonus(x) {
@@ -32,6 +32,8 @@ var armorEntitySchema = z.object({
32
32
  // string today, so `.min(1)` is forbidden here.
33
33
  rendering_hint: z.string().optional(),
34
34
  // 27
35
+ // The icon key (I3): a key of the renderer's icon set or a Lucide name; itemIconKey reads it first.
36
+ icon: z.string().optional(),
35
37
  has_fluff: z.boolean().optional(),
36
38
  // 0 carriers — symmetry with weapon
37
39
  has_fluff_images: z.boolean().optional(),