@archivist-gg/dnd5e 0.3.1 → 0.3.5

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 (259) 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 +26 -0
  10. package/dist/background/background.schema.js +5 -4
  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-2VDTW7QO.js +1580 -0
  16. package/dist/{chunk-ZZGEVJKX.js → chunk-3JOTQW7A.js} +1 -1
  17. package/dist/{chunk-5ZBE4CWV.js → chunk-3PZHBLFG.js} +1 -1
  18. package/dist/chunk-4U55VNC7.js +22 -0
  19. package/dist/{chunk-4JHOHWBI.js → chunk-4XTNPNQB.js} +1 -1
  20. package/dist/{chunk-HCP3GMHS.js → chunk-5N2GW72F.js} +15 -2
  21. package/dist/{chunk-42Y5IJKT.js → chunk-5RKLL2AD.js} +5 -1
  22. package/dist/{chunk-SE7AZDR3.js → chunk-63XQXPZL.js} +29 -3
  23. package/dist/{chunk-4F6AIZ36.js → chunk-6BFWZW6T.js} +1 -1
  24. package/dist/{chunk-GJOI2GHP.js → chunk-6GNXYAYP.js} +1 -1
  25. package/dist/{chunk-66XXW2Z2.js → chunk-6PXTNCBP.js} +2 -2
  26. package/dist/chunk-6X2NV665.js +33 -0
  27. package/dist/{chunk-LV4SNWZQ.js → chunk-6X4KUDMU.js} +8 -1
  28. package/dist/{chunk-ZMR77DDL.js → chunk-AW3EHF3L.js} +1 -1
  29. package/dist/{chunk-TLYDBT5F.js → chunk-CGRLQ34U.js} +64 -7
  30. package/dist/chunk-CJYCVNW2.js +7 -0
  31. package/dist/{chunk-XLSVNUTK.js → chunk-CV5XDJKU.js} +1 -1
  32. package/dist/{chunk-RAWJTA45.js → chunk-DEEHHLAP.js} +1 -1
  33. package/dist/{chunk-6IRYTM5V.js → chunk-DPUJDOKV.js} +119 -28
  34. package/dist/{chunk-GE4TLPWM.js → chunk-EHDATXP7.js} +1 -1
  35. package/dist/chunk-EVMUJYUD.js +195 -0
  36. package/dist/{chunk-K6Q5OUGK.js → chunk-GFZ7PITJ.js} +86 -10
  37. package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
  38. package/dist/{chunk-TN6ZLTNU.js → chunk-I2NDWFDD.js} +51 -4
  39. package/dist/{chunk-3DZ74AKK.js → chunk-IN4M4X7P.js} +8 -2
  40. package/dist/chunk-IUAAXXNO.js +58 -0
  41. package/dist/{chunk-NXSWH3AE.js → chunk-IWRVOCT5.js} +1 -1
  42. package/dist/{chunk-XJNECUPT.js → chunk-JC5UNIVM.js} +19 -0
  43. package/dist/{chunk-24GFQU7T.js → chunk-JSZ4H6AQ.js} +1 -1
  44. package/dist/{chunk-XTIQWSFM.js → chunk-KADJZGZY.js} +1 -1
  45. package/dist/{chunk-L7E2UT5A.js → chunk-KK33PIDN.js} +5 -1
  46. package/dist/chunk-KPEJMNRD.js +302 -0
  47. package/dist/chunk-KU3YLF6D.js +106 -0
  48. package/dist/{chunk-5INR4NSL.js → chunk-LDDCNPHD.js} +1 -1
  49. package/dist/chunk-LVQLWRCU.js +15 -0
  50. package/dist/chunk-MI7P3NX3.js +161 -0
  51. package/dist/{chunk-PHQRGDRQ.js → chunk-N3A5ICBO.js} +5 -1
  52. package/dist/chunk-O4SRD3J5.js +165 -0
  53. package/dist/chunk-OAXWSVJB.js +81 -0
  54. package/dist/chunk-OZP6JFGT.js +162 -0
  55. package/dist/{chunk-NAEPSZAM.js → chunk-PDJSUH24.js} +24 -2
  56. package/dist/chunk-PP2UHLBA.js +50 -0
  57. package/dist/chunk-PVCEA4HZ.js +6 -0
  58. package/dist/{chunk-RSDRMH54.js → chunk-RF4TYCA2.js} +1 -1
  59. package/dist/chunk-RLGQ5PZZ.js +16 -0
  60. package/dist/chunk-RVXC6ZHY.js +89 -0
  61. package/dist/chunk-S7REMWW4.js +21 -0
  62. package/dist/{chunk-7VJ6BOA5.js → chunk-SMLXZUBH.js} +9 -1
  63. package/dist/chunk-TOM66P2P.js +38 -0
  64. package/dist/chunk-UA5KS2PL.js +26 -0
  65. package/dist/{chunk-N6N74K6X.js → chunk-UBBKSYBY.js} +1 -1
  66. package/dist/{chunk-353WXS6S.js → chunk-UIRMA5KM.js} +1 -1
  67. package/dist/{chunk-BIK4DFWK.js → chunk-ULCQ73LA.js} +1 -1
  68. package/dist/{chunk-2BWAID3Y.js → chunk-UMWIPLLM.js} +29 -3
  69. package/dist/chunk-UQ3ZFCWD.js +178 -0
  70. package/dist/chunk-UXPGW4TI.js +101 -0
  71. package/dist/chunk-V2MJE6NC.js +133 -0
  72. package/dist/{chunk-D3CLOBE3.js → chunk-V373TRJE.js} +48 -10
  73. package/dist/chunk-VQAXTIBW.js +100 -0
  74. package/dist/chunk-W74BCA7V.js +159 -0
  75. package/dist/{chunk-WP2CX2JO.js → chunk-WELIKLZF.js} +22 -1
  76. package/dist/{chunk-MDEJHMKK.js → chunk-WHVLNFTG.js} +26 -5
  77. package/dist/{chunk-GZ46OVGZ.js → chunk-WSVMLNTO.js} +1 -1
  78. package/dist/{chunk-4LZJXJJS.js → chunk-WXWPK3LI.js} +6 -2
  79. package/dist/chunk-X3D6LXUU.js +185 -0
  80. package/dist/chunk-XJ2F2WO6.js +8 -0
  81. package/dist/{chunk-73HOFQQ5.js → chunk-Y4AGLCY6.js} +1 -1
  82. package/dist/chunk-YBLDE5CG.js +42 -0
  83. package/dist/{chunk-JEYQU3JT.js → chunk-YHYTW5QA.js} +2 -18
  84. package/dist/{chunk-PZOWBI3B.js → chunk-YLL2QXY2.js} +7 -2
  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 +239 -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 +2033 -6907
  122. package/dist/item/item.codec.js +4 -3
  123. package/dist/item/item.enrichment.js +4 -4
  124. package/dist/item/item.parser.js +3 -2
  125. package/dist/item/item.schema.d.ts +69 -3
  126. package/dist/item/item.schema.js +2 -1
  127. package/dist/item/item.types.d.ts +69 -3
  128. package/dist/monster/monster.codec.d.ts +7 -6
  129. package/dist/monster/monster.enrichment.js +4 -4
  130. package/dist/monster/monster.format.d.ts +135 -0
  131. package/dist/monster/monster.format.js +78 -0
  132. package/dist/monster/monster.parser.d.ts +6 -1
  133. package/dist/monster/monster.parser.js +10 -1
  134. package/dist/monster/monster.schema.d.ts +369 -0
  135. package/dist/monster/monster.schema.js +34 -0
  136. package/dist/monster/monster.types.d.ts +200 -14
  137. package/dist/optional-feature/optional-feature.codec.js +6 -5
  138. package/dist/optional-feature/optional-feature.parser.js +5 -4
  139. package/dist/optional-feature/optional-feature.schema.d.ts +185 -0
  140. package/dist/optional-feature/optional-feature.schema.js +4 -3
  141. package/dist/pack.d.ts +1 -1
  142. package/dist/pc/conditions.constants.d.ts +0 -1
  143. package/dist/pc/conditions.constants.js +1 -3
  144. package/dist/pc/decision-recognizer.js +1 -1
  145. package/dist/pc/pc.additional-spells.d.ts +47 -0
  146. package/dist/pc/pc.additional-spells.js +23 -0
  147. package/dist/pc/pc.asi-flatten.d.ts +18 -0
  148. package/dist/pc/pc.decision-engine.d.ts +105 -17
  149. package/dist/pc/pc.decision-engine.js +28 -12
  150. package/dist/pc/pc.defense-normalize.d.ts +11 -0
  151. package/dist/pc/pc.defense-normalize.js +6 -0
  152. package/dist/pc/pc.equipment.d.ts +31 -2
  153. package/dist/pc/pc.equipment.js +12 -8
  154. package/dist/pc/pc.feature-effects.d.ts +234 -11
  155. package/dist/pc/pc.feature-effects.js +21 -5
  156. package/dist/pc/pc.parser.js +2 -2
  157. package/dist/pc/pc.pools.d.ts +11 -3
  158. package/dist/pc/pc.pools.js +9 -13
  159. package/dist/pc/pc.proficiencies.d.ts +54 -29
  160. package/dist/pc/pc.proficiencies.js +74 -95
  161. package/dist/pc/pc.proficiency-grants.d.ts +58 -0
  162. package/dist/pc/pc.proficiency-normalize.d.ts +24 -0
  163. package/dist/pc/pc.proficiency-normalize.js +12 -0
  164. package/dist/pc/pc.proficiency-query.d.ts +13 -0
  165. package/dist/pc/pc.proficiency-query.js +1 -1
  166. package/dist/pc/pc.recalc.d.ts +69 -14
  167. package/dist/pc/pc.recalc.js +261 -122
  168. package/dist/pc/pc.resolver.d.ts +34 -8
  169. package/dist/pc/pc.resolver.js +27 -12
  170. package/dist/pc/pc.resource-seed.d.ts +3 -2
  171. package/dist/pc/pc.resource-seed.js +19 -17
  172. package/dist/pc/pc.resources.d.ts +87 -0
  173. package/dist/pc/pc.resources.js +21 -0
  174. package/dist/pc/pc.rest.d.ts +10 -1
  175. package/dist/pc/pc.rest.js +66 -12
  176. package/dist/pc/pc.schema.d.ts +139 -3
  177. package/dist/pc/pc.schema.js +4 -2
  178. package/dist/pc/pc.slotting.js +3 -3
  179. package/dist/pc/pc.spellcasting.d.ts +75 -6
  180. package/dist/pc/pc.spellcasting.js +16 -6
  181. package/dist/pc/pc.table-column.d.ts +23 -0
  182. package/dist/pc/pc.table-column.js +13 -3
  183. package/dist/pc/pc.types.d.ts +251 -12
  184. package/dist/pc/pool-layout.d.ts +47 -0
  185. package/dist/pc/pool-layout.js +16 -0
  186. package/dist/pc/roll-scope.d.ts +2 -0
  187. package/dist/pc/roll-scope.js +8 -0
  188. package/dist/pc/roll-tag-labels.d.ts +40 -0
  189. package/dist/pc/roll-tag-labels.js +17 -0
  190. package/dist/pc/spell-source.d.ts +21 -0
  191. package/dist/pc/spell-source.js +13 -0
  192. package/dist/race/race.codec.js +9 -7
  193. package/dist/race/race.parser.js +8 -6
  194. package/dist/race/race.schema.d.ts +51 -1
  195. package/dist/race/race.schema.js +8 -5
  196. package/dist/race/race.structural.d.ts +7 -0
  197. package/dist/race/race.structural.js +6 -0
  198. package/dist/race/race.types.d.ts +20 -0
  199. package/dist/schemas/attack-schema.d.ts +5 -0
  200. package/dist/schemas/caster-type-schema.d.ts +14 -0
  201. package/dist/schemas/caster-type-schema.js +6 -0
  202. package/dist/schemas/choice-schema.js +2 -2
  203. package/dist/schemas/entity-extras-schema.d.ts +53 -0
  204. package/dist/schemas/entity-extras-schema.js +10 -0
  205. package/dist/schemas/equipment-grant-schema.d.ts +2 -0
  206. package/dist/schemas/equipment-grant-schema.js +1 -1
  207. package/dist/schemas/feature-alias.d.ts +34 -0
  208. package/dist/schemas/feature-effect-schema.d.ts +133 -0
  209. package/dist/schemas/feature-effect-schema.js +1 -1
  210. package/dist/schemas/feature-schema.d.ts +8 -0
  211. package/dist/schemas/feature-schema.js +6 -4
  212. package/dist/schemas/resource-schema.d.ts +43 -0
  213. package/dist/schemas/resource-schema.js +1 -1
  214. package/dist/schemas/selection-pool-schema.d.ts +2 -0
  215. package/dist/schemas/selection-pool-schema.js +1 -1
  216. package/dist/spell/spell.access.js +23 -3
  217. package/dist/spell/spell.codec.js +4 -3
  218. package/dist/spell/spell.enrichment.js +4 -4
  219. package/dist/spell/spell.parser.d.ts +30 -0
  220. package/dist/spell/spell.parser.js +9 -2
  221. package/dist/spell/spell.scaling.d.ts +27 -6
  222. package/dist/spell/spell.scaling.js +25 -6
  223. package/dist/spell/spell.schema.d.ts +44 -2
  224. package/dist/spell/spell.schema.js +2 -1
  225. package/dist/spell/spell.types.d.ts +14 -0
  226. package/dist/subclass/subclass.codec.d.ts +1 -1
  227. package/dist/subclass/subclass.codec.js +11 -8
  228. package/dist/subclass/subclass.parser.js +10 -7
  229. package/dist/subclass/subclass.schema.d.ts +54 -7
  230. package/dist/subclass/subclass.schema.js +8 -6
  231. package/dist/subclass/subclass.types.d.ts +20 -2
  232. package/dist/types/choice.d.ts +53 -2
  233. package/dist/types/choice.js +13 -1
  234. package/dist/types/equipment-grant.d.ts +8 -1
  235. package/dist/types/feature-effect.d.ts +85 -23
  236. package/dist/types/feature.d.ts +70 -1
  237. package/dist/types/optional-feature.types.d.ts +28 -0
  238. package/dist/types/resource.d.ts +21 -1
  239. package/dist/types/selection-pool.d.ts +17 -2
  240. package/dist/weapon/weapon.classify.js +2 -2
  241. package/dist/weapon/weapon.codec.js +4 -3
  242. package/dist/weapon/weapon.parser.js +3 -2
  243. package/dist/weapon/weapon.schema.d.ts +4 -0
  244. package/dist/weapon/weapon.schema.js +2 -1
  245. package/dist/weapon/weapon.types.d.ts +8 -0
  246. package/package.json +2 -2
  247. package/dist/chunk-5XUXXAQJ.js +0 -30
  248. package/dist/chunk-7P65Q426.js +0 -158
  249. package/dist/chunk-7TEK5KK7.js +0 -49
  250. package/dist/chunk-AIRFC5NQ.js +0 -143
  251. package/dist/chunk-CZPMTQOY.js +0 -68
  252. package/dist/chunk-N2JE7BWL.js +0 -81
  253. package/dist/chunk-NXR3BD2V.js +0 -90
  254. package/dist/chunk-O5EYMKFH.js +0 -6
  255. package/dist/chunk-Q5K6LWBZ.js +0 -45
  256. package/dist/chunk-TNUFPIXI.js +0 -1110
  257. package/dist/chunk-TZK36BU7.js +0 -38
  258. package/dist/chunk-W7HWMMBF.js +0 -46
  259. package/dist/chunk-Y4I74YEG.js +0 -33
@@ -0,0 +1,38 @@
1
+ import {
2
+ ALL_SKILL_SLUGS
3
+ } from "./chunk-KU3YLF6D.js";
4
+ import {
5
+ normalizeAbility
6
+ } from "./chunk-WELIKLZF.js";
7
+
8
+ // src/pc/roll-scope.ts
9
+ var NOUNS = /\b(saving throws?|saves?|ability checks?|checks?|attack rolls?)\b/gi;
10
+ var QUANTIFIERS = /\b(all|any|other|the next|you make|that you make)\b/gi;
11
+ var slug = (s) => s.toLowerCase().trim().replace(/[^a-z]+/g, "-").replace(/(^-|-$)/g, "");
12
+ function normalizeRollScope(raw, _roll) {
13
+ if (raw == null) return void 0;
14
+ const cleaned = raw.replace(NOUNS, " ").replace(QUANTIFIERS, " ").replace(/\s+/g, " ").trim();
15
+ const parts = cleaned.split(/\s*(?:,|\/|\band\b|\bor\b)\s*/i).map((p) => p.trim()).filter(Boolean);
16
+ if (parts.length === 0 || parts.length > 3) return void 0;
17
+ const out = [];
18
+ for (let p of parts) {
19
+ const m = p.match(/^(?:[a-z]+\s*)?\(\s*([a-z -]+)\s*\)$/i);
20
+ if (m) p = m[1];
21
+ const s = slug(p);
22
+ if (ALL_SKILL_SLUGS.includes(s)) {
23
+ out.push(s);
24
+ continue;
25
+ }
26
+ const ab = normalizeAbility(p);
27
+ if (ab) {
28
+ out.push(ab);
29
+ continue;
30
+ }
31
+ return void 0;
32
+ }
33
+ return out;
34
+ }
35
+
36
+ export {
37
+ normalizeRollScope
38
+ };
@@ -0,0 +1,26 @@
1
+ // src/schemas/entity-extras-schema.ts
2
+ import { z } from "zod";
3
+ var imageField = z.union([z.string().min(1), z.array(z.string().min(1))]).optional();
4
+ var additionalSpellsEntrySchema = z.object({
5
+ name: z.string().min(1).optional(),
6
+ ability: z.union([z.string().min(1), z.object({ choose: z.array(z.string().min(1)) })]).optional(),
7
+ known: z.unknown().optional(),
8
+ innate: z.unknown().optional(),
9
+ prepared: z.unknown().optional(),
10
+ expanded: z.unknown().optional(),
11
+ resourceName: z.string().min(1).optional()
12
+ });
13
+ var progressionSchema = z.object({
14
+ name: z.string().min(1),
15
+ progression: z.union([z.record(z.string(), z.number()), z.array(z.number())]),
16
+ feature_type: z.array(z.string().min(1)).optional(),
17
+ featureType: z.array(z.string().min(1)).optional(),
18
+ category: z.array(z.string().min(1)).optional(),
19
+ required: z.record(z.string(), z.array(z.string())).optional()
20
+ });
21
+
22
+ export {
23
+ imageField,
24
+ additionalSpellsEntrySchema,
25
+ progressionSchema
26
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveBaseItem
3
- } from "./chunk-4LZJXJJS.js";
3
+ } from "./chunk-WXWPK3LI.js";
4
4
 
5
5
  // src/pc/pc.slotting.ts
6
6
  function isItemEntity(e) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  bareEntitySlug
3
- } from "./chunk-LV4SNWZQ.js";
3
+ } from "./chunk-6X4KUDMU.js";
4
4
 
5
5
  // src/weapon/weapon.classify.ts
6
6
  var PURE_RANGED_THROWN_SLUGS = /* @__PURE__ */ new Set(["dart", "net"]);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBackground
3
- } from "./chunk-5ZBE4CWV.js";
3
+ } from "./chunk-3PZHBLFG.js";
4
4
 
5
5
  // src/background/background.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,9 +1,13 @@
1
1
  import {
2
2
  featureSchema
3
- } from "./chunk-CZPMTQOY.js";
3
+ } from "./chunk-W74BCA7V.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-NXSWH3AE.js";
6
+ } from "./chunk-IWRVOCT5.js";
7
+ import {
8
+ additionalSpellsEntrySchema,
9
+ imageField
10
+ } from "./chunk-UA5KS2PL.js";
7
11
 
8
12
  // src/race/race.schema.ts
9
13
  import { z } from "zod";
@@ -55,9 +59,31 @@ var raceEntitySchema = z.object({
55
59
  variant_label: z.string().min(1),
56
60
  traits: z.array(featureSchema),
57
61
  choices: z.array(choiceSchema).optional(),
58
- subspecies_of: z.string().regex(wikilinkRegex).optional()
62
+ subspecies_of: z.string().regex(wikilinkRegex).optional(),
63
+ rendering_hint: z.string().optional(),
64
+ has_fluff: z.boolean().optional(),
65
+ has_fluff_images: z.boolean().optional(),
66
+ creature_type: z.array(z.string().min(1)).optional(),
67
+ // guarded non-empty at the emitter (finding 24)
68
+ creature_type_tags: z.array(z.string().min(1)).optional(),
69
+ additional_spells: z.union([
70
+ z.array(additionalSpellsEntrySchema),
71
+ // converter form, 109 docs
72
+ additionalSpellsEntrySchema
73
+ // SHIPPED-BUNDLE bare-object form, 3 docs (§2.8)
74
+ ]).optional(),
75
+ // The four RAW 5etools PASSTHROUGHS (race-mapper.ts D13 block copies them VERBATIM, no shape
76
+ // guard — finding 24): today's variant set is a snapshot, not a contract (lineage is already
77
+ // string|null|boolean), and NO G row ever reads them. Maximally loose by ruling; the guard
78
+ // test §9.5 pins today's variant classes since the census goes blind under z.unknown().
79
+ trait_tags: z.unknown().optional(),
80
+ lineage: z.unknown().optional(),
81
+ height_and_weight: z.unknown().optional(),
82
+ sound_clip: z.unknown().optional(),
83
+ image: imageField
59
84
  });
60
85
 
61
86
  export {
87
+ fixedAsiSchema,
62
88
  raceEntitySchema
63
89
  };
@@ -0,0 +1,178 @@
1
+ import {
2
+ featureObjectSchema
3
+ } from "./chunk-W74BCA7V.js";
4
+ import {
5
+ imageField
6
+ } from "./chunk-UA5KS2PL.js";
7
+
8
+ // src/monster/monster.schema.ts
9
+ import { z } from "zod";
10
+ var str = z.string();
11
+ var num = z.number();
12
+ var bool = z.boolean();
13
+ var strArr = z.array(z.string());
14
+ var damageQualifierSchema = z.object({
15
+ get types() {
16
+ return z.array(z.union([str, damageQualifierSchema])).optional();
17
+ },
18
+ note: str.optional(),
19
+ pre_note: str.optional(),
20
+ cond: bool.optional(),
21
+ special: str.optional()
22
+ });
23
+ var qualifierList = z.array(z.union([str, damageQualifierSchema])).optional();
24
+ var speedValue = z.union([num, z.object({ number: num, condition: str.optional() })]);
25
+ var monsterSpeedSchema = z.object({
26
+ walk: speedValue.optional(),
27
+ fly: speedValue.optional(),
28
+ swim: speedValue.optional(),
29
+ climb: speedValue.optional(),
30
+ burrow: speedValue.optional(),
31
+ hover: bool.optional(),
32
+ can_hover: bool.optional(),
33
+ alternate: z.record(str, z.array(z.object({ number: num, condition: str.optional() }))).optional(),
34
+ choose: z.object({ from: strArr, amount: num, note: str.optional() }).optional()
35
+ });
36
+ var monsterTypeSchema = z.object({
37
+ type: z.union([str, z.object({ choose: strArr })]),
38
+ tags: z.array(z.union([str, z.object({ tag: str, prefix: str, prefix_hidden: bool.optional() })])).optional(),
39
+ sidekick_type: str.optional(),
40
+ sidekick_tags: strArr.optional(),
41
+ swarm_size: str.optional(),
42
+ sidekick_hidden: bool.optional()
43
+ });
44
+ var monsterCRSchema = z.object({ cr: str, lair: str.optional(), coven: str.optional(), xp: num.optional(), xp_lair: num.optional() });
45
+ var monsterACSchema = z.object({ ac: num.optional(), from: strArr.optional(), condition: str.optional(), braces: bool.optional(), special: str.optional() });
46
+ var monsterHPSchema = z.object({ average: num.optional(), formula: str.optional(), special: str.optional() });
47
+ var alignmentEntrySchema = z.object({ alignment: strArr.optional(), chance: num.optional(), note: str.optional(), special: str.optional() });
48
+ var spellEntry = z.union([str, z.object({ entry: str, hidden: bool.optional() })]);
49
+ var spellList = z.array(spellEntry);
50
+ var spellLevel = z.object({ slots: num.optional(), lower: num.optional(), spells: strArr });
51
+ var monsterSpellcastingSchema = z.object({
52
+ name: str.optional(),
53
+ type: str.optional(),
54
+ headerEntries: strArr.optional(),
55
+ footerEntries: strArr.optional(),
56
+ ability: str.optional(),
57
+ displayAs: str.optional(),
58
+ hidden: strArr.optional(),
59
+ spells: z.record(str, spellLevel).optional(),
60
+ will: spellList.optional(),
61
+ ritual: spellList.optional(),
62
+ daily: z.record(str, spellList).optional(),
63
+ rest: z.record(str, spellList).optional(),
64
+ restLong: z.record(str, spellList).optional(),
65
+ recharge: z.record(str, spellList).optional(),
66
+ legendary: z.record(str, spellList).optional(),
67
+ charges: z.record(str, spellList).optional(),
68
+ chargesItem: str.optional()
69
+ });
70
+ var initiativeSchema = z.union([num, z.object({ proficiency: num.optional(), advantage_mode: str.optional() })]);
71
+ var gearEntrySchema = z.union([str, z.object({ item: str, quantity: num.optional(), displayName: str.optional() })]);
72
+ var featureArr = z.array(featureObjectSchema).optional();
73
+ var monsterSchema = z.object({
74
+ name: str.min(1),
75
+ size: z.union([str, strArr]).optional(),
76
+ size_note: str.optional(),
77
+ type: z.union([str, monsterTypeSchema]).optional(),
78
+ subtype: str.optional(),
79
+ alignment: z.union([str, z.array(z.union([str, alignmentEntrySchema]))]).optional(),
80
+ alignment_prefix: str.optional(),
81
+ cr: z.union([str, monsterCRSchema]).optional(),
82
+ pb_note: str.optional(),
83
+ ac: z.array(monsterACSchema).optional(),
84
+ hp: monsterHPSchema.optional(),
85
+ speed: monsterSpeedSchema.optional(),
86
+ abilities: z.object({ str: num, dex: num, con: num, int: num, wis: num, cha: num }).optional(),
87
+ saves: z.record(str, num).optional(),
88
+ skills: z.record(str, num).optional(),
89
+ skills_other: z.array(z.unknown()).optional(),
90
+ senses: strArr.nullable().optional(),
91
+ passive_perception: num.optional(),
92
+ languages: strArr.nullable().optional(),
93
+ damage_vulnerabilities: qualifierList,
94
+ damage_resistances: qualifierList,
95
+ damage_immunities: qualifierList,
96
+ condition_immunities: qualifierList,
97
+ traits: featureArr,
98
+ actions: featureArr,
99
+ bonus_actions: featureArr,
100
+ reactions: featureArr,
101
+ legendary_actions: featureArr,
102
+ mythic: featureArr,
103
+ mythic_header: strArr.optional(),
104
+ legendary_action_uses: num.optional(),
105
+ legendary_resistance: num.optional(),
106
+ legendary_actions_lair_count: num.optional(),
107
+ section_headers: z.array(z.object({ section: str, header: strArr })).optional(),
108
+ action_note: str.optional(),
109
+ reaction_note: str.optional(),
110
+ spellcasting: z.array(monsterSpellcastingSchema).optional(),
111
+ legendary_group: z.object({ name: str, source: str }).optional(),
112
+ lair_actions: z.array(z.unknown()).optional(),
113
+ regional_actions: z.array(z.unknown()).optional(),
114
+ variant: z.array(z.unknown()).optional(),
115
+ initiative: initiativeSchema.optional(),
116
+ gear: z.array(gearEntrySchema).optional(),
117
+ is_named_creature: bool.optional(),
118
+ is_npc: bool.optional(),
119
+ familiar: bool.optional(),
120
+ short_name: z.union([str, bool]).optional(),
121
+ level: num.optional(),
122
+ dragon_age: str.optional(),
123
+ dragon_casting_color: str.optional(),
124
+ environment: strArr.optional(),
125
+ treasure: strArr.optional(),
126
+ attached_items: strArr.optional(),
127
+ group: strArr.nullable().optional(),
128
+ summoned_by_spell: str.optional(),
129
+ summoned_by_spell_level: num.optional(),
130
+ summoned_by_class: str.optional(),
131
+ trait_tags: strArr.optional(),
132
+ action_tags: strArr.optional(),
133
+ sense_tags: strArr.optional(),
134
+ language_tags: strArr.optional(),
135
+ spellcasting_tags: strArr.optional(),
136
+ misc_tags: strArr.optional(),
137
+ damage_tags: strArr.optional(),
138
+ damage_tags_spell: strArr.optional(),
139
+ damage_tags_legendary: strArr.optional(),
140
+ condition_inflict: strArr.optional(),
141
+ condition_inflict_spell: strArr.optional(),
142
+ condition_inflict_legendary: strArr.optional(),
143
+ saving_throw_forced: strArr.optional(),
144
+ saving_throw_forced_spell: strArr.optional(),
145
+ saving_throw_forced_legendary: strArr.optional(),
146
+ has_fluff: bool.optional(),
147
+ has_fluff_images: bool.optional(),
148
+ has_token: bool.optional(),
149
+ token_credit: str.optional(),
150
+ token_custom: bool.optional(),
151
+ foundry_token_scale: num.optional(),
152
+ sound_clip: z.object({ type: str, path: str }).optional(),
153
+ alt_art: z.array(z.object({ name: str, source: str.optional(), page: num.optional() })).optional(),
154
+ image: imageField,
155
+ thumbnail: imageField,
156
+ rendering_hint: str.optional(),
157
+ columns: num.optional(),
158
+ slug: str.optional(),
159
+ edition: str.optional(),
160
+ source: str.optional(),
161
+ raw: z.record(str, z.unknown()).optional()
162
+ });
163
+ var MONSTER_KNOWN_KEYS = new Set(Object.keys(monsterSchema.shape));
164
+
165
+ export {
166
+ damageQualifierSchema,
167
+ monsterSpeedSchema,
168
+ monsterTypeSchema,
169
+ monsterCRSchema,
170
+ monsterACSchema,
171
+ monsterHPSchema,
172
+ alignmentEntrySchema,
173
+ monsterSpellcastingSchema,
174
+ initiativeSchema,
175
+ gearEntrySchema,
176
+ monsterSchema,
177
+ MONSTER_KNOWN_KEYS
178
+ };
@@ -0,0 +1,101 @@
1
+ import {
2
+ resolveSpellcasting
3
+ } from "./chunk-DPUJDOKV.js";
4
+ import {
5
+ warnOnce
6
+ } from "./chunk-LVQLWRCU.js";
7
+ import {
8
+ isValidMaxFormula
9
+ } from "./chunk-EVMUJYUD.js";
10
+
11
+ // src/pc/pc.resources.ts
12
+ function resourceLevelFor(source, resolved) {
13
+ var _a, _b, _c, _d;
14
+ if (source.kind === "class") {
15
+ return (_b = (_a = resolved.classes.find((c) => {
16
+ var _a2;
17
+ return ((_a2 = c.entity) == null ? void 0 : _a2.slug) === source.slug;
18
+ })) == null ? void 0 : _a.level) != null ? _b : resolved.totalLevel;
19
+ }
20
+ if (source.kind === "subclass") {
21
+ return (_d = (_c = resolved.classes.find((c) => {
22
+ var _a2;
23
+ return ((_a2 = c.subclass) == null ? void 0 : _a2.slug) === source.slug;
24
+ })) == null ? void 0 : _c.level) != null ? _d : resolved.totalLevel;
25
+ }
26
+ return resolved.totalLevel;
27
+ }
28
+ function resolveRecovery(entry) {
29
+ var _a;
30
+ const kind = (_a = entry.restores) != null ? _a : "uses";
31
+ const restTriggered = (entry.reset === "short-rest" || entry.reset === "long-rest") && !entry.action;
32
+ const flavour = kind === "spell-slots" ? "manual" : restTriggered ? "rest" : "manual";
33
+ return { entry, kind, flavour };
34
+ }
35
+ function toResolvedResource(r, fallbackName, owner) {
36
+ var _a, _b;
37
+ return {
38
+ id: r.id,
39
+ name: (_a = r.name) != null ? _a : fallbackName,
40
+ reset: r.reset,
41
+ maxFormula: r.max_formula,
42
+ ...r.die ? { die: r.die } : {},
43
+ ...r.scales_at ? { scalesAt: r.scales_at } : {},
44
+ ...((_b = r.recovery) == null ? void 0 : _b.length) ? { recovery: r.recovery.map(resolveRecovery) } : {},
45
+ owner
46
+ };
47
+ }
48
+ function resolveFeatureResources(features) {
49
+ var _a, _b;
50
+ const out = /* @__PURE__ */ new Map();
51
+ for (const rf of features) {
52
+ for (const r of (_a = rf.feature.resources) != null ? _a : []) {
53
+ if (out.has(r.id)) continue;
54
+ const owner = { kind: "feature", featureId: (_b = rf.feature.id) != null ? _b : rf.feature.name, featureName: rf.feature.name, source: rf.source };
55
+ out.set(r.id, toResolvedResource(r, rf.feature.name, owner));
56
+ }
57
+ }
58
+ return out;
59
+ }
60
+ function resolveResourceIndex(resolved) {
61
+ var _a, _b, _c, _d, _e;
62
+ const out = new Map(resolveFeatureResources((_a = resolved.features) != null ? _a : []));
63
+ for (const pool of (_b = resolved.pools) != null ? _b : []) {
64
+ const rc = resolved.classes[pool.classIndex];
65
+ const classSlug = (_c = rc == null ? void 0 : rc.entity) == null ? void 0 : _c.slug;
66
+ if (!classSlug) continue;
67
+ for (const entry of [...(_d = pool.selected) != null ? _d : [], ...(_e = pool.grants) != null ? _e : []]) {
68
+ const uses = entry.entity.uses;
69
+ if (!uses || out.has(entry.slug)) continue;
70
+ const maxFormula = String(uses.max);
71
+ if (!isValidMaxFormula(maxFormula)) {
72
+ warnOnce(`pool-uses:${entry.slug}`, `optional feature "${entry.slug}" has a prose uses.max (${JSON.stringify(uses.max)}); no tracker`);
73
+ continue;
74
+ }
75
+ out.set(entry.slug, {
76
+ id: entry.slug,
77
+ name: entry.entity.name,
78
+ reset: uses.recharge,
79
+ maxFormula,
80
+ owner: { kind: "pool", poolId: pool.id, poolLabel: pool.label, source: { kind: "class", slug: classSlug, level: pool.anchorLevel } }
81
+ });
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+ function poolSaveDC(resolved, derived, pool) {
87
+ var _a, _b;
88
+ const rc = resolved.classes[pool.classIndex];
89
+ const ability = (_b = (_a = rc == null ? void 0 : rc.subclass) == null ? void 0 : _a.spellcasting) == null ? void 0 : _b.ability;
90
+ if (!rc || !ability) return null;
91
+ if (resolveSpellcasting(rc) !== null) return null;
92
+ return 8 + derived.proficiencyBonus + derived.mods[ability];
93
+ }
94
+
95
+ export {
96
+ resourceLevelFor,
97
+ resolveRecovery,
98
+ resolveFeatureResources,
99
+ resolveResourceIndex,
100
+ poolSaveDC
101
+ };
@@ -0,0 +1,133 @@
1
+ import {
2
+ MONSTER_KNOWN_KEYS,
3
+ monsterSchema
4
+ } from "./chunk-UQ3ZFCWD.js";
5
+
6
+ // src/monster/monster.parser.ts
7
+ import { parseYaml } from "@archivist-gg/core";
8
+ var VALID_RECHARGE_TYPES = /* @__PURE__ */ new Set([
9
+ "recharge_on_roll",
10
+ "per_day",
11
+ "per_short_rest",
12
+ "per_long_rest"
13
+ ]);
14
+ var NUMERIC_STRING = /^[+-]?\d+(\.\d+)?$/;
15
+ var FEATURE_ARRAYS = ["traits", "actions", "reactions", "legendary_actions", "bonus_actions", "mythic"];
16
+ var SPEED_MODES = ["walk", "fly", "swim", "climb", "burrow"];
17
+ var ABILITY_KEYS = ["str", "dex", "con", "int", "wis", "cha"];
18
+ var NULL_KEPT = /* @__PURE__ */ new Set(["senses", "languages", "group"]);
19
+ function isRecord(v) {
20
+ return typeof v === "object" && v !== null && !Array.isArray(v);
21
+ }
22
+ function coerceNumeric(obj, key) {
23
+ const v = obj[key];
24
+ if (typeof v === "string" && NUMERIC_STRING.test(v.trim())) obj[key] = Number(v);
25
+ }
26
+ function scrubNulls(v, top) {
27
+ if (Array.isArray(v)) {
28
+ for (let i = v.length - 1; i >= 0; i--) {
29
+ if (v[i] === null) v.splice(i, 1);
30
+ else scrubNulls(v[i], false);
31
+ }
32
+ return;
33
+ }
34
+ if (!isRecord(v)) return;
35
+ for (const [k, x] of Object.entries(v)) {
36
+ if (x === null) {
37
+ if (!(top && NULL_KEPT.has(k))) delete v[k];
38
+ } else scrubNulls(x, false);
39
+ }
40
+ }
41
+ function scrubNonFinite(v) {
42
+ if (Array.isArray(v)) {
43
+ for (const x of v) scrubNonFinite(x);
44
+ return;
45
+ }
46
+ if (!isRecord(v)) return;
47
+ for (const [k, x] of Object.entries(v)) {
48
+ if (typeof x === "number" && !Number.isFinite(x)) delete v[k];
49
+ else scrubNonFinite(x);
50
+ }
51
+ }
52
+ function migrateLegacy(raw) {
53
+ scrubNulls(raw, true);
54
+ if (raw.legendary_actions != null && !Array.isArray(raw.legendary_actions) && typeof raw.legendary_actions !== "object") {
55
+ if (raw.legendary_action_uses == null) raw.legendary_action_uses = Number(raw.legendary_actions);
56
+ delete raw.legendary_actions;
57
+ }
58
+ if (!Array.isArray(raw.legendary_actions) && Array.isArray(raw.legendary)) raw.legendary_actions = raw.legendary;
59
+ delete raw.legendary;
60
+ if (typeof raw.ac === "number") raw.ac = [{ ac: raw.ac }];
61
+ if (Array.isArray(raw.ac)) raw.ac = raw.ac.map((e) => typeof e === "number" ? { ac: e } : e);
62
+ if (typeof raw.hp === "number") raw.hp = { average: raw.hp };
63
+ if (typeof raw.cr === "number") raw.cr = String(raw.cr);
64
+ for (const k of ["passive_perception", "legendary_action_uses", "legendary_resistance", "columns", "initiative"]) coerceNumeric(raw, k);
65
+ if (isRecord(raw.initiative)) coerceNumeric(raw.initiative, "proficiency");
66
+ for (const k of ["saves", "skills"]) {
67
+ const o = raw[k];
68
+ if (isRecord(o)) for (const m of Object.keys(o)) coerceNumeric(o, m);
69
+ }
70
+ if (isRecord(raw.hp)) coerceNumeric(raw.hp, "average");
71
+ if (Array.isArray(raw.ac)) {
72
+ for (const e of raw.ac) if (isRecord(e)) coerceNumeric(e, "ac");
73
+ }
74
+ if (isRecord(raw.abilities)) for (const k of ABILITY_KEYS) coerceNumeric(raw.abilities, k);
75
+ if (isRecord(raw.speed)) for (const k of SPEED_MODES) {
76
+ coerceNumeric(raw.speed, k);
77
+ const v = raw.speed[k];
78
+ if (isRecord(v)) coerceNumeric(v, "number");
79
+ }
80
+ scrubNonFinite(raw);
81
+ if (isRecord(raw.abilities)) {
82
+ for (const k of ABILITY_KEYS) if (raw.abilities[k] == null) raw.abilities[k] = 10;
83
+ }
84
+ if (typeof raw.senses === "string") raw.senses = [raw.senses];
85
+ if (typeof raw.languages === "string") raw.languages = [raw.languages];
86
+ for (const k of FEATURE_ARRAYS) {
87
+ const arr = raw[k];
88
+ if (!Array.isArray(arr)) continue;
89
+ for (const f of arr) {
90
+ if (!isRecord(f)) continue;
91
+ if (typeof f.desc === "string" && f.description === void 0 && !Array.isArray(f.entries)) {
92
+ f.description = f.desc;
93
+ delete f.desc;
94
+ }
95
+ const r = f.recharge;
96
+ if (r !== void 0 && !(isRecord(r) && typeof r.param === "number" && VALID_RECHARGE_TYPES.has(r.type))) delete f.recharge;
97
+ }
98
+ }
99
+ }
100
+ function parseMonster(source) {
101
+ var _a, _b;
102
+ const result = parseYaml(source, ["name"]);
103
+ if (!result.success) return result;
104
+ const raw = result.data;
105
+ migrateLegacy(raw);
106
+ const parsed = monsterSchema.safeParse(raw);
107
+ if (!parsed.success) {
108
+ return { success: false, error: `monster schema validation failed: ${parsed.error.message}` };
109
+ }
110
+ const monster = parsed.data;
111
+ const extras = {};
112
+ for (const [k, v] of Object.entries(raw)) if (!MONSTER_KNOWN_KEYS.has(k)) extras[k] = v;
113
+ if (Object.keys(extras).length > 0) monster.raw = { ...(_a = monster.raw) != null ? _a : {}, ...extras };
114
+ if (!monster.legendary_resistance && monster.traits) {
115
+ const lrIndex = monster.traits.findIndex((t) => {
116
+ var _a2;
117
+ return /^Legendary Resistance\s*\(/i.test((_a2 = t.name) != null ? _a2 : "");
118
+ });
119
+ if (lrIndex !== -1) {
120
+ const match = (_b = monster.traits[lrIndex].name) == null ? void 0 : _b.match(/\((\d+)\/Day\)/i);
121
+ if (match) {
122
+ monster.legendary_resistance = parseInt(match[1], 10);
123
+ monster.traits.splice(lrIndex, 1);
124
+ }
125
+ }
126
+ }
127
+ return { success: true, data: monster };
128
+ }
129
+
130
+ export {
131
+ migrateLegacy,
132
+ parseMonster
133
+ };
@@ -1,25 +1,25 @@
1
1
  import {
2
2
  classifyWeaponRange
3
- } from "./chunk-353WXS6S.js";
4
- import {
5
- normKey
6
- } from "./chunk-O5EYMKFH.js";
3
+ } from "./chunk-UIRMA5KM.js";
7
4
  import {
8
5
  defaultSlotForType,
9
6
  effectiveArmor,
10
7
  isItemEntity,
11
8
  isWeaponEntity,
12
9
  resolveEntityForEntry
13
- } from "./chunk-N6N74K6X.js";
10
+ } from "./chunk-UBBKSYBY.js";
11
+ import {
12
+ normKey
13
+ } from "./chunk-RLGQ5PZZ.js";
14
14
  import {
15
15
  readNumericBonus
16
16
  } from "./chunk-LWFTHJIE.js";
17
17
  import {
18
18
  resolveBaseItem
19
- } from "./chunk-4LZJXJJS.js";
19
+ } from "./chunk-WXWPK3LI.js";
20
20
  import {
21
21
  bareEntitySlug
22
- } from "./chunk-LV4SNWZQ.js";
22
+ } from "./chunk-6X4KUDMU.js";
23
23
 
24
24
  // src/weapon/weapon-mastery.ts
25
25
  var MASTERY = {
@@ -405,7 +405,7 @@ function magicBonusesForWeaponEntry(entry, registry, ctx) {
405
405
  itemRiders = Array.isArray(entity.damage_riders) ? entity.damage_riders.filter((r) => {
406
406
  var _a2;
407
407
  return ((_a2 = r.applies_to) != null ? _a2 : "weapon") !== "spell";
408
- }).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 } : {} })) : [];
409
409
  }
410
410
  return {
411
411
  atk: itemAttack + entryAttack,
@@ -482,6 +482,40 @@ function buildAttackRow(args) {
482
482
  }
483
483
  return row;
484
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
+ }
485
519
  function humanizeWeaponToken(s) {
486
520
  return s.replace(/[-_]/g, " ").replace(/(^|[\s(])\w/g, (c) => c.toUpperCase());
487
521
  }
@@ -571,7 +605,7 @@ function computeCarriedWeight(resolved, registry) {
571
605
  }
572
606
  return total;
573
607
  }
574
- function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, proficiencyBonus, weaponAbilities = [], weaponMasteries = []) {
608
+ function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, proficiencyBonus, weaponAbilities = [], weaponMasteries = [], attunementFloor = 0) {
575
609
  var _a, _b, _c;
576
610
  const equippedSlots = assignSlots(resolved, registry, warnings);
577
611
  const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
@@ -587,12 +621,16 @@ function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, profi
587
621
  carriedWeight: computeCarriedWeight(resolved, registry),
588
622
  // Attunement is persistent: an attuned item still occupies a slot even when unequipped (SRD).
589
623
  attunementUsed: ((_b = resolved.definition.equipment) != null ? _b : []).filter((e) => e.attuned).length,
590
- attunementLimit: (_c = overrides.attunement_limit) != null ? _c : 3
624
+ // PRECEDENCE: an explicit character override wins outright (a number the player typed is intent);
625
+ // otherwise the baseline 3 is raised — never lowered — by whatever features granted.
626
+ attunementLimit: (_c = overrides.attunement_limit) != null ? _c : Math.max(3, attunementFloor)
591
627
  };
592
628
  }
593
629
 
594
630
  export {
595
631
  emptyAppliedBonuses,
596
632
  computeAppliedBonuses,
633
+ isWeaponSlugProficient,
634
+ buildUnarmedRow,
597
635
  computeSlotsAndAttacks
598
636
  };