@archivist-gg/dnd5e 0.3.13 → 0.3.19

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 (122) hide show
  1. package/dist/background/background.codec.js +5 -5
  2. package/dist/background/background.parser.js +4 -4
  3. package/dist/background/background.schema.js +3 -3
  4. package/dist/chunk-3LWMFYKS.js +57 -0
  5. package/dist/{chunk-4SLOJXJ4.js → chunk-3SFGWFVQ.js} +1 -1
  6. package/dist/{chunk-Q6UOHCCT.js → chunk-3SIUAFDQ.js} +1 -1
  7. package/dist/chunk-4J6DS3EJ.js +30 -0
  8. package/dist/{chunk-RLGQ5PZZ.js → chunk-5JXFJ5AU.js} +11 -1
  9. package/dist/{chunk-4BZ47YJ4.js → chunk-64VNLMSG.js} +59 -16
  10. package/dist/{chunk-XY6X2APM.js → chunk-7Q2VPKCD.js} +17 -0
  11. package/dist/{chunk-Q5XTEYHD.js → chunk-7XJHI5H5.js} +16 -9
  12. package/dist/{chunk-FPGXFD2M.js → chunk-B2QHP6UF.js} +2 -2
  13. package/dist/{chunk-HJQNK3J5.js → chunk-B3DP42OM.js} +1 -4
  14. package/dist/{chunk-MC5YJ4PB.js → chunk-BJGOSM76.js} +1 -1
  15. package/dist/{chunk-VDPRBUU5.js → chunk-BJWSOQUC.js} +1 -1
  16. package/dist/{chunk-QEO7LYTG.js → chunk-EJD47JHL.js} +181 -93
  17. package/dist/{chunk-X7VZY4ON.js → chunk-FJYBLXV7.js} +2 -2
  18. package/dist/{chunk-QSBJ6NEM.js → chunk-FMUD3LDV.js} +1 -1
  19. package/dist/{chunk-LVX6TQ3R.js → chunk-FMYWORTP.js} +2 -2
  20. package/dist/{chunk-PX25R3DK.js → chunk-FVXLP7P3.js} +1 -1
  21. package/dist/{chunk-XJRNEQPD.js → chunk-GVWEFECG.js} +1 -1
  22. package/dist/{chunk-R2V5LN3I.js → chunk-HY2SRSVM.js} +1 -1
  23. package/dist/{chunk-ZMJQUZQH.js → chunk-JXWNXIIZ.js} +24 -0
  24. package/dist/chunk-KR6JF3BF.js +14 -0
  25. package/dist/{chunk-DPM6V7YX.js → chunk-LICYHPI3.js} +82 -6
  26. package/dist/{chunk-J53ERF6K.js → chunk-LNT64X7C.js} +1 -1
  27. package/dist/{chunk-RQNJXADQ.js → chunk-NKZMQV2C.js} +1 -1
  28. package/dist/{chunk-UE6LKRXQ.js → chunk-NTU2DXNG.js} +24 -1
  29. package/dist/{chunk-T5GZBKIQ.js → chunk-NXUY5XQN.js} +168 -33
  30. package/dist/{chunk-M2OWVAZG.js → chunk-NZO7M5GV.js} +1 -1
  31. package/dist/{chunk-BRGLDWUP.js → chunk-OOL4KMG2.js} +7 -3
  32. package/dist/{chunk-MWCCGPG3.js → chunk-OOSRUJBI.js} +3 -2
  33. package/dist/{chunk-H5PTKYXL.js → chunk-QA3YCOML.js} +1 -1
  34. package/dist/{chunk-MUIU7RZ3.js → chunk-QFWQKS3M.js} +1 -1
  35. package/dist/chunk-RD2MID3M.js +292 -0
  36. package/dist/chunk-TRBGC4ZG.js +112 -0
  37. package/dist/{chunk-PLOSP4XQ.js → chunk-UJCK5IW4.js} +11 -2
  38. package/dist/{chunk-5KGV6TF7.js → chunk-UNYR5GLI.js} +1 -1
  39. package/dist/{chunk-JNNNBEUH.js → chunk-VN5DLURA.js} +7 -2
  40. package/dist/{chunk-EL6QPF6I.js → chunk-VP5TIXDK.js} +1 -1
  41. package/dist/chunk-WOHX7YTH.js +165 -0
  42. package/dist/{chunk-G7ED5H2G.js → chunk-XTEFPUSN.js} +1 -1
  43. package/dist/{chunk-6ZY3U6BQ.js → chunk-ZOWKWMD2.js} +1 -1
  44. package/dist/class/class.codec.js +6 -6
  45. package/dist/class/class.parser.js +5 -5
  46. package/dist/class/class.schema.d.ts +8 -0
  47. package/dist/class/class.schema.js +4 -4
  48. package/dist/class/class.standard-array.d.ts +16 -0
  49. package/dist/class/class.standard-array.js +20 -0
  50. package/dist/class/class.types.d.ts +3 -0
  51. package/dist/dnd/amount-text.d.ts +42 -0
  52. package/dist/dnd/amount-text.js +15 -0
  53. package/dist/dnd/resource-words.d.ts +4 -0
  54. package/dist/dnd/resource-words.js +30 -0
  55. package/dist/entities/registry-revision.d.ts +25 -0
  56. package/dist/entities/registry-revision.js +10 -0
  57. package/dist/feat/feat.codec.js +5 -5
  58. package/dist/feat/feat.parser.js +4 -4
  59. package/dist/feat/feat.schema.d.ts +34 -0
  60. package/dist/feat/feat.schema.js +3 -3
  61. package/dist/index.js +4027 -383
  62. package/dist/monster/monster.parser.js +5 -5
  63. package/dist/monster/monster.schema.js +4 -4
  64. package/dist/optional-feature/optional-feature.codec.js +5 -4
  65. package/dist/optional-feature/optional-feature.parser.js +4 -3
  66. package/dist/optional-feature/optional-feature.schema.d.ts +35 -0
  67. package/dist/optional-feature/optional-feature.schema.js +3 -2
  68. package/dist/pc/pc.additional-spells.d.ts +16 -2
  69. package/dist/pc/pc.additional-spells.js +5 -2
  70. package/dist/pc/pc.amount-bindings.d.ts +41 -0
  71. package/dist/pc/pc.amount-bindings.js +28 -0
  72. package/dist/pc/pc.decision-engine.d.ts +11 -0
  73. package/dist/pc/pc.decision-engine.js +20 -16
  74. package/dist/pc/pc.entry-overrides.js +3 -2
  75. package/dist/pc/pc.equipment.js +7 -5
  76. package/dist/pc/pc.feature-effects.d.ts +21 -2
  77. package/dist/pc/pc.feature-effects.js +15 -2
  78. package/dist/pc/pc.infusions.d.ts +9 -1
  79. package/dist/pc/pc.infusions.js +3 -2
  80. package/dist/pc/pc.member-choices.d.ts +122 -0
  81. package/dist/pc/pc.member-choices.js +39 -0
  82. package/dist/pc/pc.parser.js +1 -1
  83. package/dist/pc/pc.pools.d.ts +7 -0
  84. package/dist/pc/pc.pools.js +5 -2
  85. package/dist/pc/pc.proficiencies.d.ts +14 -0
  86. package/dist/pc/pc.proficiencies.js +72 -22
  87. package/dist/pc/pc.proficiency-normalize.d.ts +14 -0
  88. package/dist/pc/pc.proficiency-normalize.js +9 -3
  89. package/dist/pc/pc.proficiency-query.js +7 -1
  90. package/dist/pc/pc.recalc.js +212 -99
  91. package/dist/pc/pc.resolver.d.ts +6 -2
  92. package/dist/pc/pc.resolver.js +16 -14
  93. package/dist/pc/pc.rest.js +4 -3
  94. package/dist/pc/pc.schema.d.ts +48 -2
  95. package/dist/pc/pc.schema.js +1 -1
  96. package/dist/pc/pc.spell-scope.d.ts +22 -0
  97. package/dist/pc/pc.tool-picks.js +2 -2
  98. package/dist/pc/pc.types.d.ts +110 -6
  99. package/dist/pc/pc.weapon-grant.d.ts +15 -0
  100. package/dist/pc/pool-layout.d.ts +3 -9
  101. package/dist/pc/pool-layout.js +1 -3
  102. package/dist/pc/resource-equivalents.d.ts +44 -0
  103. package/dist/pc/spell-source.js +4 -9
  104. package/dist/race/race.codec.js +6 -6
  105. package/dist/race/race.parser.js +5 -5
  106. package/dist/race/race.schema.js +4 -4
  107. package/dist/schemas/choice-schema.js +2 -2
  108. package/dist/schemas/feature-effect-schema.d.ts +34 -0
  109. package/dist/schemas/feature-effect-schema.js +1 -1
  110. package/dist/schemas/feature-schema.js +3 -3
  111. package/dist/spell/spell.codec.js +2 -2
  112. package/dist/spell/spell.parser.d.ts +6 -1
  113. package/dist/spell/spell.parser.js +5 -3
  114. package/dist/spell/spell.types.d.ts +10 -0
  115. package/dist/subclass/subclass.codec.js +6 -6
  116. package/dist/subclass/subclass.parser.js +5 -5
  117. package/dist/subclass/subclass.schema.js +4 -4
  118. package/dist/types/choice.d.ts +7 -2
  119. package/dist/types/feature-effect.d.ts +24 -0
  120. package/dist/types/optional-feature.types.d.ts +6 -1
  121. package/dist/types/selection-pool.d.ts +2 -2
  122. package/package.json +1 -1
@@ -0,0 +1,122 @@
1
+ /**
2
+ * D2b-A · nested choices on pool members (design §2.2, user rulings 2026-09-27: GENERAL and RECURSIVE).
3
+ *
4
+ * A pool member (an invocation, a maneuver, a fighting-style option, ...) may carry its own choices: Lessons of the First
5
+ * Ones' Origin feat, Superior Technique's maneuver, Blessed and Druidic Warrior's cantrips, Pact of the Tome's cantrips
6
+ * and rituals, the three blasts' cantrip. A nested pick lives in the pool's ANCHOR block under
7
+ * `<poolId>:<pickKey>:<choiceId>` (a nested feat's own children under `<poolId>:<pickKey>:feat:<childId>`, the decision
8
+ * engine's prefix chaining), keyed by the stored pick string, so reordering or adding picks never moves it.
9
+ *
10
+ * The choices are the member's authored `choices`; absent (every note in the vault today), they are DERIVED at read time
11
+ * from the raw keys the notes already carry (`feat_progression`, `optionalfeature_progression`, `additional_spells`
12
+ * with `choose`, a `spell-choose` prerequisite), so existing notes work with no re-import. Authored `choices` win.
13
+ */
14
+ import type { Character, ChoiceValue, FeatVia, ResolvedCharacter, ResolvedClass, ResolvedSpell, SpellTargets } from "./pc.types.js";
15
+ import type { Choice } from "@archivist-gg/dnd5e/types/choice";
16
+ import type { OptionalFeatureEntity } from "@archivist-gg/dnd5e/types/optional-feature.types";
17
+ import type { FeatEntity } from "@archivist-gg/dnd5e/feat/feat.types";
18
+ /** Whether `ch` names a target rather than granting what it picks (the data flag `target: true`, which the spell-choose
19
+ * derivation below writes and an authored note may carry). */
20
+ export declare function isTargetChoice(ch: Choice): boolean;
21
+ /**
22
+ * The member's choices: its authored `choices`, else the ones its raw keys ask for (see the module docblock). Pure; the
23
+ * derived rows carry the member's edition so a pick never offers the other edition's twin.
24
+ */
25
+ export declare function memberChoices(entity: OptionalFeatureEntity | null | undefined): Choice[];
26
+ /**
27
+ * The spells a member's spell-scoped damage rider reaches, as its DATA names them (found live 2026-09-28: Agonizing
28
+ * Blast's Charisma never reached its cantrip): each answer of a spell target pick (`isTargetChoice`, the blasts' cantrip,
29
+ * read through `read`, the member's nested key) and each `spell-known` prerequisite (the 2014 Agonizing Blast's Eldritch
30
+ * Blast). A target pick left unanswered keeps its filter in `open` (see {@link spellsReached}). Null when the member names
31
+ * no spell, so its spell riders reach none.
32
+ */
33
+ export declare function memberSpellTargets(member: OptionalFeatureEntity, read: (choiceId: string) => ChoiceValue | undefined): SpellTargets | null;
34
+ /**
35
+ * The slugs of the character's `spells` a rider with `targets` reaches, unique, in `spells` order: every spell a ref
36
+ * names (its full slug, else its bare name, so a path link `[[SRD 5e/eldritch-blast]]` finds `srd-5e_spell_eldritch-blast`),
37
+ * and, for each open target pick, its ONE legal answer: the character's own spells that match the pick's filter and roll
38
+ * damage, when exactly one does ("one of your known Warlock cantrips that deals damage" with only Eldritch Blast known).
39
+ * Two or more legal answers are a choice the player has not made, and reach none.
40
+ */
41
+ export declare function spellsReached(targets: SpellTargets | undefined, spells: ReadonlyArray<Pick<ResolvedSpell, "slug" | "entity">>): string[];
42
+ /** One stored pool pick whose member carries choices, with the block its nested picks live in. */
43
+ export interface PoolMemberSite {
44
+ classIndex: number;
45
+ /** The pool's anchor level (the block's key in `choices`). */
46
+ level: number;
47
+ poolId: string;
48
+ /** The stored pick string (a copy of a repeatable note keeps its `slug#param`). */
49
+ pickKey: string;
50
+ member: OptionalFeatureEntity;
51
+ memberSlug: string;
52
+ choices: Choice[];
53
+ /** `<poolId>:<pickKey>:` */
54
+ prefix: string;
55
+ /** The nested pick stored for a member choice id. */
56
+ read: (choiceId: string) => ChoiceValue | undefined;
57
+ /** The anchor block, as stored. */
58
+ block: Record<string, ChoiceValue>;
59
+ }
60
+ /** Structural subset of the registry the raw enumerator reads. */
61
+ export interface MemberRegistry {
62
+ getByTypeAndSlug(entityType: string, slug: string): {
63
+ slug: string;
64
+ data: Record<string, unknown>;
65
+ } | undefined;
66
+ search(query: string, entityType: string, limit: number): Array<{
67
+ slug: string;
68
+ data: Record<string, unknown>;
69
+ }>;
70
+ }
71
+ /**
72
+ * Every pool member site from the RAW character (the resolver's view, before pools resolve): each class's and subclass's
73
+ * declared pools, their anchor levels (`poolAnchorLevel`, the one derivation `resolvePool` uses), the picks stored there,
74
+ * each resolved to its note (full slug, else bare). A pick with no note, or a note with no choices, yields no site, so an
75
+ * orphan nested key is inert.
76
+ */
77
+ export declare function poolMemberSites(classes: ResolvedClass[], registry: MemberRegistry): PoolMemberSite[];
78
+ /** The same sites read from a RESOLVED character's pools (the ledger's, the proficiency walk's and recalc's view). */
79
+ export declare function poolMemberSitesOf(resolved: Pick<ResolvedCharacter, "pools" | "classes">): PoolMemberSite[];
80
+ /** A feat the character picked, where it was picked, and how its own children are read. */
81
+ export interface FeatPickSite {
82
+ slug: string;
83
+ classIndex: number;
84
+ level: number | null;
85
+ via: FeatVia | null;
86
+ /** A child choice of THIS feat, as stored (`feat:<id>` in the slot's block, or `<prefix>feat:<id>` under a member). */
87
+ read: (childId: string) => ChoiceValue | undefined;
88
+ }
89
+ /**
90
+ * Every feat pick with its own read (design §2.2 step 4): the class-slot picks (`choices[<level>].feat`, children under
91
+ * `feat:<id>`) and the pool-member picks (a member choice of `entity_type: feat`, children under `<prefix>feat:<id>`).
92
+ * The ONE list the four feat readers walk (the resolver's feats and feat spells, the proficiency walk, ability points).
93
+ * `classSlugOf` names a class for the provenance. NOT de-duplicated: a feat taken twice (Magic Initiate, one spell list
94
+ * each) is two sites with two reads; a reader that lists feats de-duplicates by slug itself (first wins).
95
+ */
96
+ export declare function featPickSites(classEntries: ReadonlyArray<{
97
+ choices?: Character["class"][number]["choices"] | ResolvedClass["choices"];
98
+ }> | undefined, memberSites: PoolMemberSite[], classSlugOf: (classIndex: number) => string | null): FeatPickSite[];
99
+ /** SRD-G T4 (audit §3b): a feat the character picked through a SPECIES (the SRD 5.2 Human's Versatile: "You gain an
100
+ * Origin feat of your choice"): a `select-entity` of `entity_type: feat` on `race.choices[]` or a trait's `choices[]`,
101
+ * answered under `origin_choices["race:<id>"]`. The feat's OWN children are read under `race:feat:<childId>`, the key
102
+ * the builder's ledger writes them to (a race item's feat children carry the `feat:` prefix, namespace `race`). */
103
+ export interface RaceFeatSite {
104
+ slug: string;
105
+ via: FeatVia;
106
+ read: (childId: string) => ChoiceValue | undefined;
107
+ }
108
+ export declare function raceFeatSites(race: {
109
+ slug: string;
110
+ choices?: unknown;
111
+ traits?: ReadonlyArray<{
112
+ choices?: unknown;
113
+ }>;
114
+ } | null | undefined, originChoices: Readonly<Record<string, ChoiceValue>> | undefined): RaceFeatSite[];
115
+ /** Look a feat pick up in the resolved feats (by slug, then bare slug). */
116
+ export declare function featOfSite(site: FeatPickSite, feats: ReadonlyArray<FeatEntity>): FeatEntity | undefined;
117
+ /**
118
+ * The nested keys an array write to `key` orphans (design §2.2 step 8): every `<key>:<k>:*` whose `k` was stored before
119
+ * and is not in `next`. Pure; the writer deletes them in the same write, so a re-pick never resurrects an old answer.
120
+ * A key that is not a pool (no array stored) orphans nothing.
121
+ */
122
+ export declare function nestedPickKeysToPrune(block: Record<string, unknown> | undefined, key: string, next: readonly string[]): string[];
@@ -0,0 +1,39 @@
1
+ import {
2
+ featOfSite,
3
+ featPickSites,
4
+ isTargetChoice,
5
+ memberChoices,
6
+ memberSpellTargets,
7
+ nestedPickKeysToPrune,
8
+ poolMemberSites,
9
+ poolMemberSitesOf,
10
+ raceFeatSites,
11
+ spellsReached
12
+ } from "../chunk-RD2MID3M.js";
13
+ import "../chunk-NXUY5XQN.js";
14
+ import "../chunk-W6GC6OKK.js";
15
+ import "../chunk-B3DP42OM.js";
16
+ import "../chunk-IUAAXXNO.js";
17
+ import "../chunk-ZUQW5VBZ.js";
18
+ import "../chunk-IHE6KVC3.js";
19
+ import "../chunk-LHZXYFBU.js";
20
+ import "../chunk-YBLDE5CG.js";
21
+ import "../chunk-LVQLWRCU.js";
22
+ import "../chunk-CM5CWO4K.js";
23
+ import "../chunk-4J6DS3EJ.js";
24
+ import "../chunk-ZCTUYGNV.js";
25
+ import "../chunk-3TM34RSJ.js";
26
+ import "../chunk-PDJSUH24.js";
27
+ import "../chunk-WELIKLZF.js";
28
+ export {
29
+ featOfSite,
30
+ featPickSites,
31
+ isTargetChoice,
32
+ memberChoices,
33
+ memberSpellTargets,
34
+ nestedPickKeysToPrune,
35
+ poolMemberSites,
36
+ poolMemberSitesOf,
37
+ raceFeatSites,
38
+ spellsReached
39
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  characterSchema
3
- } from "../chunk-UE6LKRXQ.js";
3
+ } from "../chunk-NTU2DXNG.js";
4
4
  import "../chunk-S7REMWW4.js";
5
5
  import {
6
6
  keptSlugForLink
@@ -9,6 +9,13 @@ export interface PoolRegistry {
9
9
  getByTypeAndSlug(entityType: string, slug: string): RegisteredEntity | undefined;
10
10
  }
11
11
  export declare function levelPrereqMax(d: OptionalFeatureEntity): number;
12
+ /** The pool's ANCHOR level (its picks live in `choices[anchorLevel][pool.id]`): the first level whose count column
13
+ * carries a value of at least 1, else 1; `columnSeen` says whether any level carried the column at all. ONE derivation
14
+ * shared by `resolvePool` and the nested-choice enumerator (D2b-A, `pc.member-choices.ts`), so the two always agree. */
15
+ export declare function poolAnchorLevel(entity: Pick<NonNullable<ResolvedClass["entity"]>, "table">, pool: Pick<SelectionPool, "count">): {
16
+ anchorLevel: number;
17
+ columnSeen: boolean;
18
+ };
12
19
  export declare function resolvePool(rc: ResolvedClass, classIndex: number, pool: SelectionPool, registry: PoolRegistry, index: ResourceIndex, edition?: string): ResolvedPool;
13
20
  export declare function resolveAllPools(resolved: ResolvedCharacter, registry: PoolRegistry, index: ResourceIndex): ResolvedPool[];
14
21
  /** The stored picks that survived resolution but are NOT in the collapsed `available` list, compared by FULL
@@ -1,23 +1,26 @@
1
1
  import {
2
2
  levelPrereqMax,
3
+ poolAnchorLevel,
3
4
  resolveAllPools,
4
5
  resolvePool,
5
6
  strandedPicks
6
- } from "../chunk-T5GZBKIQ.js";
7
+ } from "../chunk-NXUY5XQN.js";
7
8
  import "../chunk-W6GC6OKK.js";
8
- import "../chunk-HJQNK3J5.js";
9
+ import "../chunk-B3DP42OM.js";
9
10
  import "../chunk-IUAAXXNO.js";
10
11
  import "../chunk-ZUQW5VBZ.js";
11
12
  import "../chunk-IHE6KVC3.js";
12
13
  import "../chunk-LHZXYFBU.js";
13
14
  import "../chunk-LVQLWRCU.js";
14
15
  import "../chunk-CM5CWO4K.js";
16
+ import "../chunk-4J6DS3EJ.js";
15
17
  import "../chunk-ZCTUYGNV.js";
16
18
  import "../chunk-3TM34RSJ.js";
17
19
  import "../chunk-PDJSUH24.js";
18
20
  import "../chunk-WELIKLZF.js";
19
21
  export {
20
22
  levelPrereqMax,
23
+ poolAnchorLevel,
21
24
  resolveAllPools,
22
25
  resolvePool,
23
26
  strandedPicks
@@ -1,4 +1,6 @@
1
1
  import type { ResolvedCharacter } from "./pc.types.js";
2
+ import type { WeaponEntity } from "@archivist-gg/dnd5e/weapon/weapon.types";
3
+ import { type ProficiencyKind } from "./pc.proficiency-normalize.js";
2
4
  import { type ProficiencyEntry, type ProficiencyGrant } from "./pc.proficiency-grants.js";
3
5
  /** Re-exported for the plugin: both types are DECLARED in the leaf so that
4
6
  * pc.decision-engine can reference them without importing this module, which
@@ -62,3 +64,15 @@ export interface ProficiencySources {
62
64
  }
63
65
  export declare function collectProficiencySources(resolved: ResolvedCharacter): ProficiencySources;
64
66
  export declare function aggregateProficiencies(resolved: ResolvedCharacter): ProficiencyAggregate;
67
+ /** D4 A: one kind's proficiencies as the picker needs them: `effective` is exactly the sheet's line (the aggregate's
68
+ * bucket), `removed` every grant or pick the user's overrides suppress, keeping its `sources`. Both halves come out of
69
+ * the composers the sheet reads, so the picker and the sheet line cannot disagree. */
70
+ export declare function proficiencyOverview(resolved: ResolvedCharacter, kind: ProficiencyKind): {
71
+ effective: ProficiencyEntry[];
72
+ removed: ProficiencyEntry[];
73
+ };
74
+ /** D4 A: the effective weapon entry that covers `weapon` without naming it, a category (`simple`, `martial`, or the
75
+ * entity's own `martial-melee` form) or a property-qualified grant ("Martial weapons that have the Light property").
76
+ * The same three arms as the live gate, `isWeaponSlugProficient` (pc.equipment.ts), so the picker's dimmed "via" row
77
+ * and the attack row's proficiency agree. Undefined when nothing but the weapon's own entry could cover it. */
78
+ export declare function weaponCoveredBy(effective: readonly ProficiencyEntry[], weapon: Pick<WeaponEntity, "category" | "properties" | "range" | "slug">): ProficiencyEntry | undefined;
@@ -1,37 +1,47 @@
1
+ import {
2
+ qualifiedGrantCovers
3
+ } from "../chunk-3LWMFYKS.js";
4
+ import "../chunk-YDGMBMYI.js";
1
5
  import {
2
6
  collectChosenProficiencies,
3
7
  collectProficiencyGrants,
4
- computeEffectiveProficiencies
5
- } from "../chunk-QEO7LYTG.js";
8
+ computeEffectiveProficiencies,
9
+ computeSuppressedProficiencies
10
+ } from "../chunk-EJD47JHL.js";
6
11
  import "../chunk-2LULMQTF.js";
7
- import "../chunk-H5PTKYXL.js";
8
- import "../chunk-KSTV7J2U.js";
9
- import "../chunk-DPUJDOKV.js";
10
- import "../chunk-4BZ47YJ4.js";
12
+ import "../chunk-QA3YCOML.js";
13
+ import "../chunk-64VNLMSG.js";
11
14
  import "../chunk-TOM66P2P.js";
12
15
  import "../chunk-KU3YLF6D.js";
13
16
  import {
17
+ normKey,
18
+ proficiencyKey,
14
19
  proficiencyLabel,
15
- toProfSlug
16
- } from "../chunk-RLGQ5PZZ.js";
17
- import "../chunk-T5GZBKIQ.js";
20
+ toProfSlug,
21
+ weaponProfKey
22
+ } from "../chunk-5JXFJ5AU.js";
23
+ import "../chunk-RD2MID3M.js";
24
+ import "../chunk-LICYHPI3.js";
25
+ import "../chunk-PVCEA4HZ.js";
26
+ import "../chunk-KSTV7J2U.js";
27
+ import "../chunk-DPUJDOKV.js";
28
+ import "../chunk-LBEXLN6I.js";
29
+ import "../chunk-NXUY5XQN.js";
18
30
  import "../chunk-W6GC6OKK.js";
19
- import "../chunk-HJQNK3J5.js";
31
+ import "../chunk-B3DP42OM.js";
20
32
  import "../chunk-IUAAXXNO.js";
21
33
  import "../chunk-YHYTW5QA.js";
22
- import "../chunk-DPM6V7YX.js";
23
- import "../chunk-PVCEA4HZ.js";
24
- import "../chunk-LBEXLN6I.js";
25
34
  import "../chunk-ZUQW5VBZ.js";
26
35
  import "../chunk-IHE6KVC3.js";
27
36
  import "../chunk-LHZXYFBU.js";
37
+ import "../chunk-YBLDE5CG.js";
28
38
  import "../chunk-LVQLWRCU.js";
29
39
  import "../chunk-CM5CWO4K.js";
30
- import "../chunk-ZCTUYGNV.js";
31
- import "../chunk-YBLDE5CG.js";
40
+ import "../chunk-4J6DS3EJ.js";
32
41
  import "../chunk-EVMUJYUD.js";
33
42
  import "../chunk-VTTA2ALZ.js";
34
- import "../chunk-XY6X2APM.js";
43
+ import "../chunk-ZCTUYGNV.js";
44
+ import "../chunk-7Q2VPKCD.js";
35
45
  import "../chunk-ZDAEGJH4.js";
36
46
  import "../chunk-3TM34RSJ.js";
37
47
  import "../chunk-PDJSUH24.js";
@@ -51,8 +61,29 @@ function collectProficiencySources(resolved) {
51
61
  };
52
62
  }
53
63
  function aggregateProficiencies(resolved) {
54
- const src = collectProficiencySources(resolved);
64
+ const { armor, weapons } = composeArmorWeapons(resolved);
55
65
  const effective = computeEffectiveProficiencies(resolved);
66
+ return { armor: armor.kept, weapons: weapons.kept, tools: effective.tools, languages: effective.languages };
67
+ }
68
+ function proficiencyOverview(resolved, kind) {
69
+ if (kind === "armor" || kind === "weapons") {
70
+ const c = composeArmorWeapons(resolved)[kind];
71
+ return { effective: c.kept, removed: c.suppressed };
72
+ }
73
+ return { effective: computeEffectiveProficiencies(resolved)[kind], removed: computeSuppressedProficiencies(resolved)[kind] };
74
+ }
75
+ function weaponCoveredBy(effective, weapon) {
76
+ const cat = typeof weapon.category === "string" ? weapon.category : "";
77
+ const base = cat.split("-")[0];
78
+ return effective.find((e) => {
79
+ const key = weaponProfKey(e.value);
80
+ if ((key === "simple" || key === "martial") && key === base) return true;
81
+ if (cat && key === normKey(cat)) return true;
82
+ return qualifiedGrantCovers(e.value, weapon);
83
+ });
84
+ }
85
+ function composeArmorWeapons(resolved) {
86
+ const src = collectProficiencySources(resolved);
56
87
  return {
57
88
  // Walk order defines `sources` order (spec §4.1): class before feats, and
58
89
  // for weapons the class `fixed` names before the class categories, matching
@@ -67,10 +98,27 @@ function aggregateProficiencies(resolved) {
67
98
  // keeps origin `grant` and its granting source(s) (R4-G3b §7 F5 · the same
68
99
  // grants-then-picks order computeEffectiveProficiencies uses for the
69
100
  // language and tool buckets). Armor passes none.
70
- armor: composeGrantEntries([src.classArmor, src.featArmor, src.effectArmor]),
71
- weapons: composeGrantEntries([src.classWeaponFixed, src.classWeaponCategories, src.featWeapons, src.effectWeapons], src.chosenWeapons),
72
- tools: effective.tools,
73
- languages: effective.languages
101
+ armor: applyKindOverrides(resolved, "armor", composeGrantEntries([src.classArmor, src.featArmor, src.effectArmor])),
102
+ weapons: applyKindOverrides(
103
+ resolved,
104
+ "weapons",
105
+ composeGrantEntries([src.classWeaponFixed, src.classWeaponCategories, src.featWeapons, src.effectWeapons], src.chosenWeapons)
106
+ )
107
+ };
108
+ }
109
+ function applyKindOverrides(resolved, kind, entries) {
110
+ var _a, _b, _c, _d;
111
+ const ov = (_b = (_a = resolved.definition) == null ? void 0 : _a.overrides) == null ? void 0 : _b[kind];
112
+ const key = (v) => proficiencyKey(kind, v);
113
+ const all = [...entries];
114
+ for (const v of (_c = ov == null ? void 0 : ov.add) != null ? _c : []) {
115
+ if (!all.some((e) => key(e.value) === key(v))) all.push({ value: v, label: prettyName(v), sources: [], origin: "manual" });
116
+ }
117
+ const removes = new Set(((_d = ov == null ? void 0 : ov.remove) != null ? _d : []).map(key));
118
+ const byLabel = (a, b) => a.label < b.label ? -1 : a.label > b.label ? 1 : 0;
119
+ return {
120
+ kept: all.filter((e) => !removes.has(key(e.value))).sort(byLabel),
121
+ suppressed: all.filter((e) => removes.has(key(e.value))).sort(byLabel)
74
122
  };
75
123
  }
76
124
  function composeGrantEntries(buckets, picks = []) {
@@ -102,5 +150,7 @@ function prettyName(slug) {
102
150
  }
103
151
  export {
104
152
  aggregateProficiencies,
105
- collectProficiencySources
153
+ collectProficiencySources,
154
+ proficiencyOverview,
155
+ weaponCoveredBy
106
156
  };
@@ -27,3 +27,17 @@ export declare const humanizeProficiency: (s: string) => string;
27
27
  * the first character capitalised, every other letter untouched. Title-casing it word by word lower-cased
28
28
  * `2H` to `2h` and read "That Have The Light Property". */
29
29
  export declare const proficiencyLabel: (raw: string) => string;
30
+ /** The four proficiency kinds the sheet lists and the D4 pickers edit, one picker per kind. */
31
+ export type ProficiencyKind = "armor" | "weapons" | "tools" | "languages";
32
+ /** D4 A: the key of a WEAPON proficiency value, so every spelling of one grant folds onto one key. A category reads
33
+ * `simple` or `martial` whatever its spelling ("martial", "Martial weapons"); anything else is keyed by
34
+ * {@link normKey}, the rule the live weapon gate (`isWeaponSlugProficient`) matches a weapon's name by, so
35
+ * "longswords", "Longsword" and "longsword" meet, and "hand-crossbow" meets "Hand Crossbows" and "Crossbow, hand". */
36
+ export declare const weaponProfKey: (raw: string) => string;
37
+ /** D4 A: the key of an ARMOR proficiency value: `light`, `medium`, `heavy`, `shield` whatever the spelling
38
+ * ("Heavy armor", "shields"). */
39
+ export declare const armorProfKey: (raw: string) => string;
40
+ /** D4 A: the one comparison key per kind, used by the engine's override readers and the plugin's writers alike, so a
41
+ * value the writer stores and the value the reader suppresses always meet. Languages and tools keep `toProfSlug`,
42
+ * unchanged. */
43
+ export declare const proficiencyKey: (kind: ProficiencyKind, raw: string) => string;
@@ -1,12 +1,18 @@
1
1
  import {
2
+ armorProfKey,
2
3
  humanizeProficiency,
3
4
  normKey,
5
+ proficiencyKey,
4
6
  proficiencyLabel,
5
- toProfSlug
6
- } from "../chunk-RLGQ5PZZ.js";
7
+ toProfSlug,
8
+ weaponProfKey
9
+ } from "../chunk-5JXFJ5AU.js";
7
10
  export {
11
+ armorProfKey,
8
12
  humanizeProficiency,
9
13
  normKey,
14
+ proficiencyKey,
10
15
  proficiencyLabel,
11
- toProfSlug
16
+ toProfSlug,
17
+ weaponProfKey
12
18
  };
@@ -1,10 +1,16 @@
1
+ import {
2
+ qualifiedGrantCovers
3
+ } from "../chunk-3LWMFYKS.js";
4
+ import "../chunk-YDGMBMYI.js";
1
5
  import {
2
6
  normKey
3
- } from "../chunk-RLGQ5PZZ.js";
7
+ } from "../chunk-5JXFJ5AU.js";
8
+ import "../chunk-ZCTUYGNV.js";
4
9
 
5
10
  // src/pc/pc.proficiency-query.ts
6
11
  function isProficientWithWeapon(weapon, profs) {
7
12
  if (profs.weapons.specific.some((e) => e === weapon.slug || normKey(e) === normKey(weapon.name))) return true;
13
+ if (profs.weapons.specific.some((e) => qualifiedGrantCovers(e, weapon))) return true;
8
14
  const baseCategory = weapon.category.split("-")[0];
9
15
  return profs.weapons.categories.includes(baseCategory);
10
16
  }