@archivist-gg/dnd5e 0.3.0 → 0.3.4

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 (260) hide show
  1. package/LICENSES/SRD.md +25 -6
  2. package/dist/armor/armor.codec.js +4 -3
  3. package/dist/armor/armor.parser.js +3 -2
  4. package/dist/armor/armor.schema.d.ts +4 -0
  5. package/dist/armor/armor.schema.js +2 -1
  6. package/dist/armor/armor.types.d.ts +8 -0
  7. package/dist/background/background.codec.js +7 -6
  8. package/dist/background/background.parser.js +6 -5
  9. package/dist/background/background.schema.d.ts +34 -0
  10. package/dist/background/background.schema.js +9 -6
  11. package/dist/background/background.types.d.ts +22 -0
  12. package/dist/{chunk-4TIJKN6O.js → chunk-23LZLBJK.js} +1 -1
  13. package/dist/{chunk-AFKTJXRA.js → chunk-2DFENQIJ.js} +18 -0
  14. package/dist/chunk-2FRUSVZS.js +489 -0
  15. package/dist/chunk-4FWNK4FD.js +159 -0
  16. package/dist/chunk-4U55VNC7.js +22 -0
  17. package/dist/{chunk-4JHOHWBI.js → chunk-4XTNPNQB.js} +1 -1
  18. package/dist/{chunk-HCP3GMHS.js → chunk-5N2GW72F.js} +15 -2
  19. package/dist/{chunk-42Y5IJKT.js → chunk-5U5FFFA3.js} +5 -1
  20. package/dist/{chunk-4F6AIZ36.js → chunk-5XMQPTHD.js} +1 -1
  21. package/dist/{chunk-GJOI2GHP.js → chunk-6GNXYAYP.js} +1 -1
  22. package/dist/chunk-6PKV5V2H.js +178 -0
  23. package/dist/{chunk-66XXW2Z2.js → chunk-6PXTNCBP.js} +2 -2
  24. package/dist/chunk-6X2NV665.js +33 -0
  25. package/dist/{chunk-LV4SNWZQ.js → chunk-6X4KUDMU.js} +8 -1
  26. package/dist/{chunk-ZMR77DDL.js → chunk-AW3EHF3L.js} +1 -1
  27. package/dist/chunk-CJYCVNW2.js +7 -0
  28. package/dist/chunk-COEOOQX2.js +133 -0
  29. package/dist/{chunk-XTIQWSFM.js → chunk-D5U7GMPX.js} +1 -1
  30. package/dist/{chunk-RAWJTA45.js → chunk-DEEHHLAP.js} +1 -1
  31. package/dist/chunk-DPUJDOKV.js +365 -0
  32. package/dist/{chunk-73HOFQQ5.js → chunk-ESS46GR4.js} +1 -1
  33. package/dist/chunk-EVMUJYUD.js +195 -0
  34. package/dist/{chunk-2BWAID3Y.js → chunk-FDBWYMZK.js} +29 -3
  35. package/dist/{chunk-K6Q5OUGK.js → chunk-GFZ7PITJ.js} +86 -10
  36. package/dist/chunk-H7KBLR4X.js +81 -0
  37. package/dist/{chunk-L7E2UT5A.js → chunk-HB7XZXDK.js} +5 -1
  38. package/dist/{chunk-VTYUBINW.js → chunk-HJ5EEZ47.js} +1 -1
  39. package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
  40. package/dist/{chunk-A4WENOUQ.js → chunk-I2NDWFDD.js} +52 -8
  41. package/dist/{chunk-RSDRMH54.js → chunk-IMZFOOVL.js} +1 -1
  42. package/dist/{chunk-3DZ74AKK.js → chunk-IN4M4X7P.js} +8 -2
  43. package/dist/chunk-IUAAXXNO.js +58 -0
  44. package/dist/{chunk-XJNECUPT.js → chunk-JC5UNIVM.js} +19 -0
  45. package/dist/chunk-JJ46T5XX.js +297 -0
  46. package/dist/{chunk-TUIFSVLI.js → chunk-JRDPUDT5.js} +30 -3
  47. package/dist/{chunk-24GFQU7T.js → chunk-JSZ4H6AQ.js} +1 -1
  48. package/dist/chunk-KU3YLF6D.js +106 -0
  49. package/dist/{chunk-5INR4NSL.js → chunk-LDDCNPHD.js} +1 -1
  50. package/dist/{chunk-DZXSWSBP.js → chunk-LMBAEIP6.js} +1 -1
  51. package/dist/chunk-LVQLWRCU.js +15 -0
  52. package/dist/chunk-MI7P3NX3.js +161 -0
  53. package/dist/chunk-O4SRD3J5.js +165 -0
  54. package/dist/{chunk-EMOORSQ7.js → chunk-OSQFPGLM.js} +45 -6
  55. package/dist/chunk-OZP6JFGT.js +162 -0
  56. package/dist/{chunk-NAEPSZAM.js → chunk-PDJSUH24.js} +24 -2
  57. package/dist/chunk-PP2UHLBA.js +50 -0
  58. package/dist/chunk-PVCEA4HZ.js +6 -0
  59. package/dist/{chunk-XLSVNUTK.js → chunk-R3VAKWKW.js} +1 -1
  60. package/dist/chunk-RIXI5D3R.js +89 -0
  61. package/dist/chunk-RLGQ5PZZ.js +16 -0
  62. package/dist/chunk-S7REMWW4.js +21 -0
  63. package/dist/{chunk-7VJ6BOA5.js → chunk-SMLXZUBH.js} +9 -1
  64. package/dist/{chunk-NXSWH3AE.js → chunk-T26ZVCL5.js} +1 -1
  65. package/dist/chunk-TOM66P2P.js +38 -0
  66. package/dist/chunk-UA5KS2PL.js +26 -0
  67. package/dist/{chunk-N6N74K6X.js → chunk-UBBKSYBY.js} +1 -1
  68. package/dist/{chunk-353WXS6S.js → chunk-UIRMA5KM.js} +1 -1
  69. package/dist/{chunk-GE4TLPWM.js → chunk-US5IKYCW.js} +1 -1
  70. package/dist/chunk-UXPGW4TI.js +101 -0
  71. package/dist/chunk-VQAXTIBW.js +100 -0
  72. package/dist/{chunk-WP2CX2JO.js → chunk-WELIKLZF.js} +22 -1
  73. package/dist/chunk-WPWOHGPI.js +172 -0
  74. package/dist/{chunk-GZ46OVGZ.js → chunk-WSVMLNTO.js} +1 -1
  75. package/dist/{chunk-4LZJXJJS.js → chunk-WXWPK3LI.js} +6 -2
  76. package/dist/{chunk-ZZGEVJKX.js → chunk-XBQMCEI2.js} +1 -1
  77. package/dist/{chunk-PHQRGDRQ.js → chunk-XGOFP7AQ.js} +5 -1
  78. package/dist/chunk-XJ2F2WO6.js +8 -0
  79. package/dist/chunk-XQCSGNXE.js +1580 -0
  80. package/dist/chunk-YBLDE5CG.js +42 -0
  81. package/dist/{chunk-JEYQU3JT.js → chunk-YHYTW5QA.js} +2 -18
  82. package/dist/{chunk-TLYDBT5F.js → chunk-YJJTISBN.js} +64 -7
  83. package/dist/{chunk-PZOWBI3B.js → chunk-YLL2QXY2.js} +7 -2
  84. package/dist/{chunk-MDEJHMKK.js → chunk-ZONJ66NE.js} +26 -5
  85. package/dist/class/class.codec.js +12 -9
  86. package/dist/class/class.parser.js +11 -8
  87. package/dist/class/class.schema.d.ts +55 -3
  88. package/dist/class/class.schema.js +9 -7
  89. package/dist/class/class.types.d.ts +48 -3
  90. package/dist/condition/condition.codec.d.ts +9 -0
  91. package/dist/condition/condition.entity-type.d.ts +2 -0
  92. package/dist/condition/condition.parser.d.ts +13 -0
  93. package/dist/condition/condition.parser.js +7 -0
  94. package/dist/condition/condition.schema.d.ts +29 -0
  95. package/dist/condition/condition.types.d.ts +15 -0
  96. package/dist/condition/condition.types.js +0 -0
  97. package/dist/dnd/constants.d.ts +22 -0
  98. package/dist/dnd/constants.js +9 -3
  99. package/dist/dnd/formula-tags.js +2 -2
  100. package/dist/dnd/math.d.ts +26 -0
  101. package/dist/dnd/math.js +12 -2
  102. package/dist/dnd/prose-tags.js +1 -1
  103. package/dist/dnd/resource-formula.d.ts +36 -10
  104. package/dist/dnd/resource-formula.js +10 -155
  105. package/dist/dnd/srd-tag-converter.js +3 -3
  106. package/dist/dnd/tag-grammar.js +1 -1
  107. package/dist/dnd/warn-once.d.ts +2 -0
  108. package/dist/dnd/warn-once.js +8 -0
  109. package/dist/entities/base-item-resolver.js +2 -2
  110. package/dist/entities/slug.d.ts +13 -0
  111. package/dist/entities/slug.js +5 -3
  112. package/dist/entities/wikilink-display.d.ts +3 -0
  113. package/dist/entities/wikilink-display.js +10 -0
  114. package/dist/feat/feat.category-codes.d.ts +32 -0
  115. package/dist/feat/feat.category-codes.js +14 -0
  116. package/dist/feat/feat.codec.js +7 -5
  117. package/dist/feat/feat.parser.js +6 -4
  118. package/dist/feat/feat.schema.d.ts +230 -0
  119. package/dist/feat/feat.schema.js +5 -3
  120. package/dist/feat/feat.types.d.ts +37 -1
  121. package/dist/index.js +2120 -6923
  122. package/dist/item/item.ai-schema.d.ts +1 -1
  123. package/dist/item/item.codec.js +4 -3
  124. package/dist/item/item.enrichment.js +4 -4
  125. package/dist/item/item.parser.js +3 -2
  126. package/dist/item/item.schema.d.ts +69 -3
  127. package/dist/item/item.schema.js +2 -1
  128. package/dist/item/item.types.d.ts +69 -3
  129. package/dist/monster/monster.codec.d.ts +7 -6
  130. package/dist/monster/monster.enrichment.js +4 -4
  131. package/dist/monster/monster.format.d.ts +135 -0
  132. package/dist/monster/monster.format.js +78 -0
  133. package/dist/monster/monster.parser.d.ts +6 -1
  134. package/dist/monster/monster.parser.js +10 -1
  135. package/dist/monster/monster.schema.d.ts +369 -0
  136. package/dist/monster/monster.schema.js +34 -0
  137. package/dist/monster/monster.types.d.ts +200 -14
  138. package/dist/optional-feature/optional-feature.codec.js +6 -5
  139. package/dist/optional-feature/optional-feature.parser.js +5 -4
  140. package/dist/optional-feature/optional-feature.schema.d.ts +176 -0
  141. package/dist/optional-feature/optional-feature.schema.js +4 -3
  142. package/dist/pack.d.ts +1 -1
  143. package/dist/pc/conditions.constants.d.ts +0 -1
  144. package/dist/pc/conditions.constants.js +1 -3
  145. package/dist/pc/decision-recognizer.js +1 -1
  146. package/dist/pc/pc.additional-spells.d.ts +47 -0
  147. package/dist/pc/pc.additional-spells.js +23 -0
  148. package/dist/pc/pc.asi-flatten.d.ts +18 -0
  149. package/dist/pc/pc.decision-engine.d.ts +106 -5
  150. package/dist/pc/pc.decision-engine.js +28 -10
  151. package/dist/pc/pc.defense-normalize.d.ts +11 -0
  152. package/dist/pc/pc.defense-normalize.js +6 -0
  153. package/dist/pc/pc.equipment.d.ts +30 -1
  154. package/dist/pc/pc.equipment.js +12 -7
  155. package/dist/pc/pc.feature-effects.d.ts +225 -11
  156. package/dist/pc/pc.feature-effects.js +21 -5
  157. package/dist/pc/pc.parser.js +2 -2
  158. package/dist/pc/pc.pools.d.ts +11 -3
  159. package/dist/pc/pc.pools.js +9 -13
  160. package/dist/pc/pc.proficiencies.d.ts +60 -4
  161. package/dist/pc/pc.proficiencies.js +89 -37
  162. package/dist/pc/pc.proficiency-grants.d.ts +58 -0
  163. package/dist/pc/pc.proficiency-normalize.d.ts +29 -0
  164. package/dist/pc/pc.proficiency-normalize.js +12 -0
  165. package/dist/pc/pc.proficiency-query.d.ts +13 -0
  166. package/dist/pc/pc.proficiency-query.js +5 -1
  167. package/dist/pc/pc.recalc.d.ts +69 -14
  168. package/dist/pc/pc.recalc.js +291 -142
  169. package/dist/pc/pc.resolver.d.ts +34 -8
  170. package/dist/pc/pc.resolver.js +27 -12
  171. package/dist/pc/pc.resource-seed.d.ts +3 -2
  172. package/dist/pc/pc.resource-seed.js +19 -17
  173. package/dist/pc/pc.resources.d.ts +87 -0
  174. package/dist/pc/pc.resources.js +21 -0
  175. package/dist/pc/pc.rest.d.ts +10 -1
  176. package/dist/pc/pc.rest.js +66 -12
  177. package/dist/pc/pc.schema.d.ts +147 -7
  178. package/dist/pc/pc.schema.js +4 -2
  179. package/dist/pc/pc.slotting.js +3 -3
  180. package/dist/pc/pc.spellcasting.d.ts +80 -7
  181. package/dist/pc/pc.spellcasting.js +18 -6
  182. package/dist/pc/pc.table-column.d.ts +23 -0
  183. package/dist/pc/pc.table-column.js +13 -3
  184. package/dist/pc/pc.types.d.ts +253 -16
  185. package/dist/pc/pool-layout.d.ts +47 -0
  186. package/dist/pc/pool-layout.js +16 -0
  187. package/dist/pc/roll-scope.d.ts +2 -0
  188. package/dist/pc/roll-scope.js +8 -0
  189. package/dist/pc/roll-tag-labels.d.ts +40 -0
  190. package/dist/pc/roll-tag-labels.js +17 -0
  191. package/dist/pc/spell-source.d.ts +21 -0
  192. package/dist/pc/spell-source.js +13 -0
  193. package/dist/race/race.codec.js +9 -7
  194. package/dist/race/race.parser.js +8 -6
  195. package/dist/race/race.schema.d.ts +51 -1
  196. package/dist/race/race.schema.js +8 -5
  197. package/dist/race/race.structural.d.ts +7 -0
  198. package/dist/race/race.structural.js +6 -0
  199. package/dist/race/race.types.d.ts +20 -0
  200. package/dist/schemas/attack-schema.d.ts +5 -0
  201. package/dist/schemas/caster-type-schema.d.ts +14 -0
  202. package/dist/schemas/caster-type-schema.js +6 -0
  203. package/dist/schemas/choice-schema.js +2 -2
  204. package/dist/schemas/entity-extras-schema.d.ts +53 -0
  205. package/dist/schemas/entity-extras-schema.js +10 -0
  206. package/dist/schemas/equipment-grant-schema.d.ts +2 -0
  207. package/dist/schemas/equipment-grant-schema.js +1 -1
  208. package/dist/schemas/feature-alias.d.ts +34 -0
  209. package/dist/schemas/feature-effect-schema.d.ts +124 -0
  210. package/dist/schemas/feature-effect-schema.js +1 -1
  211. package/dist/schemas/feature-schema.d.ts +8 -0
  212. package/dist/schemas/feature-schema.js +6 -4
  213. package/dist/schemas/resource-schema.d.ts +43 -0
  214. package/dist/schemas/resource-schema.js +1 -1
  215. package/dist/schemas/selection-pool-schema.d.ts +2 -0
  216. package/dist/schemas/selection-pool-schema.js +1 -1
  217. package/dist/spell/spell.access.js +23 -3
  218. package/dist/spell/spell.codec.js +4 -3
  219. package/dist/spell/spell.enrichment.js +4 -4
  220. package/dist/spell/spell.parser.d.ts +30 -0
  221. package/dist/spell/spell.parser.js +9 -2
  222. package/dist/spell/spell.scaling.d.ts +27 -6
  223. package/dist/spell/spell.scaling.js +25 -6
  224. package/dist/spell/spell.schema.d.ts +44 -2
  225. package/dist/spell/spell.schema.js +2 -1
  226. package/dist/spell/spell.types.d.ts +14 -0
  227. package/dist/subclass/subclass.codec.d.ts +1 -1
  228. package/dist/subclass/subclass.codec.js +11 -8
  229. package/dist/subclass/subclass.parser.js +10 -7
  230. package/dist/subclass/subclass.schema.d.ts +54 -7
  231. package/dist/subclass/subclass.schema.js +8 -6
  232. package/dist/subclass/subclass.types.d.ts +20 -2
  233. package/dist/types/choice.d.ts +56 -0
  234. package/dist/types/choice.js +21 -3
  235. package/dist/types/equipment-grant.d.ts +8 -1
  236. package/dist/types/feature-effect.d.ts +78 -23
  237. package/dist/types/feature.d.ts +70 -1
  238. package/dist/types/optional-feature.types.d.ts +28 -0
  239. package/dist/types/resource.d.ts +21 -1
  240. package/dist/types/selection-pool.d.ts +17 -2
  241. package/dist/weapon/weapon.classify.js +2 -2
  242. package/dist/weapon/weapon.codec.js +4 -3
  243. package/dist/weapon/weapon.parser.js +3 -2
  244. package/dist/weapon/weapon.schema.d.ts +4 -0
  245. package/dist/weapon/weapon.schema.js +2 -1
  246. package/dist/weapon/weapon.types.d.ts +8 -0
  247. package/package.json +2 -2
  248. package/dist/chunk-5MZSB6OM.js +0 -1089
  249. package/dist/chunk-5XUXXAQJ.js +0 -30
  250. package/dist/chunk-7P65Q426.js +0 -158
  251. package/dist/chunk-AIRFC5NQ.js +0 -143
  252. package/dist/chunk-BH6AMZVY.js +0 -25
  253. package/dist/chunk-CZPMTQOY.js +0 -68
  254. package/dist/chunk-M2EMFD6S.js +0 -269
  255. package/dist/chunk-N2JE7BWL.js +0 -81
  256. package/dist/chunk-NXR3BD2V.js +0 -90
  257. package/dist/chunk-Q5K6LWBZ.js +0 -45
  258. package/dist/chunk-TZK36BU7.js +0 -38
  259. package/dist/chunk-W7HWMMBF.js +0 -46
  260. package/dist/chunk-Y4I74YEG.js +0 -33
@@ -0,0 +1,165 @@
1
+ import {
2
+ derivePoolLayout
3
+ } from "./chunk-PP2UHLBA.js";
4
+ import {
5
+ readTableColumn
6
+ } from "./chunk-IUAAXXNO.js";
7
+ import {
8
+ warnOnce
9
+ } from "./chunk-LVQLWRCU.js";
10
+ import {
11
+ bareEntitySlug,
12
+ wikilinkTailSlug
13
+ } from "./chunk-6X4KUDMU.js";
14
+
15
+ // src/pc/pc.pools.ts
16
+ function levelPrereqMax(d) {
17
+ var _a;
18
+ return ((_a = d.prerequisites) != null ? _a : []).filter((p) => p.kind === "level").reduce((m, p) => Math.max(m, p.min), 0);
19
+ }
20
+ function resolvePool(rc, classIndex, pool, registry, index) {
21
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
22
+ const entity = rc.entity;
23
+ const level = rc.level;
24
+ const ownerBare = bareEntitySlug(entity.slug);
25
+ const ownerPrefix = entity.slug.split("_")[0];
26
+ const col = pool.count.column;
27
+ const count = (_a = readTableColumn(entity.table, level, [col])) != null ? _a : 0;
28
+ let anchorLevel = 1;
29
+ let columnSeen = false;
30
+ for (let l = 1; l <= 20; l++) {
31
+ const c = readTableColumn(entity.table, l, [col]);
32
+ if (c != null) columnSeen = true;
33
+ if (c != null && c >= 1) {
34
+ anchorLevel = l;
35
+ break;
36
+ }
37
+ }
38
+ if (!columnSeen) {
39
+ warnOnce(
40
+ `pool-column:${pool.id}`,
41
+ `Archivist: pool "${pool.id}" declares count column "${col}", which "${entity.slug}"'s class table carries at no level; the pool resolves count 0 at anchor level 1`
42
+ );
43
+ }
44
+ const ft = pool.source.where.feature_type;
45
+ const all = registry.search("", "optional-feature", Number.POSITIVE_INFINITY).slice().sort((a, b) => a.name.localeCompare(b.name) || a.slug.localeCompare(b.slug));
46
+ const byBare = /* @__PURE__ */ new Map();
47
+ const candidates = /* @__PURE__ */ new Map();
48
+ for (const e of all) {
49
+ const d = e.data;
50
+ const entry = { slug: e.slug, entity: d, ...e.compendium ? { compendium: e.compendium } : {} };
51
+ const bare = bareEntitySlug(e.slug);
52
+ byBare.set(bare, entry);
53
+ if (d.feature_type !== ft) continue;
54
+ if (!((_b = d.available_to) != null ? _b : []).some((l) => wikilinkTailSlug(l) === ownerBare)) continue;
55
+ if (levelPrereqMax(d) > level) continue;
56
+ const group = candidates.get(bare);
57
+ if (group) group.push(entry);
58
+ else candidates.set(bare, [entry]);
59
+ }
60
+ const available = [];
61
+ for (const [bare, group] of candidates) {
62
+ if (group.length === 1) {
63
+ available.push(group[0]);
64
+ continue;
65
+ }
66
+ const owned = group.find((e) => e.slug.split("_")[0] === ownerPrefix);
67
+ if (!owned) {
68
+ warnOnce(
69
+ `pool-collapse:${bare}`,
70
+ `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}"`
71
+ );
72
+ }
73
+ available.push(owned != null ? owned : group[0]);
74
+ }
75
+ const grantDecls = [
76
+ ...(_d = (_c = rc.entity) == null ? void 0 : _c.pool_grants) != null ? _d : [],
77
+ ...(_f = (_e = rc.subclass) == null ? void 0 : _e.pool_grants) != null ? _f : []
78
+ ];
79
+ const grantsRaw = grantDecls.filter((g) => g.pool === pool.id).flatMap((g) => g.grants).filter((g) => g.at_level <= level).map((g) => byBare.get(wikilinkTailSlug(g.feature))).filter((e) => e != null);
80
+ const grantedBare = /* @__PURE__ */ new Set();
81
+ const grants = grantsRaw.filter((e) => {
82
+ const key = bareEntitySlug(e.slug);
83
+ if (grantedBare.has(key)) return false;
84
+ grantedBare.add(key);
85
+ return true;
86
+ });
87
+ const bySlugAvailable = new Map(available.map((e) => [e.slug, e]));
88
+ const byBareAvailable = new Map(available.map((e) => [bareEntitySlug(e.slug), e]));
89
+ const resolveStored = (s) => {
90
+ const exact = bySlugAvailable.get(s);
91
+ if (exact) return exact;
92
+ const survivor = byBareAvailable.get(bareEntitySlug(s));
93
+ if (survivor) return survivor;
94
+ const full = registry.getByTypeAndSlug("optional-feature", s);
95
+ if (full) {
96
+ const d = full.data;
97
+ return { slug: full.slug, entity: d, ...full.compendium ? { compendium: full.compendium } : {} };
98
+ }
99
+ return byBare.get(bareEntitySlug(s));
100
+ };
101
+ const raw = (_g = rc.choices[anchorLevel]) == null ? void 0 : _g[pool.id];
102
+ const selectedSlugs = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
103
+ const selected = [];
104
+ const seenBare = /* @__PURE__ */ new Set();
105
+ for (const s of selectedSlugs) {
106
+ const entry = resolveStored(s);
107
+ if (!entry) continue;
108
+ const bare = bareEntitySlug(entry.slug);
109
+ if (grantedBare.has(bare) || seenBare.has(bare)) continue;
110
+ seenBare.add(bare);
111
+ selected.push(entry);
112
+ }
113
+ const members = [...available, ...grants];
114
+ const layout = derivePoolLayout(members);
115
+ const votes = /* @__PURE__ */ new Map();
116
+ for (const e of members) {
117
+ const id = (_h = e.entity.consumes) == null ? void 0 : _h.resource;
118
+ if (!id || !index.has(id)) continue;
119
+ votes.set(id, ((_i = votes.get(id)) != null ? _i : 0) + 1);
120
+ }
121
+ let resource;
122
+ let bestN = 0;
123
+ for (const [id, n] of votes) if (n > bestN) {
124
+ resource = id;
125
+ bestN = n;
126
+ }
127
+ return {
128
+ id: pool.id,
129
+ label: pool.label,
130
+ classIndex,
131
+ count,
132
+ anchorLevel,
133
+ selected,
134
+ available,
135
+ grants,
136
+ ...layout ? { layout } : {},
137
+ ...resource ? { resource } : {}
138
+ };
139
+ }
140
+ function resolveAllPools(resolved, registry, index) {
141
+ const out = [];
142
+ resolved.classes.forEach((rc, classIndex) => {
143
+ var _a, _b, _c;
144
+ if (!rc.entity) return;
145
+ const decls = [...(_a = rc.entity.selection_pools) != null ? _a : [], ...(_c = (_b = rc.subclass) == null ? void 0 : _b.selection_pools) != null ? _c : []];
146
+ const seen = /* @__PURE__ */ new Set();
147
+ for (const pool of decls) {
148
+ if (seen.has(pool.id)) continue;
149
+ seen.add(pool.id);
150
+ out.push(resolvePool(rc, classIndex, pool, registry, index));
151
+ }
152
+ });
153
+ return out;
154
+ }
155
+ function strandedPicks(pool) {
156
+ const inAvailable = new Set(pool.available.map((e) => e.slug));
157
+ return pool.selected.filter((e) => !inAvailable.has(e.slug));
158
+ }
159
+
160
+ export {
161
+ levelPrereqMax,
162
+ resolvePool,
163
+ resolveAllPools,
164
+ strandedPicks
165
+ };
@@ -1,22 +1,25 @@
1
1
  import {
2
2
  classifyWeaponRange
3
- } from "./chunk-353WXS6S.js";
3
+ } from "./chunk-UIRMA5KM.js";
4
4
  import {
5
5
  defaultSlotForType,
6
6
  effectiveArmor,
7
7
  isItemEntity,
8
8
  isWeaponEntity,
9
9
  resolveEntityForEntry
10
- } from "./chunk-N6N74K6X.js";
10
+ } from "./chunk-UBBKSYBY.js";
11
+ import {
12
+ normKey
13
+ } from "./chunk-RLGQ5PZZ.js";
11
14
  import {
12
15
  readNumericBonus
13
16
  } from "./chunk-LWFTHJIE.js";
14
17
  import {
15
18
  resolveBaseItem
16
- } from "./chunk-4LZJXJJS.js";
19
+ } from "./chunk-WXWPK3LI.js";
17
20
  import {
18
21
  bareEntitySlug
19
- } from "./chunk-LV4SNWZQ.js";
22
+ } from "./chunk-6X4KUDMU.js";
20
23
 
21
24
  // src/weapon/weapon-mastery.ts
22
25
  var MASTERY = {
@@ -361,7 +364,7 @@ function attackAbility(weapon, mods, weaponAbilities) {
361
364
  }
362
365
  function isWeaponSlugProficient(weapon, weaponSlug, profs) {
363
366
  const wp = profs.weapons;
364
- if (wp.specific.includes(weaponSlug)) return true;
367
+ if (wp.specific.some((e) => e === weaponSlug || normKey(e) === normKey(weapon.name))) return true;
365
368
  const cat = weapon.category;
366
369
  if (typeof cat === "string") {
367
370
  if (/^simple\b/.test(cat) && wp.categories.includes("simple")) return true;
@@ -402,7 +405,7 @@ function magicBonusesForWeaponEntry(entry, registry, ctx) {
402
405
  itemRiders = Array.isArray(entity.damage_riders) ? entity.damage_riders.filter((r) => {
403
406
  var _a2;
404
407
  return ((_a2 = r.applies_to) != null ? _a2 : "weapon") !== "spell";
405
- }).map((r) => ({ amount: r.amount, damage_type: r.damage_type, source: entity.name })) : [];
408
+ }).map((r) => ({ amount: r.amount, damage_type: r.damage_type, source: entity.name, ...r.condition ? { condition: r.condition } : {} })) : [];
406
409
  }
407
410
  return {
408
411
  atk: itemAttack + entryAttack,
@@ -479,6 +482,40 @@ function buildAttackRow(args) {
479
482
  }
480
483
  return row;
481
484
  }
485
+ function buildUnarmedRow(mods, proficiencyBonus, spec) {
486
+ var _a;
487
+ const candidates = ["str", ...(_a = spec == null ? void 0 : spec.abilities) != null ? _a : []];
488
+ let ability = "str";
489
+ for (const ab of candidates) if (mods[ab] >= mods[ability]) ability = ab;
490
+ const abilityMod = mods[ability];
491
+ const toHit = abilityMod + proficiencyBonus;
492
+ const flat = `${abilityMod >= 0 ? "+" : ""}${abilityMod}`.replace(/^\+0$/, "");
493
+ const damageDice = (spec == null ? void 0 : spec.dice) ? `${spec.dice}${flat}` : String(Math.max(0, 1 + abilityMod));
494
+ const label = `${ability.toUpperCase()} modifier`;
495
+ return {
496
+ id: "unarmed-strike",
497
+ name: "Unarmed Strike",
498
+ unarmed: true,
499
+ range: "5 ft",
500
+ toHit,
501
+ damageDice,
502
+ damageType: "bludgeoning",
503
+ properties: [],
504
+ proficient: true,
505
+ breakdown: {
506
+ toHit: [
507
+ { source: label, amount: abilityMod, kind: "ability" },
508
+ { source: "Proficiency bonus", amount: proficiencyBonus, kind: "ability" }
509
+ ],
510
+ damage: [
511
+ { source: "Base damage", amount: 0, kind: "ability" },
512
+ { source: label, amount: abilityMod, kind: "ability" }
513
+ ]
514
+ },
515
+ subLabel: "Unarmed",
516
+ actionCost: "action"
517
+ };
518
+ }
482
519
  function humanizeWeaponToken(s) {
483
520
  return s.replace(/[-_]/g, " ").replace(/(^|[\s(])\w/g, (c) => c.toUpperCase());
484
521
  }
@@ -591,5 +628,7 @@ function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, profi
591
628
  export {
592
629
  emptyAppliedBonuses,
593
630
  computeAppliedBonuses,
631
+ isWeaponSlugProficient,
632
+ buildUnarmedRow,
594
633
  computeSlotsAndAttacks
595
634
  };
@@ -0,0 +1,162 @@
1
+ import {
2
+ spellEntitySchema
3
+ } from "./chunk-VQAXTIBW.js";
4
+
5
+ // src/spell/spell.parser.ts
6
+ import { parseYaml, toStringSafe } from "@archivist-gg/core";
7
+ var KNOWN_KEYS = /* @__PURE__ */ new Set([
8
+ "name",
9
+ "level",
10
+ "school",
11
+ "casting_time",
12
+ "range",
13
+ "components",
14
+ "duration",
15
+ "concentration",
16
+ "ritual",
17
+ "classes",
18
+ "description",
19
+ "at_higher_levels",
20
+ "damage",
21
+ "saving_throw",
22
+ "casting_options",
23
+ // §2 · the fourteen converter keys. This gate and `spellEntitySchema` are independent: a key
24
+ // missing here is refused BEFORE zod ever sees it, so both must learn every new key.
25
+ "rendering_hint",
26
+ "misc_tags",
27
+ "area_tags",
28
+ "condition_inflict",
29
+ "affects_creature_type",
30
+ "spell_attack",
31
+ "ability_check",
32
+ "damage_resist",
33
+ "damage_immune",
34
+ "condition_immune",
35
+ "damage_vulnerable",
36
+ "has_fluff",
37
+ "image",
38
+ "has_fluff_images",
39
+ // Body-only metadata that's emitted to YAML but ignored at runtime — accept silently.
40
+ "slug",
41
+ "edition",
42
+ "source"
43
+ ]);
44
+ var DURATION_END_WORDS = { dispel: "dispelled", trigger: "triggered" };
45
+ function normalizeSpellComponents(value) {
46
+ if (typeof value === "string") return value;
47
+ if (value == null || typeof value !== "object" || Array.isArray(value)) return toStringSafe(value);
48
+ const c = value;
49
+ const parts = [];
50
+ if (c.v === true) parts.push("V");
51
+ if (c.s === true) parts.push("S");
52
+ if (typeof c.m === "string") parts.push(`M (${c.m})`);
53
+ else if (c.m === true) parts.push("M");
54
+ else if (c.m != null && typeof c.m === "object") {
55
+ const text = c.m.text;
56
+ parts.push(typeof text === "string" ? `M (${text})` : "M");
57
+ }
58
+ if (c.r === true) parts.push("R");
59
+ return parts.join(", ");
60
+ }
61
+ function normalizeSpellDurationEntry(entry) {
62
+ if (entry == null || typeof entry !== "object") return toStringSafe(entry);
63
+ const e = entry;
64
+ if (e.type === "permanent" && Array.isArray(e.ends)) {
65
+ return "until " + e.ends.map((w) => {
66
+ var _a;
67
+ return (_a = DURATION_END_WORDS[String(w)]) != null ? _a : String(w);
68
+ }).join(" or ");
69
+ }
70
+ if (e.type === "timed" && e.duration != null) {
71
+ const unit = e.duration.type;
72
+ const amount = e.duration.amount;
73
+ if (typeof unit === "string" && typeof amount === "number") {
74
+ const prefix = e.duration.up_to === true ? "up to " : "";
75
+ return `${prefix}${amount} ${unit}${amount > 1 ? "s" : ""}`;
76
+ }
77
+ }
78
+ return toStringSafe(entry);
79
+ }
80
+ function normalizeSpellDuration(value) {
81
+ if (typeof value === "string") return value;
82
+ if (!Array.isArray(value)) return toStringSafe(value);
83
+ return value.map((entry) => normalizeSpellDurationEntry(entry)).join("; ");
84
+ }
85
+ function mirrorSpellShapes(entity) {
86
+ return {
87
+ ...entity,
88
+ ...entity.components != null ? { components: normalizeSpellComponents(entity.components) } : {},
89
+ ...entity.duration != null ? { duration: normalizeSpellDuration(entity.duration) } : {}
90
+ };
91
+ }
92
+ function parseSpell(source) {
93
+ const result = parseYaml(source, ["name"]);
94
+ if (!result.success) return result;
95
+ const meta = {};
96
+ if (typeof result.data.source === "string") meta.source = result.data.source;
97
+ if (typeof result.data.edition === "string") meta.edition = result.data.edition;
98
+ const filtered = {};
99
+ for (const [k, v] of Object.entries(result.data)) {
100
+ if (k === "slug" || k === "edition" || k === "source") continue;
101
+ if (!KNOWN_KEYS.has(k)) {
102
+ return { success: false, error: `Unknown spell field: ${k}` };
103
+ }
104
+ filtered[k] = v;
105
+ }
106
+ const parsed = spellEntitySchema.safeParse(filtered);
107
+ if (!parsed.success) {
108
+ return { success: false, error: `spell schema validation failed: ${parsed.error.message}` };
109
+ }
110
+ const raw = parsed.data;
111
+ const spell = { name: raw.name };
112
+ if (raw.level != null) spell.level = raw.level;
113
+ if (raw.school != null) spell.school = toStringSafe(raw.school);
114
+ if (raw.casting_time != null) spell.casting_time = toStringSafe(raw.casting_time);
115
+ if (raw.range != null) spell.range = toStringSafe(raw.range);
116
+ if (raw.components != null) spell.components = normalizeSpellComponents(raw.components);
117
+ if (raw.duration != null) spell.duration = normalizeSpellDuration(raw.duration);
118
+ if (raw.concentration != null) spell.concentration = Boolean(raw.concentration);
119
+ if (raw.ritual != null) spell.ritual = Boolean(raw.ritual);
120
+ if (raw.classes) spell.classes = raw.classes.map(String);
121
+ if (raw.description) spell.description = raw.description;
122
+ if (raw.at_higher_levels) spell.at_higher_levels = raw.at_higher_levels.map(String);
123
+ if (raw.damage) spell.damage = { types: raw.damage.types.map(String) };
124
+ if (raw.saving_throw) spell.saving_throw = { ability: raw.saving_throw.ability };
125
+ if (raw.casting_options) {
126
+ spell.casting_options = raw.casting_options.map((opt) => {
127
+ const co = { type: opt.type };
128
+ if (opt.damage_roll !== void 0) co.damage_roll = opt.damage_roll;
129
+ if (opt.target_count !== void 0) co.target_count = opt.target_count;
130
+ if (opt.duration !== void 0) co.duration = opt.duration;
131
+ if (opt.range !== void 0) co.range = opt.range;
132
+ if (opt.concentration !== void 0) co.concentration = opt.concentration;
133
+ if (opt.shape_size !== void 0) co.shape_size = opt.shape_size;
134
+ if (opt.desc !== void 0) co.desc = opt.desc;
135
+ return co;
136
+ });
137
+ }
138
+ if (raw.rendering_hint != null) spell.rendering_hint = raw.rendering_hint;
139
+ if (raw.misc_tags != null) spell.misc_tags = raw.misc_tags;
140
+ if (raw.area_tags != null) spell.area_tags = raw.area_tags;
141
+ if (raw.condition_inflict != null) spell.condition_inflict = raw.condition_inflict;
142
+ if (raw.affects_creature_type != null) spell.affects_creature_type = raw.affects_creature_type;
143
+ if (raw.spell_attack != null) spell.spell_attack = raw.spell_attack;
144
+ if (raw.ability_check != null) spell.ability_check = raw.ability_check;
145
+ if (raw.damage_resist != null) spell.damage_resist = raw.damage_resist;
146
+ if (raw.damage_immune != null) spell.damage_immune = raw.damage_immune;
147
+ if (raw.condition_immune != null) spell.condition_immune = raw.condition_immune;
148
+ if (raw.damage_vulnerable != null) spell.damage_vulnerable = raw.damage_vulnerable;
149
+ if (raw.has_fluff != null) spell.has_fluff = raw.has_fluff;
150
+ if (raw.image != null) spell.image = raw.image;
151
+ if (raw.has_fluff_images != null) spell.has_fluff_images = raw.has_fluff_images;
152
+ if (meta.source) spell.source = meta.source;
153
+ if (meta.edition) spell.edition = meta.edition;
154
+ return { success: true, data: spell };
155
+ }
156
+
157
+ export {
158
+ normalizeSpellComponents,
159
+ normalizeSpellDuration,
160
+ mirrorSpellShapes,
161
+ parseSpell
162
+ };
@@ -2,8 +2,9 @@ import {
2
2
  ABILITY_KEYS,
3
3
  CR_PROFICIENCY,
4
4
  CR_XP,
5
+ DAMAGE_TYPES,
5
6
  SIZE_HIT_DICE
6
- } from "./chunk-WP2CX2JO.js";
7
+ } from "./chunk-WELIKLZF.js";
7
8
 
8
9
  // src/dnd/math.ts
9
10
  function abilityModifier(score) {
@@ -68,6 +69,22 @@ function abilityNameToKey(name) {
68
69
  }
69
70
  return null;
70
71
  }
72
+ var CANONICAL_DAMAGE_TYPES = new Set(DAMAGE_TYPES.map((t) => t.toLowerCase()));
73
+ function isCanonicalDamageType(type) {
74
+ return CANONICAL_DAMAGE_TYPES.has(type.trim().toLowerCase());
75
+ }
76
+ var CHOSEN_DAMAGE_TYPE = "chosen";
77
+ var CHOSEN_DAMAGE_TYPE_NOTE = "damage type of your choice";
78
+ function isChosenDamageType(type) {
79
+ return type !== void 0 && type.trim().toLowerCase() === CHOSEN_DAMAGE_TYPE;
80
+ }
81
+ function isRenderableDamageText(text) {
82
+ var _a;
83
+ const m = /^(?:\d*d\d+(?:\s*[+-]\s*\d+)?|[+-]?\d+)(?:\s+(.*))?$/i.exec(text.trim());
84
+ if (!m) return false;
85
+ const type = ((_a = m[1]) != null ? _a : "").trim();
86
+ return type === "" || isCanonicalDamageType(type);
87
+ }
71
88
 
72
89
  export {
73
90
  abilityModifier,
@@ -84,5 +101,10 @@ export {
84
101
  passivePerception,
85
102
  attackBonus,
86
103
  saveDC,
87
- abilityNameToKey
104
+ abilityNameToKey,
105
+ isCanonicalDamageType,
106
+ CHOSEN_DAMAGE_TYPE,
107
+ CHOSEN_DAMAGE_TYPE_NOTE,
108
+ isChosenDamageType,
109
+ isRenderableDamageText
88
110
  };
@@ -0,0 +1,50 @@
1
+ // src/pc/pool-layout.ts
2
+ var RENDERING_HINT_LAYOUT = {
3
+ "dice-pool": "dice-pool",
4
+ "point-pool": "point-pool"
5
+ };
6
+ function derivePoolLayout(entries) {
7
+ var _a, _b;
8
+ const votes = /* @__PURE__ */ new Map();
9
+ for (const e of entries) {
10
+ const hint = (_a = e.entity.rendering_hint) != null ? _a : "";
11
+ if (!Object.prototype.hasOwnProperty.call(RENDERING_HINT_LAYOUT, hint)) continue;
12
+ const layout = RENDERING_HINT_LAYOUT[hint];
13
+ votes.set(layout, ((_b = votes.get(layout)) != null ? _b : 0) + 1);
14
+ }
15
+ let best;
16
+ let bestN = 0;
17
+ let tied = false;
18
+ for (const [layout, n] of votes) {
19
+ if (n > bestN) {
20
+ best = layout;
21
+ bestN = n;
22
+ tied = false;
23
+ } else if (n === bestN) tied = true;
24
+ }
25
+ return tied ? void 0 : best;
26
+ }
27
+ var RENDERING_HINT_AFFORDANCE = {
28
+ "granted-die-to-ally": "granted-die"
29
+ };
30
+ function deriveEntryAffordance(entity) {
31
+ var _a;
32
+ const hint = (_a = entity.rendering_hint) != null ? _a : "";
33
+ if (!Object.prototype.hasOwnProperty.call(RENDERING_HINT_AFFORDANCE, hint)) return void 0;
34
+ return RENDERING_HINT_AFFORDANCE[hint];
35
+ }
36
+ var AFFORDANCE_CAPTIONS = {
37
+ "granted-die": "{amount}{die} to an ally"
38
+ };
39
+ function formatAffordanceCaption(affordance, values) {
40
+ return AFFORDANCE_CAPTIONS[affordance].replace(/\{(\w+)\}/g, (whole, key) => Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : whole);
41
+ }
42
+
43
+ export {
44
+ RENDERING_HINT_LAYOUT,
45
+ derivePoolLayout,
46
+ RENDERING_HINT_AFFORDANCE,
47
+ deriveEntryAffordance,
48
+ AFFORDANCE_CAPTIONS,
49
+ formatAffordanceCaption
50
+ };
@@ -0,0 +1,6 @@
1
+ // src/race/race.structural.ts
2
+ var RACE_STRUCTURAL_PSEUDO = /* @__PURE__ */ new Set(["size", "speed", "darkvision"]);
3
+
4
+ export {
5
+ RACE_STRUCTURAL_PSEUDO
6
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSubclass
3
- } from "./chunk-L7E2UT5A.js";
3
+ } from "./chunk-HB7XZXDK.js";
4
4
 
5
5
  // src/subclass/subclass.codec.ts
6
6
  import yaml from "js-yaml";
@@ -0,0 +1,89 @@
1
+ import {
2
+ actionCostEnum,
3
+ resourceSchema
4
+ } from "./chunk-YLL2QXY2.js";
5
+ import {
6
+ choiceSchema
7
+ } from "./chunk-T26ZVCL5.js";
8
+ import {
9
+ featureEffectSchema
10
+ } from "./chunk-WPWOHGPI.js";
11
+ import {
12
+ additionalSpellsEntrySchema,
13
+ imageField,
14
+ progressionSchema
15
+ } from "./chunk-UA5KS2PL.js";
16
+
17
+ // src/feat/feat.schema.ts
18
+ import { z } from "zod";
19
+ var abilityEnum = z.enum(["str", "dex", "con", "int", "wis", "cha"]);
20
+ var editionEnum = z.enum(["2014", "2024"]);
21
+ var categoryEnum = z.enum(["origin", "general", "fighting-style", "epic-boon"]);
22
+ var prerequisiteSchema = z.discriminatedUnion("kind", [
23
+ z.object({ kind: z.literal("ability"), ability: abilityEnum, min: z.number().int().positive() }),
24
+ z.object({ kind: z.literal("level"), min: z.number().int().positive() }),
25
+ z.object({ kind: z.literal("spellcaster") }),
26
+ z.object({
27
+ kind: z.literal("proficiency"),
28
+ proficiency_type: z.enum(["armor", "weapon", "tool", "skill", "saving-throw"]),
29
+ value: z.string().min(1)
30
+ }),
31
+ z.object({ kind: z.literal("race"), slug: z.string().min(1) }),
32
+ z.object({ kind: z.literal("class"), slug: z.string().min(1) }),
33
+ // The exclusive-feat-category and feat-category arms carry a raw 5etools category code, not a
34
+ // slug; featCategoryLabel names it. The rest carry slugs the renderer humanizes, except other,
35
+ // which carries free prose that prereqText returns verbatim.
36
+ z.object({ kind: z.literal("feat"), slug: z.string().min(1) }),
37
+ z.object({ kind: z.literal("campaign"), slug: z.string().min(1) }),
38
+ z.object({ kind: z.literal("exclusive-feat-category"), slug: z.string().min(1) }),
39
+ z.object({ kind: z.literal("feature"), slug: z.string().min(1) }),
40
+ z.object({ kind: z.literal("other"), detail: z.string().min(1) }),
41
+ z.object({ kind: z.literal("feat-category"), slug: z.string().min(1) }),
42
+ z.object({ kind: z.literal("background"), slug: z.string().min(1) })
43
+ ]);
44
+ var featEntitySchema = z.object({
45
+ slug: z.string().min(1),
46
+ name: z.string().min(1),
47
+ edition: editionEnum,
48
+ source: z.string().min(1),
49
+ category: categoryEnum,
50
+ description: z.string(),
51
+ prerequisites: z.array(prerequisiteSchema),
52
+ benefits: z.array(z.string()),
53
+ effects: z.array(featureEffectSchema),
54
+ grants_asi: z.object({
55
+ amount: z.number().int().positive(),
56
+ pool: z.array(abilityEnum).optional()
57
+ }).nullable(),
58
+ repeatable: z.boolean(),
59
+ choices: z.array(choiceSchema),
60
+ rendering_hint: z.string().optional(),
61
+ additional_spells: z.array(additionalSpellsEntrySchema).optional(),
62
+ // 71
63
+ has_fluff: z.boolean().optional(),
64
+ // 0 carriers — symmetry, §9.4
65
+ has_fluff_images: z.boolean().optional(),
66
+ // 41
67
+ trait_tags: z.unknown().optional(),
68
+ // 7, nullable measured — same passthrough family
69
+ optionalfeature_progression: z.array(progressionSchema).optional(),
70
+ // 4
71
+ resources: z.array(resourceSchema).optional(),
72
+ // closes the feat.types.ts `Feat.resources` schema/type asymmetry (C);
73
+ // ZERO converter/bundle carriers (no census row) — the only behaviour delta is AUTHORED feats,
74
+ // where the type has promised the field all along. Import resourceSchema.
75
+ action_cost: actionCostEnum.optional(),
76
+ // ruling R-G1b-5 (gate1-r1 f1): a TOP-LEVEL bundle key on ONE
77
+ // feat (`SRD 2024/Feats/Boon of the Night Spirit.md`, `action_cost: bonus-action` at entity top
78
+ // level) — the ninth SRD-control non-kept row: top-level on the ENTITY, not featureSchema-shaped
79
+ // (finding 14's nine rows split 4 featureSchema / 4 §2.8-arity / this one, which needed its own
80
+ // declaration — gate1-r4 R4-1). `actionCostEnum` is exported from
81
+ // `schemas/resource-schema.ts` (optionalFeatureEntitySchema already imports it). MAPPED since
82
+ // R4-G3a §10.2.3: the resolver aliases it onto the resolved feature's `action`, which is what
83
+ // draws the feat note's Action line and files the row under that economy.
84
+ image: imageField
85
+ });
86
+
87
+ export {
88
+ featEntitySchema
89
+ };
@@ -0,0 +1,16 @@
1
+ // src/pc/pc.proficiency-normalize.ts
2
+ var normKey = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean).map((w) => w.endsWith("s") ? w.slice(0, -1) : w).sort().join(" ");
3
+ var toProfSlug = (s) => s.toLowerCase().replace(/’/g, "'").trim().replace(/\s+/g, "-");
4
+ var humanizeProficiency = (s) => s.replace(/-/g, " ").replace(/(^|\s)\w/g, (c) => c.toUpperCase());
5
+ var proficiencyLabel = (raw) => {
6
+ const text = raw.replace(/’/g, "'").trim().replace(/\s+/g, " ");
7
+ if (!/\s/.test(text) || text === text.toLowerCase()) return humanizeProficiency(toProfSlug(raw));
8
+ return text.charAt(0).toUpperCase() + text.slice(1);
9
+ };
10
+
11
+ export {
12
+ normKey,
13
+ toProfSlug,
14
+ humanizeProficiency,
15
+ proficiencyLabel
16
+ };
@@ -0,0 +1,21 @@
1
+ // src/pc/conditions.constants.ts
2
+ var CONDITION_SLUGS = [
3
+ "blinded",
4
+ "charmed",
5
+ "deafened",
6
+ "frightened",
7
+ "grappled",
8
+ "incapacitated",
9
+ "invisible",
10
+ "paralyzed",
11
+ "petrified",
12
+ "poisoned",
13
+ "prone",
14
+ "restrained",
15
+ "stunned",
16
+ "unconscious"
17
+ ];
18
+
19
+ export {
20
+ CONDITION_SLUGS
21
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  itemEntitySchema
3
- } from "./chunk-K6Q5OUGK.js";
3
+ } from "./chunk-GFZ7PITJ.js";
4
4
 
5
5
  // src/item/item.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -41,6 +41,14 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
41
41
  "scroll_level",
42
42
  "unidentified",
43
43
  "masked_category",
44
+ // Modelled top-level converter keys (spec §4). The schema declaring them is NOT
45
+ // enough: this set is the second, independent gate — a key missing here is
46
+ // copied into `raw` as well, so the two lists must move together.
47
+ "modify_speed",
48
+ "rendering_hint",
49
+ "has_fluff",
50
+ "has_fluff_images",
51
+ "image",
44
52
  // Legacy fields kept (removed in Slice 7 after grep verifies no consumer)
45
53
  "damage_dice",
46
54
  "damage_type",