@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
@@ -3,24 +3,40 @@ import {
3
3
  buildDecisionLedger,
4
4
  collectChosenAbilityPoints,
5
5
  collectChosenProficiencies,
6
- collectLanguageToolChoiceStatus,
6
+ computeEffectiveProficiencies
7
+ } from "../chunk-2VDTW7QO.js";
8
+ import "../chunk-UXPGW4TI.js";
9
+ import "../chunk-DPUJDOKV.js";
10
+ import "../chunk-O4SRD3J5.js";
11
+ import "../chunk-PP2UHLBA.js";
12
+ import "../chunk-IUAAXXNO.js";
13
+ import "../chunk-YHYTW5QA.js";
14
+ import "../chunk-MI7P3NX3.js";
15
+ import "../chunk-PVCEA4HZ.js";
16
+ import "../chunk-UBBKSYBY.js";
17
+ import "../chunk-KPEJMNRD.js";
18
+ import "../chunk-TOM66P2P.js";
19
+ import "../chunk-KU3YLF6D.js";
20
+ import "../chunk-RLGQ5PZZ.js";
21
+ import "../chunk-LVQLWRCU.js";
22
+ import "../chunk-WXWPK3LI.js";
23
+ import {
7
24
  wikilinkTailSlug
8
- } from "../chunk-TNUFPIXI.js";
9
- import "../chunk-7TEK5KK7.js";
10
- import "../chunk-6IRYTM5V.js";
11
- import "../chunk-5XUXXAQJ.js";
12
- import "../chunk-JEYQU3JT.js";
13
- import "../chunk-N6N74K6X.js";
14
- import "../chunk-4LZJXJJS.js";
15
- import "../chunk-LV4SNWZQ.js";
25
+ } from "../chunk-6X4KUDMU.js";
26
+ import "../chunk-YBLDE5CG.js";
27
+ import "../chunk-EVMUJYUD.js";
16
28
  import "../chunk-VTTA2ALZ.js";
17
- import "../chunk-NAEPSZAM.js";
18
- import "../chunk-WP2CX2JO.js";
29
+ import "../chunk-OZP6JFGT.js";
30
+ import "../chunk-VQAXTIBW.js";
31
+ import "../chunk-PDJSUH24.js";
32
+ import "../chunk-WELIKLZF.js";
33
+ import "../chunk-4U55VNC7.js";
34
+ import "../chunk-UA5KS2PL.js";
19
35
  export {
20
36
  __matchesFilterForTest,
21
37
  buildDecisionLedger,
22
38
  collectChosenAbilityPoints,
23
39
  collectChosenProficiencies,
24
- collectLanguageToolChoiceStatus,
40
+ computeEffectiveProficiencies,
25
41
  wikilinkTailSlug
26
42
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Canonical key for a defense value (damage type or condition).
3
+ *
4
+ * Deliberately SIMPLER than `toProfSlug` (pc.proficiency-normalize.ts): damage types and conditions carry
5
+ * no apostrophes and no hyphens, so there is no U+2019 fold and no space-to-hyphen step. Do NOT reuse
6
+ * `normKey` · it token-sorts and singularizes for weapon display names and is actively wrong here.
7
+ *
8
+ * The whole point of this function is that ONE normalizer serves every reader and writer on the defenses
9
+ * path. Adding a second is the bug R4-P5 exists to remove.
10
+ */
11
+ export declare function toDefenseSlug(s: string): string;
@@ -0,0 +1,6 @@
1
+ import {
2
+ toDefenseSlug
3
+ } from "../chunk-XJ2F2WO6.js";
4
+ export {
5
+ toDefenseSlug
6
+ };
@@ -1,6 +1,7 @@
1
1
  import type { EntityRegistry } from "@archivist-gg/core";
2
2
  import type { Ability } from "@archivist-gg/dnd5e";
3
- import type { AppliedBonuses, DerivedEquipment, ResolvedCharacter, ProficiencySet } from "./pc.types.js";
3
+ import type { WeaponEntity } from "@archivist-gg/dnd5e/weapon/weapon.types";
4
+ import type { AppliedBonuses, AttackRow, DerivedEquipment, ResolvedCharacter, ProficiencySet } from "./pc.types.js";
4
5
  import type { WeaponAbilityOverride } from "./pc.feature-effects.js";
5
6
  interface ProficienciesForQuery {
6
7
  armor: ProficiencySet;
@@ -9,5 +10,33 @@ interface ProficienciesForQuery {
9
10
  }
10
11
  export declare function emptyAppliedBonuses(): AppliedBonuses;
11
12
  export declare function computeAppliedBonuses(resolved: ResolvedCharacter, _profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[]): AppliedBonuses;
12
- export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number, weaponAbilities?: WeaponAbilityOverride[], weaponMasteries?: string[]): DerivedEquipment;
13
+ /** The LIVE weapon-proficiency gate · the one the attack pipeline calls at
14
+ * `computeAttacks` below. Exported for the overlay effect-slug guard
15
+ * (`tests/srd-canonical/overlay-effect-slugs.test.ts`), which validates
16
+ * authored `proficiency_type: weapon` values by asking this function whether
17
+ * any SRD weapon comes out proficient. Note `pc.proficiency-query.ts`'s
18
+ * `isProficientWithWeapon` is a near-twin with NO production callers · it
19
+ * differs on the third arm (it compares only the base category, this one also
20
+ * accepts the full hyphenated form), so guards wanting runtime fidelity must
21
+ * use THIS one. */
22
+ export declare function isWeaponSlugProficient(weapon: WeaponEntity, weaponSlug: string, profs: ProficienciesForQuery): boolean;
23
+ /** The die and ability set an `unarmed-strike` effect (or its synthetic) resolved for this character (R4-G6b §5.1). */
24
+ export interface UnarmedStrikeSpec {
25
+ dice?: string;
26
+ abilities?: Ability[];
27
+ }
28
+ /** The always-present Unarmed Strike row (R4-G6b §5.1): 1 + the modifier bludgeoning, 5 ft, always proficient. With a
29
+ * DIE the damage string reproduces the OUTPUT of `buildAttackRow`'s `formatDice` (a closure over `dmgFlat` that cannot
30
+ * be called): the flat part's `^\+0$` strip equals the whole-string `\+0$` strip for every modifier, so a die reads
31
+ * `1d6-1`. With NO die the base is the number 1, and a signed suffix on a number is unevaluated arithmetic (`1-1`,
32
+ * `1+3`), so the flat base prints EVALUATED and floored at 0: `String(Math.max(0, 1 + mod))` (R4-G7 T8 RIDER-10; the
33
+ * user's Q-3 "1 + STR" is the formula, the evaluated number its display). The
34
+ * ability is STR unless the spec lists others; the highest modifier wins and a LATER candidate wins a tie, so a
35
+ * listed DEX beats STR on a tie (the shipped finesse idiom `mods.dex >= mods.str`). Called from recalc, which owns
36
+ * the walk that produces `spec`; never from `computeAttacks`, so the row is present with or without a registry.
37
+ * The `subLabel` is an ENGINE string, printed as it ships: the sheet humanizes a weapon's category tokens, and
38
+ * this row carries no weapon, so `Unarmed` is capitalised here beside `Unarmed Strike`, `5 ft` and `bludgeoning`
39
+ * rather than in a renderer (R4-G6b live rider R-1). */
40
+ export declare function buildUnarmedRow(mods: Record<Ability, number>, proficiencyBonus: number, spec?: UnarmedStrikeSpec): AttackRow;
41
+ export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number, weaponAbilities?: WeaponAbilityOverride[], weaponMasteries?: string[], attunementFloor?: number): DerivedEquipment;
13
42
  export {};
@@ -1,17 +1,21 @@
1
1
  import {
2
+ buildUnarmedRow,
2
3
  computeAppliedBonuses,
3
4
  computeSlotsAndAttacks,
4
- emptyAppliedBonuses
5
- } from "../chunk-D3CLOBE3.js";
6
- import "../chunk-353WXS6S.js";
7
- import "../chunk-O5EYMKFH.js";
8
- import "../chunk-N6N74K6X.js";
5
+ emptyAppliedBonuses,
6
+ isWeaponSlugProficient
7
+ } from "../chunk-V373TRJE.js";
8
+ import "../chunk-UIRMA5KM.js";
9
+ import "../chunk-UBBKSYBY.js";
10
+ import "../chunk-RLGQ5PZZ.js";
9
11
  import "../chunk-LWFTHJIE.js";
10
12
  import "../chunk-DNAJRMNN.js";
11
- import "../chunk-4LZJXJJS.js";
12
- import "../chunk-LV4SNWZQ.js";
13
+ import "../chunk-WXWPK3LI.js";
14
+ import "../chunk-6X4KUDMU.js";
13
15
  export {
16
+ buildUnarmedRow,
14
17
  computeAppliedBonuses,
15
18
  computeSlotsAndAttacks,
16
- emptyAppliedBonuses
19
+ emptyAppliedBonuses,
20
+ isWeaponSlugProficient
17
21
  };
@@ -1,6 +1,6 @@
1
- import type { SenseType } from "@archivist-gg/dnd5e/types/feature-effect";
1
+ import type { FeatureEffect, SenseType } from "@archivist-gg/dnd5e/types/feature-effect";
2
2
  import type { Ability } from "@archivist-gg/dnd5e";
3
- import type { DamageRider, ResolvedFeature, RollModifierEntry } from "./pc.types.js";
3
+ import type { DamageRider, DefenseGrant, FeatureSource, ResolvedCharacter, ResolvedFeature, RollModifierEntry, SaveOutcomeEntry } from "./pc.types.js";
4
4
  /**
5
5
  * A melee-attack ability override from a `weapon-ability` effect. `weaponSlugs`
6
6
  * (bare, namespace-stripped) scopes the override to matching weapon types only;
@@ -35,27 +35,93 @@ export interface FeatureEffectTotals {
35
35
  * a "base speed becomes 60" feature). Max across all set effects; 0 = none.
36
36
  * recalc applies it as Math.max(set, race + additive bonuses) so it never
37
37
  * lowers an already-higher speed and is independent of the additive bonus.
38
+ * A `scales_at` progression resolves BEFORE the max, at the effect's own source level (R4-G7 §7.3).
38
39
  */
39
40
  speed_walk_set: number;
41
+ /**
42
+ * Absolute attunement CAP granted by `attunement-limit` effects (the Artificer's Magic Item
43
+ * Adept / Savant / Master line). Max across all grants; 0 = nothing granted it. recalc applies it
44
+ * as Math.max(3, attunement_set) so a grant can never LOWER the baseline, and an explicit
45
+ * `overrides.attunement_limit` on the character still wins outright — a number the player typed is
46
+ * intent, not a derived value. A `scales_at` progression resolves BEFORE the max, at the effect's
47
+ * own source level (R4-G7 §7.3).
48
+ */
49
+ attunement_set: number;
50
+ /** Flat ability-score bumps from `ability-score-increase` effects whose `abilities` is a FIXED LIST (the three level-20
51
+ * capstones). `chosen` arms are the ASI SLOT's second encoding and MUST NOT fold (the synthesized feat decision pays
52
+ * them; a flat fold double-counts). No cap: `max` is declared and unread (user ruling 2026-09-03). */
53
+ ability_bonus: Partial<Record<Ability, number>>;
40
54
  /** Max range per sense type granted by effects; 0 = none for that type. */
41
55
  senses: Record<SenseType, number>;
42
- /** One term per ac-bonus effect, labeled with the owning feature's name. */
56
+ /** One term per ac-bonus effect, labeled with the owning feature's name. `condition` is the
57
+ * effect's situational qualifier, carried verbatim onto the ACTerm and never evaluated. */
43
58
  ac_terms: {
44
59
  value: number;
45
60
  requires_armor: boolean;
46
61
  label: string;
62
+ condition?: string;
47
63
  }[];
48
- resistances: string[];
49
- condition_immunities: string[];
50
64
  /**
51
- * skills are kebab-case lowercase slugs (matching skill slugs). armor/weapons
52
- * are lowercase CATEGORY words ("heavy"/"shield", "simple"/"martial") — the
53
- * same form class/race/feat grants use; recalc folds them into the matcher's
54
- * `.categories` bucket, NOT `.specific` (which is per-item slugs). tools/languages
55
- * keep their display spelling; saves are canonical ability keys.
65
+ * The four defense buckets a feature effect can grant, as DefenseGrant ENTRIES (R4-G3a §3.2.1).
66
+ * Deduped by value case-insensitively, first spelling wins, trimmed · exactly the contract
67
+ * `pushUnique` had when these were `string[]` · but the SECOND granting feature's NAME is merged
68
+ * into `sources` rather than discarded. `immunity` and `vulnerability` fold REGARDLESS of
69
+ * `condition` (following `resistance`: dropping a real immunity is worse than showing it
70
+ * unqualified); `immune-condition` keeps its `while` skip. Values stay in the AUTHORED spelling ·
71
+ * toDefenseSlug runs in pc.recalc.ts, where composeDefenseEntries turns these into DefenseEntry.
72
+ */
73
+ resistances: DefenseGrant[];
74
+ immunities: DefenseGrant[];
75
+ vulnerabilities: DefenseGrant[];
76
+ condition_immunities: DefenseGrant[];
77
+ /**
78
+ * skills, tools and languages are canonical slugs (toProfSlug: lowercase, U+2019
79
+ * folded to ASCII, whitespace collapsed to hyphens). armor and weapons entries
80
+ * are only `.toLowerCase()`d, NOT slugified, so an authored value keeps its
81
+ * spaces. recalc folds armor into armor.categories ONLY; it folds every weapons
82
+ * value into weapons.categories and additionally routes a non-category value
83
+ * into weapons.specific. saves are canonical ability keys, and are the one
84
+ * bucket a closed vocabulary genuinely guarantees: normalizeAbility returns null
85
+ * for anything else and the effect is dropped.
86
+ *
87
+ * What is INTENDED and what is ENFORCED differ here, and the gap is silent.
88
+ * INTENDED: skills/tools/languages land on ALL_SKILL_SLUGS / ALL_TOOLS /
89
+ * ALL_LANGUAGES, over which toProfSlug is the identity; armor values are
90
+ * CATEGORY words ("heavy"/"shield"); weapons values are a category word
91
+ * ("simple"/"martial", or an entity-level "martial-melee" form) or an authored
92
+ * weapon NAME. ENFORCED at runtime: nothing. feature-effect-schema's proficiency
93
+ * arm is `value: z.string().min(1)`, an OPEN string, and classifyProficiencyEffect
94
+ * does no membership test. The only guard is
95
+ * tests/srd-canonical/overlay-effect-slugs.test.ts, and it sees OVERLAY-AUTHORED
96
+ * data only · there skill/tool/language are checked against those three lists and
97
+ * weapon against the live gate, while armor and saving-throw are unchecked even
98
+ * there. Vault homebrew reaching this at runtime is checked by nothing.
99
+ *
100
+ * An off-vocabulary value does not crash and does not disappear · it becomes a
101
+ * value that can never coincide with the canonical one. Measured: an authored
102
+ * `{proficiency_type:"tool", value:"Playing Card Set"}` normalizes to
103
+ * "playing-card-set", which is NOT in ALL_TOOLS (that list carries the 2024
104
+ * spelling "playing-cards"). It still folds, and still renders as its own row
105
+ * labeled "Playing Card Set", but everything keyed on the canonical slug · the
106
+ * first-seen-wins dedupe in computeEffectiveProficiencies, an
107
+ * `overrides.tools.remove` suppression, the builder's already-satisfied
108
+ * exclusion · misses it, so the character ends up able to hold both.
109
+ *
110
+ * `skillExpertise` is NOT a seventh bucket · it is a SUBSET of `skills`, in the
111
+ * same canonical slug form, carrying the values whose effect set
112
+ * `expertise: true`. An expertise skill is pushed to BOTH, so every consumer
113
+ * that only knows about proficiency keeps working and recalc's `expSet` gains
114
+ * one source. SKILLS ONLY (spec §7.2): a tool or language effect carrying
115
+ * `expertise: true` folds as a plain proficiency and never lands here. It is
116
+ * not lost · since R4-G4 §9.2 the authored flag travels the GRANT channel
117
+ * instead (`EffectProficiencyGrant.expertise` -> `ProficiencyGrant.expertise`
118
+ * -> `ProficiencyEntry.expertise`), which is the channel the sheet panel and
119
+ * the proficiency modal read. Routing it here as well would put a value in the
120
+ * skill-expertise set that no skill key can ever match.
56
121
  */
57
122
  proficiencies: {
58
123
  skills: string[];
124
+ skillExpertise: string[];
59
125
  tools: string[];
60
126
  languages: string[];
61
127
  saves: Ability[];
@@ -78,6 +144,12 @@ export interface FeatureEffectTotals {
78
144
  * labeled with the owning feature's name.
79
145
  */
80
146
  rollModifiers: RollModifierEntry[];
147
+ /**
148
+ * Order-preserving list of `save-outcome` entries (R4-G3a §5.3), labeled with the owning
149
+ * feature's name. Pass-through (no dedupe/merge); `ability: "any"` becomes an ABSENT ability,
150
+ * which the save chip reads as "every save".
151
+ */
152
+ saveOutcomes: SaveOutcomeEntry[];
81
153
  /**
82
154
  * Lowest weapon-attack crit threshold (natural roll that scores a critical
83
155
  * hit) granted by `crit-range` effects. Folds via Math.min from init 20, so
@@ -86,10 +158,11 @@ export interface FeatureEffectTotals {
86
158
  */
87
159
  critRange: number;
88
160
  /**
89
- * Max extra attacks per Attack action granted by `extra-attack` effects.
161
+ * Max EXTRA attacks per Attack action granted by `extra-attack` effects, never the total:
90
162
  * Non-stacking (D&D Extra Attack features don't stack): folds via Math.max
91
163
  * from init 0, so 0 = no extra attacks. recalc maps this onto
92
164
  * DerivedStats.attacksPerAction as `1 + extraAttack`.
165
+ * A `scales_at` progression resolves BEFORE the max, at the effect's own source level (R4-G7 §7.3).
93
166
  */
94
167
  extraAttack: number;
95
168
  /**
@@ -115,8 +188,158 @@ export declare function emptyFeatureEffectTotals(): FeatureEffectTotals;
115
188
  * is OFF by default, so callers passing no opts see activatable features fold to
116
189
  * nothing (correct — a buff is off until toggled). Non-activatable features fold
117
190
  * unconditionally regardless of opts.
191
+ *
192
+ * R4-G7 §7.3: `levelFor` answers "at what level does an effect from THIS source resolve", which is the rule
193
+ * `resourceLevelFor` (pc.resources.ts) already applies to a resource's own `scales_at`: the character's level
194
+ * in the granting class for a `class` source, the level in the class owning the subclass for a `subclass`
195
+ * source, the total level otherwise. Both production callers pass exactly that (dnd5e `pc.recalc.ts` and the
196
+ * plugin's builder `abilities-step.ts`); a caller that passes none leaves every `scales_at` unresolved and
197
+ * every effect at its BASE value, which is what a fixture with no character behind it should read.
118
198
  */
119
199
  export interface FeatureEffectsOpts {
120
200
  activeBuffs?: Set<string>;
201
+ levelFor?: (source: FeatureSource) => number;
121
202
  }
203
+ /** Does this feature's effects fold right now? An activatable feature folds ONLY
204
+ * while its id is in the active set; a buff is off until toggled. Shared by
205
+ * computeFeatureEffects and the display-side proficiency collector so the fold
206
+ * and the display can never disagree about boons (spec fence F3). */
207
+ export declare function foldsNow(rf: ResolvedFeature, activeBuffs: Set<string>): boolean;
208
+ /** The ONE assembly of everything whose effects fold: authored features plus
209
+ * pool-granted and pool-selected boons, with the active-buff set.
210
+ *
211
+ * Returning BOTH halves is load-bearing: a caller handed only the feature list
212
+ * would drop activeBuffs and computeFeatureEffects would skip every activatable
213
+ * boon · exactly the display/gate divergence this helper exists to close.
214
+ *
215
+ * The guards defend against CAST-BUILT FIXTURES, not against the type: `features`,
216
+ * `pools` and `state` are all non-optional on ResolvedCharacter, but `tests/` is
217
+ * typechecked by nothing. Only fixtures that actually reach this function count,
218
+ * and today that is THREE entry points: recalc, the display-side walk
219
+ * (computeEffectiveProficiencies → collectProficiencyGrants), and this
220
+ * function's own direct tests. All three guards are LIVE. Counts below were
221
+ * measured by deleting each guard and running the suite · re-derive them the
222
+ * same way rather than trusting the numbers, which drift as tests are added:
223
+ * · `pools ?? []` is exercised by `emptyResolved()` in
224
+ * tests/pc-recalc-feature-effects.test.ts, which omits pools and IS fed to
225
+ * recalc, and by `dwarf()` in tests/pc-proficiency-effective.test.ts. This
226
+ * guard predates the extraction; it was already here. Deleting it: 149 tests
227
+ * red across 13 files.
228
+ * · `state?.` is exercised by the "survives a cast-built fixture with no pools
229
+ * and no state" case in tests/pc-feature-effects.test.ts, which calls this
230
+ * function directly with `{ features: [...] } as never`, and again by
231
+ * `dwarf()`. Deleting it: 11 red across 2 files.
232
+ * · `features ?? []` is exercised by `dwarf()` in
233
+ * tests/pc-proficiency-effective.test.ts, which omits `features` and reaches
234
+ * here through the display walk. Deleting it: 7 red, all in that file.
235
+ * ⚠️ This bullet used to read "NOT exercised today, and is the one kept
236
+ * purely for the display-side callers". That was TRUE when written and was
237
+ * falsified LATER IN THIS SAME PHASE by the tasks that wired the display
238
+ * path, because nobody re-read it. A guard's own justification is the thing
239
+ * most likely to go stale under you · re-run the deletion before restating
240
+ * any of these three.
241
+ * Do not "simplify" any of the three away. */
242
+ export declare function assembleEffectFeatures(resolved: ResolvedCharacter): {
243
+ features: ResolvedFeature[];
244
+ activeBuffs: Set<string>;
245
+ };
246
+ /** Spec R4-G1a D2. Whether an effect changes the character it is written on. `subject` is absent on every SRD
247
+ * effect (=== "self"); the converter emits "self" today and will emit other creatures later (Hound of Ill Omen
248
+ * imposes disadvantage on a TARGET). A non-self effect never folds onto the PC: not here, not in
249
+ * collectProficiencyEffectGrants, not in unarmoredACBreakdown, not in recalc's weapon-ability scan. Its FEATURE
250
+ * still resolves and renders (it is not buildOnly); R4-G3a §4 gives it a row-local caption.
251
+ *
252
+ * R4-G3a §4.4: a subject that is neither absent nor "self" warns ONCE per distinct subject string
253
+ * (`warnOnce`, keyed on the subject). The converter's vocabulary here is open, so an unexpected
254
+ * spelling ("Self", "target creature") silently stops folding today; the warning names it without
255
+ * changing the answer. Never a refusal: the return value is unchanged. */
256
+ export declare function foldsOnSelf(eff: {
257
+ subject?: string;
258
+ }): boolean;
259
+ /** The effects of a feature that fold on the character: every reader of `feature.effects` for a derived stat goes through this. */
260
+ export declare function selfEffectsOf(f: {
261
+ effects?: FeatureEffect[];
262
+ }): FeatureEffect[];
122
263
  export declare function computeFeatureEffects(features: ResolvedFeature[], opts?: FeatureEffectsOpts): FeatureEffectTotals;
264
+ /** The ONE interpreter of `kind: "proficiency"` effects (spec fence F3).
265
+ *
266
+ * Two consumers with different needs, so the return carries BOTH forms:
267
+ * `applyEffect` pushes `value` (normalized) into FeatureEffectTotals, while the
268
+ * display collector reads `raw` for armor/weapons · composeGrantEntries stores
269
+ * the raw authored string as ProficiencyEntry.value (spec §4.8 #1) and computes
270
+ * its dedupe key separately. Tools/languages read `value`.
271
+ *
272
+ * `saves` is discriminated because FeatureEffectTotals.proficiencies.saves is
273
+ * Ability[], not string[] (fence F1). A null return means "not a proficiency
274
+ * effect, or an unresolvable ability".
275
+ *
276
+ * `expertise` is REQUIRED and always a boolean, never absent: the authored key
277
+ * is optional, so folding it here once means no consumer has to tell
278
+ * `undefined` from `false`. It is reported for EVERY bucket · deciding that
279
+ * only skills act on it belongs to the fold (spec §7.2), not to the
280
+ * classification. */
281
+ export type ProficiencyClassification = {
282
+ bucket: "skills" | "tools" | "languages" | "armor" | "weapons";
283
+ value: string;
284
+ raw: string;
285
+ expertise: boolean;
286
+ } | {
287
+ bucket: "saves";
288
+ value: Ability;
289
+ raw: string;
290
+ expertise: boolean;
291
+ };
292
+ export declare function classifyProficiencyEffect(eff: FeatureEffect): ProficiencyClassification | null;
293
+ /** One proficiency an effect grants, tagged with the source it came from.
294
+ *
295
+ * BOTH forms are carried because the two display consumers need different ones
296
+ * and, since the tool/language canonicalization landed, they no longer coincide:
297
+ * armor/weapons read `raw` (composeGrantEntries stores the raw authored string
298
+ * as ProficiencyEntry.value and keys its dedupe on toProfSlug separately), while
299
+ * tools/languages read `value`, the canonical slug the effective-proficiency
300
+ * matcher matches its vocabulary and suppressions on. Reaching for the wrong
301
+ * field is silently wrong, not harmlessly identical.
302
+ *
303
+ * `sourceSlug` is the source's slug VERBATIM (namespace and all), because the
304
+ * consumer resolves it against the resolved character's entities to get a
305
+ * display name. */
306
+ export interface EffectProficiencyGrant {
307
+ value: string;
308
+ raw: string;
309
+ sourceKind: FeatureSource["kind"];
310
+ sourceSlug: string;
311
+ /**
312
+ * Set (to `true`) only when the granting effect carried `expertise: true`, and
313
+ * ABSENT otherwise, so a plain grant keeps the exact four-key shape the two
314
+ * display consumers already read. Reported for every bucket, unlike the fold,
315
+ * which routes skills only (spec §7.2). CONSUMED since R4-G4 §9.2 by
316
+ * `toGrants` in pc.proficiency-grants.ts, which spreads it onto
317
+ * `ProficiencyGrant` for every bucket; the tools display is the one reader
318
+ * today, and it reads this rather than re-deriving it from the raw effects.
319
+ */
320
+ expertise?: boolean;
321
+ }
322
+ export type EffectProficiencyGrants = Record<"skills" | "tools" | "languages" | "saves" | "armor" | "weapons", EffectProficiencyGrant[]>;
323
+ /** Every proficiency an effect grants, tagged with the FeatureSource it came from.
324
+ *
325
+ * Deliberately NOT deduped: composeGrantEntries collects distinct SOURCES per
326
+ * value, so collapsing on value alone here would silently drop the second
327
+ * granting entity and blank half the provenance.
328
+ *
329
+ * `skills` and `saves` are collected because classifyProficiencyEffect's bucket
330
+ * union has SIX members and this function indexes the record by it · drop either
331
+ * key and `out[c.bucket].push(...)` is a type error, and a hand-built record
332
+ * missing one crashes on the first such effect. They have NO display consumer:
333
+ * effect-granted skills already reach the sheet via recalc's skill union and
334
+ * saves via its save set, and the Proficiencies panel has only four rows. No
335
+ * test asserts either bucket, and `toGrants` in pc.proficiency-grants.ts reads
336
+ * only the other four. That is deliberate, not an oversight.
337
+ *
338
+ * Reads the same three rules as the fold, and NOTHING else: foldsNow decides
339
+ * what is active, selfEffectsOf decides whose numbers an effect changes and
340
+ * classifyProficiencyEffect decides what a proficiency effect means (spec fence
341
+ * F3), so the display can never disagree with the engine.
342
+ *
343
+ * Reads only effects that fold on self (`selfEffectsOf`), the same predicate as
344
+ * the fold, so a non-self proficiency is neither granted nor displayed. */
345
+ export declare function collectProficiencyEffectGrants(features: ResolvedFeature[], activeBuffs: Set<string>): EffectProficiencyGrants;
@@ -1,10 +1,26 @@
1
1
  import {
2
+ assembleEffectFeatures,
3
+ classifyProficiencyEffect,
4
+ collectProficiencyEffectGrants,
2
5
  computeFeatureEffects,
3
- emptyFeatureEffectTotals
4
- } from "../chunk-AIRFC5NQ.js";
5
- import "../chunk-LV4SNWZQ.js";
6
- import "../chunk-WP2CX2JO.js";
6
+ emptyFeatureEffectTotals,
7
+ foldsNow,
8
+ foldsOnSelf,
9
+ selfEffectsOf
10
+ } from "../chunk-KPEJMNRD.js";
11
+ import "../chunk-TOM66P2P.js";
12
+ import "../chunk-KU3YLF6D.js";
13
+ import "../chunk-RLGQ5PZZ.js";
14
+ import "../chunk-LVQLWRCU.js";
15
+ import "../chunk-6X4KUDMU.js";
16
+ import "../chunk-WELIKLZF.js";
7
17
  export {
18
+ assembleEffectFeatures,
19
+ classifyProficiencyEffect,
20
+ collectProficiencyEffectGrants,
8
21
  computeFeatureEffects,
9
- emptyFeatureEffectTotals
22
+ emptyFeatureEffectTotals,
23
+ foldsNow,
24
+ foldsOnSelf,
25
+ selfEffectsOf
10
26
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  characterSchema
3
- } from "../chunk-TN6ZLTNU.js";
4
- import "../chunk-TZK36BU7.js";
3
+ } from "../chunk-I2NDWFDD.js";
4
+ import "../chunk-S7REMWW4.js";
5
5
 
6
6
  // src/pc/pc.parser.ts
7
7
  import * as yaml from "js-yaml";
@@ -1,12 +1,20 @@
1
1
  import type { RegisteredEntity } from "@archivist-gg/core";
2
- import type { ResolvedCharacter, ResolvedClass, ResolvedPool } from "./pc.types.js";
2
+ import type { ResolvedCharacter, ResolvedClass, ResolvedPool, ResolvedPoolEntry } from "./pc.types.js";
3
3
  import type { SelectionPool } from "@archivist-gg/dnd5e/types/selection-pool";
4
4
  import type { OptionalFeatureEntity } from "@archivist-gg/dnd5e/types/optional-feature.types";
5
+ import type { ResourceIndex } from "./pc.resources.js";
5
6
  /** Structural subset of EntityRegistry used here (lets tests pass a fake). */
6
7
  export interface PoolRegistry {
7
8
  search(query: string, entityType: string, limit: number): RegisteredEntity[];
8
9
  getByTypeAndSlug(entityType: string, slug: string): RegisteredEntity | undefined;
9
10
  }
10
11
  export declare function levelPrereqMax(d: OptionalFeatureEntity): number;
11
- export declare function resolvePool(rc: ResolvedClass, classIndex: number, pool: SelectionPool, registry: PoolRegistry): ResolvedPool;
12
- export declare function resolveAllPools(resolved: ResolvedCharacter, registry: PoolRegistry): ResolvedPool[];
12
+ export declare function resolvePool(rc: ResolvedClass, classIndex: number, pool: SelectionPool, registry: PoolRegistry, index: ResourceIndex): ResolvedPool;
13
+ export declare function resolveAllPools(resolved: ResolvedCharacter, registry: PoolRegistry, index: ResourceIndex): ResolvedPool[];
14
+ /** The stored picks that survived resolution but are NOT in the collapsed `available` list, compared by FULL
15
+ * slug: exactly the rule the plugin's `strandedSelections` used before T3 retires it (R4-G5 §3.2.3). Because
16
+ * §9.2.3's resolution rewrites a collapsed twin to its survivor, only a GENUINE prerequisite-failing pick (the
17
+ * one resolved by step 3 from the full scan) can land here, and it keeps its entity so the builder strip and the
18
+ * sheet can both dress it "prerequisite unmet". A granted feature is already excluded from `selected`, so a
19
+ * grant never appears here. Pure over the resolved pool: it reads nothing the resolver did not compute. */
20
+ export declare function strandedPicks(pool: ResolvedPool): ResolvedPoolEntry[];
@@ -1,20 +1,16 @@
1
1
  import {
2
2
  levelPrereqMax,
3
3
  resolveAllPools,
4
- resolvePool
5
- } from "../chunk-TNUFPIXI.js";
6
- import "../chunk-7TEK5KK7.js";
7
- import "../chunk-6IRYTM5V.js";
8
- import "../chunk-5XUXXAQJ.js";
9
- import "../chunk-JEYQU3JT.js";
10
- import "../chunk-N6N74K6X.js";
11
- import "../chunk-4LZJXJJS.js";
12
- import "../chunk-LV4SNWZQ.js";
13
- import "../chunk-VTTA2ALZ.js";
14
- import "../chunk-NAEPSZAM.js";
15
- import "../chunk-WP2CX2JO.js";
4
+ resolvePool,
5
+ strandedPicks
6
+ } from "../chunk-O4SRD3J5.js";
7
+ import "../chunk-PP2UHLBA.js";
8
+ import "../chunk-IUAAXXNO.js";
9
+ import "../chunk-LVQLWRCU.js";
10
+ import "../chunk-6X4KUDMU.js";
16
11
  export {
17
12
  levelPrereqMax,
18
13
  resolveAllPools,
19
- resolvePool
14
+ resolvePool,
15
+ strandedPicks
20
16
  };
@@ -1,39 +1,64 @@
1
1
  import type { ResolvedCharacter } from "./pc.types.js";
2
- import { type ChoiceStatus } from "./pc.decision-engine.js";
2
+ import { type ProficiencyEntry, type ProficiencyGrant } from "./pc.proficiency-grants.js";
3
+ /** Re-exported for the plugin: both types are DECLARED in the leaf so that
4
+ * pc.decision-engine can reference them without importing this module, which
5
+ * would close a cycle (spec §4.3). */
6
+ export type { ProficiencyEntry, ProficiencyOrigin } from "./pc.proficiency-grants.js";
7
+ /** Every bucket carries provenance (spec §7.1). `languages`/`tools` ARE
8
+ * {@link computeEffectiveProficiencies}' output, unmodified · `armor`/`weapons`
9
+ * are composed below from the raw grant walk, because they have no vocabulary
10
+ * constant to canonicalize against (spec §3.3's second rule) · and for `weapons`
11
+ * that grant walk is followed by the chosen `select-entity{weapon}` picks
12
+ * (R4-G3b §7 F5), which land with origin `pick`. */
3
13
  export interface ProficiencyAggregate {
4
- armor: string[];
5
- weapons: string[];
6
- tools: string[];
7
- languages: string[];
8
- /** Unresolved language/tool decisions as "choose N" placeholders (N = count -
9
- * selected, only when > 0). Additive; consumed by the plugin proficiency panel. */
10
- choices: {
11
- languages: string[];
12
- tools: string[];
13
- };
14
+ armor: ProficiencyEntry[];
15
+ weapons: ProficiencyEntry[];
16
+ tools: ProficiencyEntry[];
17
+ languages: ProficiencyEntry[];
14
18
  }
15
19
  /** Normalized intermediate of every proficiency/language source the DISPLAY path
16
- * reads, in one place. Only the class branch is a live proficiency source today
17
- * (race/bg/feat `.proficiencies` object fields do not exist on those entities);
18
- * race languages, bg tool/lang, and feat grants read their real fields. Chosen
19
- * picks + per-choice status come from the single decision walk in
20
- * pc.decision-engine so display can never diverge from the persisted picks. */
20
+ * reads, in one place. Among the ENTITY branches only the class one is a live
21
+ * proficiency source today (race/bg/feat `.proficiencies` object fields do not
22
+ * exist on those entities); race languages, bg tool/lang, and feat grants read
23
+ * their real fields, and the four `effect*` buckets carry what a feature's
24
+ * `kind: "proficiency"` effects grant · that is the live path a 2014 race trait
25
+ * actually uses (R4-P3c). Chosen picks come from the single decision walk in
26
+ * pc.decision-engine so display can never diverge from the persisted picks.
27
+ *
28
+ * Every grant bucket carries `{value, source}` rather than a bare value: the
29
+ * walk holds the granting entity and the aggregate needs it to fill
30
+ * `ProficiencyEntry.sources` for armor and weapons (spec §7.1). The
31
+ * language/tool grant buckets are still surfaced here for completeness, but
32
+ * the aggregate no longer reads them · languages and tools come from
33
+ * computeEffectiveProficiencies, which does its own grant walk so that
34
+ * suppressions and manual adds can never be skipped by a second composer.
35
+ *
36
+ * These names are RE-DECLARED by hand rather than inherited: this interface
37
+ * does not `extends ProficiencyGrants`, so a bucket added to the grant walk
38
+ * stays invisible here until it is added here too. `collectProficiencySources`
39
+ * spreads the grants object, so an undeclared bucket still flows at RUNTIME ·
40
+ * the compiler is the only thing that notices, and only where the field is
41
+ * read. Nothing iterates these objects with Object.keys/values/entries, so a
42
+ * declaration gap is inert rather than corrupting; it is still a gap. */
21
43
  export interface ProficiencySources {
22
- classArmor: string[];
23
- classWeaponFixed: string[];
24
- classWeaponCategories: string[];
25
- classToolFixed: string[];
26
- raceLangFixed: string[];
27
- bgToolFixed: string[];
28
- bgLangFixed: string[];
29
- featArmor: string[];
30
- featWeapons: string[];
31
- featTools: string[];
32
- featLanguages: string[];
44
+ classArmor: ProficiencyGrant[];
45
+ classWeaponFixed: ProficiencyGrant[];
46
+ classWeaponCategories: ProficiencyGrant[];
47
+ classToolFixed: ProficiencyGrant[];
48
+ raceLangFixed: ProficiencyGrant[];
49
+ bgToolFixed: ProficiencyGrant[];
50
+ bgLangFixed: ProficiencyGrant[];
51
+ featArmor: ProficiencyGrant[];
52
+ featWeapons: ProficiencyGrant[];
53
+ featTools: ProficiencyGrant[];
54
+ featLanguages: ProficiencyGrant[];
55
+ effectArmor: ProficiencyGrant[];
56
+ effectWeapons: ProficiencyGrant[];
57
+ effectTools: ProficiencyGrant[];
58
+ effectLanguages: ProficiencyGrant[];
33
59
  chosenLanguages: string[];
34
60
  chosenTools: string[];
35
- languageChoices: ChoiceStatus[];
36
- toolChoices: ChoiceStatus[];
61
+ chosenWeapons: string[];
37
62
  }
38
63
  export declare function collectProficiencySources(resolved: ResolvedCharacter): ProficiencySources;
39
64
  export declare function aggregateProficiencies(resolved: ResolvedCharacter): ProficiencyAggregate;