@archivist-gg/dnd5e 0.3.0 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/SRD.md +25 -6
- package/dist/armor/armor.codec.js +4 -3
- package/dist/armor/armor.parser.js +3 -2
- package/dist/armor/armor.schema.d.ts +4 -0
- package/dist/armor/armor.schema.js +2 -1
- package/dist/armor/armor.types.d.ts +8 -0
- package/dist/background/background.codec.js +7 -6
- package/dist/background/background.parser.js +6 -5
- package/dist/background/background.schema.d.ts +34 -0
- package/dist/background/background.schema.js +9 -6
- package/dist/background/background.types.d.ts +22 -0
- package/dist/{chunk-4TIJKN6O.js → chunk-23LZLBJK.js} +1 -1
- package/dist/{chunk-AFKTJXRA.js → chunk-2DFENQIJ.js} +18 -0
- package/dist/chunk-2FRUSVZS.js +489 -0
- package/dist/chunk-4FWNK4FD.js +159 -0
- package/dist/chunk-4U55VNC7.js +22 -0
- package/dist/{chunk-4JHOHWBI.js → chunk-4XTNPNQB.js} +1 -1
- package/dist/{chunk-HCP3GMHS.js → chunk-5N2GW72F.js} +15 -2
- package/dist/{chunk-42Y5IJKT.js → chunk-5U5FFFA3.js} +5 -1
- package/dist/{chunk-4F6AIZ36.js → chunk-5XMQPTHD.js} +1 -1
- package/dist/{chunk-GJOI2GHP.js → chunk-6GNXYAYP.js} +1 -1
- package/dist/chunk-6PKV5V2H.js +178 -0
- package/dist/{chunk-66XXW2Z2.js → chunk-6PXTNCBP.js} +2 -2
- package/dist/chunk-6X2NV665.js +33 -0
- package/dist/{chunk-LV4SNWZQ.js → chunk-6X4KUDMU.js} +8 -1
- package/dist/{chunk-ZMR77DDL.js → chunk-AW3EHF3L.js} +1 -1
- package/dist/chunk-CJYCVNW2.js +7 -0
- package/dist/chunk-COEOOQX2.js +133 -0
- package/dist/{chunk-XTIQWSFM.js → chunk-D5U7GMPX.js} +1 -1
- package/dist/{chunk-RAWJTA45.js → chunk-DEEHHLAP.js} +1 -1
- package/dist/chunk-DPUJDOKV.js +365 -0
- package/dist/{chunk-73HOFQQ5.js → chunk-ESS46GR4.js} +1 -1
- package/dist/chunk-EVMUJYUD.js +195 -0
- package/dist/{chunk-2BWAID3Y.js → chunk-FDBWYMZK.js} +29 -3
- package/dist/{chunk-K6Q5OUGK.js → chunk-GFZ7PITJ.js} +86 -10
- package/dist/chunk-H7KBLR4X.js +81 -0
- package/dist/{chunk-L7E2UT5A.js → chunk-HB7XZXDK.js} +5 -1
- package/dist/{chunk-VTYUBINW.js → chunk-HJ5EEZ47.js} +1 -1
- package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
- package/dist/{chunk-A4WENOUQ.js → chunk-I2NDWFDD.js} +52 -8
- package/dist/{chunk-RSDRMH54.js → chunk-IMZFOOVL.js} +1 -1
- package/dist/{chunk-3DZ74AKK.js → chunk-IN4M4X7P.js} +8 -2
- package/dist/chunk-IUAAXXNO.js +58 -0
- package/dist/{chunk-XJNECUPT.js → chunk-JC5UNIVM.js} +19 -0
- package/dist/chunk-JJ46T5XX.js +297 -0
- package/dist/{chunk-TUIFSVLI.js → chunk-JRDPUDT5.js} +30 -3
- package/dist/{chunk-24GFQU7T.js → chunk-JSZ4H6AQ.js} +1 -1
- package/dist/chunk-KU3YLF6D.js +106 -0
- package/dist/{chunk-5INR4NSL.js → chunk-LDDCNPHD.js} +1 -1
- package/dist/{chunk-DZXSWSBP.js → chunk-LMBAEIP6.js} +1 -1
- package/dist/chunk-LVQLWRCU.js +15 -0
- package/dist/chunk-MI7P3NX3.js +161 -0
- package/dist/chunk-O4SRD3J5.js +165 -0
- package/dist/{chunk-EMOORSQ7.js → chunk-OSQFPGLM.js} +45 -6
- package/dist/chunk-OZP6JFGT.js +162 -0
- package/dist/{chunk-NAEPSZAM.js → chunk-PDJSUH24.js} +24 -2
- package/dist/chunk-PP2UHLBA.js +50 -0
- package/dist/chunk-PVCEA4HZ.js +6 -0
- package/dist/{chunk-XLSVNUTK.js → chunk-R3VAKWKW.js} +1 -1
- package/dist/chunk-RIXI5D3R.js +89 -0
- package/dist/chunk-RLGQ5PZZ.js +16 -0
- package/dist/chunk-S7REMWW4.js +21 -0
- package/dist/{chunk-7VJ6BOA5.js → chunk-SMLXZUBH.js} +9 -1
- package/dist/{chunk-NXSWH3AE.js → chunk-T26ZVCL5.js} +1 -1
- package/dist/chunk-TOM66P2P.js +38 -0
- package/dist/chunk-UA5KS2PL.js +26 -0
- package/dist/{chunk-N6N74K6X.js → chunk-UBBKSYBY.js} +1 -1
- package/dist/{chunk-353WXS6S.js → chunk-UIRMA5KM.js} +1 -1
- package/dist/{chunk-GE4TLPWM.js → chunk-US5IKYCW.js} +1 -1
- package/dist/chunk-UXPGW4TI.js +101 -0
- package/dist/chunk-VQAXTIBW.js +100 -0
- package/dist/{chunk-WP2CX2JO.js → chunk-WELIKLZF.js} +22 -1
- package/dist/chunk-WPWOHGPI.js +172 -0
- package/dist/{chunk-GZ46OVGZ.js → chunk-WSVMLNTO.js} +1 -1
- package/dist/{chunk-4LZJXJJS.js → chunk-WXWPK3LI.js} +6 -2
- package/dist/{chunk-ZZGEVJKX.js → chunk-XBQMCEI2.js} +1 -1
- package/dist/{chunk-PHQRGDRQ.js → chunk-XGOFP7AQ.js} +5 -1
- package/dist/chunk-XJ2F2WO6.js +8 -0
- package/dist/chunk-XQCSGNXE.js +1580 -0
- package/dist/chunk-YBLDE5CG.js +42 -0
- package/dist/{chunk-JEYQU3JT.js → chunk-YHYTW5QA.js} +2 -18
- package/dist/{chunk-TLYDBT5F.js → chunk-YJJTISBN.js} +64 -7
- package/dist/{chunk-PZOWBI3B.js → chunk-YLL2QXY2.js} +7 -2
- package/dist/{chunk-MDEJHMKK.js → chunk-ZONJ66NE.js} +26 -5
- package/dist/class/class.codec.js +12 -9
- package/dist/class/class.parser.js +11 -8
- package/dist/class/class.schema.d.ts +55 -3
- package/dist/class/class.schema.js +9 -7
- package/dist/class/class.types.d.ts +48 -3
- package/dist/condition/condition.codec.d.ts +9 -0
- package/dist/condition/condition.entity-type.d.ts +2 -0
- package/dist/condition/condition.parser.d.ts +13 -0
- package/dist/condition/condition.parser.js +7 -0
- package/dist/condition/condition.schema.d.ts +29 -0
- package/dist/condition/condition.types.d.ts +15 -0
- package/dist/condition/condition.types.js +0 -0
- package/dist/dnd/constants.d.ts +22 -0
- package/dist/dnd/constants.js +9 -3
- package/dist/dnd/formula-tags.js +2 -2
- package/dist/dnd/math.d.ts +26 -0
- package/dist/dnd/math.js +12 -2
- package/dist/dnd/prose-tags.js +1 -1
- package/dist/dnd/resource-formula.d.ts +36 -10
- package/dist/dnd/resource-formula.js +10 -155
- package/dist/dnd/srd-tag-converter.js +3 -3
- package/dist/dnd/tag-grammar.js +1 -1
- package/dist/dnd/warn-once.d.ts +2 -0
- package/dist/dnd/warn-once.js +8 -0
- package/dist/entities/base-item-resolver.js +2 -2
- package/dist/entities/slug.d.ts +13 -0
- package/dist/entities/slug.js +5 -3
- package/dist/entities/wikilink-display.d.ts +3 -0
- package/dist/entities/wikilink-display.js +10 -0
- package/dist/feat/feat.category-codes.d.ts +32 -0
- package/dist/feat/feat.category-codes.js +14 -0
- package/dist/feat/feat.codec.js +7 -5
- package/dist/feat/feat.parser.js +6 -4
- package/dist/feat/feat.schema.d.ts +230 -0
- package/dist/feat/feat.schema.js +5 -3
- package/dist/feat/feat.types.d.ts +37 -1
- package/dist/index.js +2120 -6923
- package/dist/item/item.ai-schema.d.ts +1 -1
- package/dist/item/item.codec.js +4 -3
- package/dist/item/item.enrichment.js +4 -4
- package/dist/item/item.parser.js +3 -2
- package/dist/item/item.schema.d.ts +69 -3
- package/dist/item/item.schema.js +2 -1
- package/dist/item/item.types.d.ts +69 -3
- package/dist/monster/monster.codec.d.ts +7 -6
- package/dist/monster/monster.enrichment.js +4 -4
- package/dist/monster/monster.format.d.ts +135 -0
- package/dist/monster/monster.format.js +78 -0
- package/dist/monster/monster.parser.d.ts +6 -1
- package/dist/monster/monster.parser.js +10 -1
- package/dist/monster/monster.schema.d.ts +369 -0
- package/dist/monster/monster.schema.js +34 -0
- package/dist/monster/monster.types.d.ts +200 -14
- package/dist/optional-feature/optional-feature.codec.js +6 -5
- package/dist/optional-feature/optional-feature.parser.js +5 -4
- package/dist/optional-feature/optional-feature.schema.d.ts +176 -0
- package/dist/optional-feature/optional-feature.schema.js +4 -3
- package/dist/pack.d.ts +1 -1
- package/dist/pc/conditions.constants.d.ts +0 -1
- package/dist/pc/conditions.constants.js +1 -3
- package/dist/pc/decision-recognizer.js +1 -1
- package/dist/pc/pc.additional-spells.d.ts +47 -0
- package/dist/pc/pc.additional-spells.js +23 -0
- package/dist/pc/pc.asi-flatten.d.ts +18 -0
- package/dist/pc/pc.decision-engine.d.ts +106 -5
- package/dist/pc/pc.decision-engine.js +28 -10
- package/dist/pc/pc.defense-normalize.d.ts +11 -0
- package/dist/pc/pc.defense-normalize.js +6 -0
- package/dist/pc/pc.equipment.d.ts +30 -1
- package/dist/pc/pc.equipment.js +12 -7
- package/dist/pc/pc.feature-effects.d.ts +225 -11
- package/dist/pc/pc.feature-effects.js +21 -5
- package/dist/pc/pc.parser.js +2 -2
- package/dist/pc/pc.pools.d.ts +11 -3
- package/dist/pc/pc.pools.js +9 -13
- package/dist/pc/pc.proficiencies.d.ts +60 -4
- package/dist/pc/pc.proficiencies.js +89 -37
- package/dist/pc/pc.proficiency-grants.d.ts +58 -0
- package/dist/pc/pc.proficiency-normalize.d.ts +29 -0
- package/dist/pc/pc.proficiency-normalize.js +12 -0
- package/dist/pc/pc.proficiency-query.d.ts +13 -0
- package/dist/pc/pc.proficiency-query.js +5 -1
- package/dist/pc/pc.recalc.d.ts +69 -14
- package/dist/pc/pc.recalc.js +291 -142
- package/dist/pc/pc.resolver.d.ts +34 -8
- package/dist/pc/pc.resolver.js +27 -12
- package/dist/pc/pc.resource-seed.d.ts +3 -2
- package/dist/pc/pc.resource-seed.js +19 -17
- package/dist/pc/pc.resources.d.ts +87 -0
- package/dist/pc/pc.resources.js +21 -0
- package/dist/pc/pc.rest.d.ts +10 -1
- package/dist/pc/pc.rest.js +66 -12
- package/dist/pc/pc.schema.d.ts +147 -7
- package/dist/pc/pc.schema.js +4 -2
- package/dist/pc/pc.slotting.js +3 -3
- package/dist/pc/pc.spellcasting.d.ts +80 -7
- package/dist/pc/pc.spellcasting.js +18 -6
- package/dist/pc/pc.table-column.d.ts +23 -0
- package/dist/pc/pc.table-column.js +13 -3
- package/dist/pc/pc.types.d.ts +253 -16
- package/dist/pc/pool-layout.d.ts +47 -0
- package/dist/pc/pool-layout.js +16 -0
- package/dist/pc/roll-scope.d.ts +2 -0
- package/dist/pc/roll-scope.js +8 -0
- package/dist/pc/roll-tag-labels.d.ts +40 -0
- package/dist/pc/roll-tag-labels.js +17 -0
- package/dist/pc/spell-source.d.ts +21 -0
- package/dist/pc/spell-source.js +13 -0
- package/dist/race/race.codec.js +9 -7
- package/dist/race/race.parser.js +8 -6
- package/dist/race/race.schema.d.ts +51 -1
- package/dist/race/race.schema.js +8 -5
- package/dist/race/race.structural.d.ts +7 -0
- package/dist/race/race.structural.js +6 -0
- package/dist/race/race.types.d.ts +20 -0
- package/dist/schemas/attack-schema.d.ts +5 -0
- package/dist/schemas/caster-type-schema.d.ts +14 -0
- package/dist/schemas/caster-type-schema.js +6 -0
- package/dist/schemas/choice-schema.js +2 -2
- package/dist/schemas/entity-extras-schema.d.ts +53 -0
- package/dist/schemas/entity-extras-schema.js +10 -0
- package/dist/schemas/equipment-grant-schema.d.ts +2 -0
- package/dist/schemas/equipment-grant-schema.js +1 -1
- package/dist/schemas/feature-alias.d.ts +34 -0
- package/dist/schemas/feature-effect-schema.d.ts +124 -0
- package/dist/schemas/feature-effect-schema.js +1 -1
- package/dist/schemas/feature-schema.d.ts +8 -0
- package/dist/schemas/feature-schema.js +6 -4
- package/dist/schemas/resource-schema.d.ts +43 -0
- package/dist/schemas/resource-schema.js +1 -1
- package/dist/schemas/selection-pool-schema.d.ts +2 -0
- package/dist/schemas/selection-pool-schema.js +1 -1
- package/dist/spell/spell.access.js +23 -3
- package/dist/spell/spell.codec.js +4 -3
- package/dist/spell/spell.enrichment.js +4 -4
- package/dist/spell/spell.parser.d.ts +30 -0
- package/dist/spell/spell.parser.js +9 -2
- package/dist/spell/spell.scaling.d.ts +27 -6
- package/dist/spell/spell.scaling.js +25 -6
- package/dist/spell/spell.schema.d.ts +44 -2
- package/dist/spell/spell.schema.js +2 -1
- package/dist/spell/spell.types.d.ts +14 -0
- package/dist/subclass/subclass.codec.d.ts +1 -1
- package/dist/subclass/subclass.codec.js +11 -8
- package/dist/subclass/subclass.parser.js +10 -7
- package/dist/subclass/subclass.schema.d.ts +54 -7
- package/dist/subclass/subclass.schema.js +8 -6
- package/dist/subclass/subclass.types.d.ts +20 -2
- package/dist/types/choice.d.ts +56 -0
- package/dist/types/choice.js +21 -3
- package/dist/types/equipment-grant.d.ts +8 -1
- package/dist/types/feature-effect.d.ts +78 -23
- package/dist/types/feature.d.ts +70 -1
- package/dist/types/optional-feature.types.d.ts +28 -0
- package/dist/types/resource.d.ts +21 -1
- package/dist/types/selection-pool.d.ts +17 -2
- package/dist/weapon/weapon.classify.js +2 -2
- package/dist/weapon/weapon.codec.js +4 -3
- package/dist/weapon/weapon.parser.js +3 -2
- package/dist/weapon/weapon.schema.d.ts +4 -0
- package/dist/weapon/weapon.schema.js +2 -1
- package/dist/weapon/weapon.types.d.ts +8 -0
- package/package.json +2 -2
- package/dist/chunk-5MZSB6OM.js +0 -1089
- package/dist/chunk-5XUXXAQJ.js +0 -30
- package/dist/chunk-7P65Q426.js +0 -158
- package/dist/chunk-AIRFC5NQ.js +0 -143
- package/dist/chunk-BH6AMZVY.js +0 -25
- package/dist/chunk-CZPMTQOY.js +0 -68
- package/dist/chunk-M2EMFD6S.js +0 -269
- package/dist/chunk-N2JE7BWL.js +0 -81
- package/dist/chunk-NXR3BD2V.js +0 -90
- package/dist/chunk-Q5K6LWBZ.js +0 -45
- package/dist/chunk-TZK36BU7.js +0 -38
- package/dist/chunk-W7HWMMBF.js +0 -46
- package/dist/chunk-Y4I74YEG.js +0 -33
|
@@ -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;
|
|
@@ -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 {
|
|
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;
|
|
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;
|
|
12
41
|
export declare function computeSlotsAndAttacks(resolved: ResolvedCharacter, mods: Record<Ability, number>, profs: ProficienciesForQuery, registry: EntityRegistry, warnings: string[], proficiencyBonus: number, weaponAbilities?: WeaponAbilityOverride[], weaponMasteries?: string[]): DerivedEquipment;
|
|
13
42
|
export {};
|
package/dist/pc/pc.equipment.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buildUnarmedRow,
|
|
2
3
|
computeAppliedBonuses,
|
|
3
4
|
computeSlotsAndAttacks,
|
|
4
|
-
emptyAppliedBonuses
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import "../chunk-
|
|
5
|
+
emptyAppliedBonuses,
|
|
6
|
+
isWeaponSlugProficient
|
|
7
|
+
} from "../chunk-OSQFPGLM.js";
|
|
8
|
+
import "../chunk-UIRMA5KM.js";
|
|
9
|
+
import "../chunk-UBBKSYBY.js";
|
|
10
|
+
import "../chunk-RLGQ5PZZ.js";
|
|
8
11
|
import "../chunk-LWFTHJIE.js";
|
|
9
12
|
import "../chunk-DNAJRMNN.js";
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-WXWPK3LI.js";
|
|
14
|
+
import "../chunk-6X4KUDMU.js";
|
|
12
15
|
export {
|
|
16
|
+
buildUnarmedRow,
|
|
13
17
|
computeAppliedBonuses,
|
|
14
18
|
computeSlotsAndAttacks,
|
|
15
|
-
emptyAppliedBonuses
|
|
19
|
+
emptyAppliedBonuses,
|
|
20
|
+
isWeaponSlugProficient
|
|
16
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,84 @@ 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
|
+
/** Flat ability-score bumps from `ability-score-increase` effects whose `abilities` is a FIXED LIST (the three level-20
|
|
42
|
+
* capstones). `chosen` arms are the ASI SLOT's second encoding and MUST NOT fold (the synthesized feat decision pays
|
|
43
|
+
* them; a flat fold double-counts). No cap: `max` is declared and unread (user ruling 2026-09-03). */
|
|
44
|
+
ability_bonus: Partial<Record<Ability, number>>;
|
|
40
45
|
/** Max range per sense type granted by effects; 0 = none for that type. */
|
|
41
46
|
senses: Record<SenseType, number>;
|
|
42
|
-
/** One term per ac-bonus effect, labeled with the owning feature's name.
|
|
47
|
+
/** One term per ac-bonus effect, labeled with the owning feature's name. `condition` is the
|
|
48
|
+
* effect's situational qualifier, carried verbatim onto the ACTerm and never evaluated. */
|
|
43
49
|
ac_terms: {
|
|
44
50
|
value: number;
|
|
45
51
|
requires_armor: boolean;
|
|
46
52
|
label: string;
|
|
53
|
+
condition?: string;
|
|
47
54
|
}[];
|
|
48
|
-
resistances: string[];
|
|
49
|
-
condition_immunities: string[];
|
|
50
55
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
+
* The four defense buckets a feature effect can grant, as DefenseGrant ENTRIES (R4-G3a §3.2.1).
|
|
57
|
+
* Deduped by value case-insensitively, first spelling wins, trimmed · exactly the contract
|
|
58
|
+
* `pushUnique` had when these were `string[]` · but the SECOND granting feature's NAME is merged
|
|
59
|
+
* into `sources` rather than discarded. `immunity` and `vulnerability` fold REGARDLESS of
|
|
60
|
+
* `condition` (following `resistance`: dropping a real immunity is worse than showing it
|
|
61
|
+
* unqualified); `immune-condition` keeps its `while` skip. Values stay in the AUTHORED spelling ·
|
|
62
|
+
* toDefenseSlug runs in pc.recalc.ts, where composeDefenseEntries turns these into DefenseEntry.
|
|
63
|
+
*/
|
|
64
|
+
resistances: DefenseGrant[];
|
|
65
|
+
immunities: DefenseGrant[];
|
|
66
|
+
vulnerabilities: DefenseGrant[];
|
|
67
|
+
condition_immunities: DefenseGrant[];
|
|
68
|
+
/**
|
|
69
|
+
* skills, tools and languages are canonical slugs (toProfSlug: lowercase, U+2019
|
|
70
|
+
* folded to ASCII, whitespace collapsed to hyphens). armor and weapons entries
|
|
71
|
+
* are only `.toLowerCase()`d, NOT slugified, so an authored value keeps its
|
|
72
|
+
* spaces. recalc folds armor into armor.categories ONLY; it folds every weapons
|
|
73
|
+
* value into weapons.categories and additionally routes a non-category value
|
|
74
|
+
* into weapons.specific. saves are canonical ability keys, and are the one
|
|
75
|
+
* bucket a closed vocabulary genuinely guarantees: normalizeAbility returns null
|
|
76
|
+
* for anything else and the effect is dropped.
|
|
77
|
+
*
|
|
78
|
+
* What is INTENDED and what is ENFORCED differ here, and the gap is silent.
|
|
79
|
+
* INTENDED: skills/tools/languages land on ALL_SKILL_SLUGS / ALL_TOOLS /
|
|
80
|
+
* ALL_LANGUAGES, over which toProfSlug is the identity; armor values are
|
|
81
|
+
* CATEGORY words ("heavy"/"shield"); weapons values are a category word
|
|
82
|
+
* ("simple"/"martial", or an entity-level "martial-melee" form) or an authored
|
|
83
|
+
* weapon NAME. ENFORCED at runtime: nothing. feature-effect-schema's proficiency
|
|
84
|
+
* arm is `value: z.string().min(1)`, an OPEN string, and classifyProficiencyEffect
|
|
85
|
+
* does no membership test. The only guard is
|
|
86
|
+
* tests/srd-canonical/overlay-effect-slugs.test.ts, and it sees OVERLAY-AUTHORED
|
|
87
|
+
* data only · there skill/tool/language are checked against those three lists and
|
|
88
|
+
* weapon against the live gate, while armor and saving-throw are unchecked even
|
|
89
|
+
* there. Vault homebrew reaching this at runtime is checked by nothing.
|
|
90
|
+
*
|
|
91
|
+
* An off-vocabulary value does not crash and does not disappear · it becomes a
|
|
92
|
+
* value that can never coincide with the canonical one. Measured: an authored
|
|
93
|
+
* `{proficiency_type:"tool", value:"Playing Card Set"}` normalizes to
|
|
94
|
+
* "playing-card-set", which is NOT in ALL_TOOLS (that list carries the 2024
|
|
95
|
+
* spelling "playing-cards"). It still folds, and still renders as its own row
|
|
96
|
+
* labeled "Playing Card Set", but everything keyed on the canonical slug · the
|
|
97
|
+
* first-seen-wins dedupe in computeEffectiveProficiencies, an
|
|
98
|
+
* `overrides.tools.remove` suppression, the builder's already-satisfied
|
|
99
|
+
* exclusion · misses it, so the character ends up able to hold both.
|
|
100
|
+
*
|
|
101
|
+
* `skillExpertise` is NOT a seventh bucket · it is a SUBSET of `skills`, in the
|
|
102
|
+
* same canonical slug form, carrying the values whose effect set
|
|
103
|
+
* `expertise: true`. An expertise skill is pushed to BOTH, so every consumer
|
|
104
|
+
* that only knows about proficiency keeps working and recalc's `expSet` gains
|
|
105
|
+
* one source. SKILLS ONLY (spec §7.2): a tool or language effect carrying
|
|
106
|
+
* `expertise: true` folds as a plain proficiency and never lands here. It is
|
|
107
|
+
* not lost · since R4-G4 §9.2 the authored flag travels the GRANT channel
|
|
108
|
+
* instead (`EffectProficiencyGrant.expertise` -> `ProficiencyGrant.expertise`
|
|
109
|
+
* -> `ProficiencyEntry.expertise`), which is the channel the sheet panel and
|
|
110
|
+
* the proficiency modal read. Routing it here as well would put a value in the
|
|
111
|
+
* skill-expertise set that no skill key can ever match.
|
|
56
112
|
*/
|
|
57
113
|
proficiencies: {
|
|
58
114
|
skills: string[];
|
|
115
|
+
skillExpertise: string[];
|
|
59
116
|
tools: string[];
|
|
60
117
|
languages: string[];
|
|
61
118
|
saves: Ability[];
|
|
@@ -78,6 +135,12 @@ export interface FeatureEffectTotals {
|
|
|
78
135
|
* labeled with the owning feature's name.
|
|
79
136
|
*/
|
|
80
137
|
rollModifiers: RollModifierEntry[];
|
|
138
|
+
/**
|
|
139
|
+
* Order-preserving list of `save-outcome` entries (R4-G3a §5.3), labeled with the owning
|
|
140
|
+
* feature's name. Pass-through (no dedupe/merge); `ability: "any"` becomes an ABSENT ability,
|
|
141
|
+
* which the save chip reads as "every save".
|
|
142
|
+
*/
|
|
143
|
+
saveOutcomes: SaveOutcomeEntry[];
|
|
81
144
|
/**
|
|
82
145
|
* Lowest weapon-attack crit threshold (natural roll that scores a critical
|
|
83
146
|
* hit) granted by `crit-range` effects. Folds via Math.min from init 20, so
|
|
@@ -86,10 +149,11 @@ export interface FeatureEffectTotals {
|
|
|
86
149
|
*/
|
|
87
150
|
critRange: number;
|
|
88
151
|
/**
|
|
89
|
-
* Max
|
|
152
|
+
* Max EXTRA attacks per Attack action granted by `extra-attack` effects, never the total:
|
|
90
153
|
* Non-stacking (D&D Extra Attack features don't stack): folds via Math.max
|
|
91
154
|
* from init 0, so 0 = no extra attacks. recalc maps this onto
|
|
92
155
|
* DerivedStats.attacksPerAction as `1 + extraAttack`.
|
|
156
|
+
* A `scales_at` progression resolves BEFORE the max, at the effect's own source level (R4-G7 §7.3).
|
|
93
157
|
*/
|
|
94
158
|
extraAttack: number;
|
|
95
159
|
/**
|
|
@@ -115,8 +179,158 @@ export declare function emptyFeatureEffectTotals(): FeatureEffectTotals;
|
|
|
115
179
|
* is OFF by default, so callers passing no opts see activatable features fold to
|
|
116
180
|
* nothing (correct — a buff is off until toggled). Non-activatable features fold
|
|
117
181
|
* unconditionally regardless of opts.
|
|
182
|
+
*
|
|
183
|
+
* R4-G7 §7.3: `levelFor` answers "at what level does an effect from THIS source resolve", which is the rule
|
|
184
|
+
* `resourceLevelFor` (pc.resources.ts) already applies to a resource's own `scales_at`: the character's level
|
|
185
|
+
* in the granting class for a `class` source, the level in the class owning the subclass for a `subclass`
|
|
186
|
+
* source, the total level otherwise. Both production callers pass exactly that (dnd5e `pc.recalc.ts` and the
|
|
187
|
+
* plugin's builder `abilities-step.ts`); a caller that passes none leaves every `scales_at` unresolved and
|
|
188
|
+
* every effect at its BASE value, which is what a fixture with no character behind it should read.
|
|
118
189
|
*/
|
|
119
190
|
export interface FeatureEffectsOpts {
|
|
120
191
|
activeBuffs?: Set<string>;
|
|
192
|
+
levelFor?: (source: FeatureSource) => number;
|
|
121
193
|
}
|
|
194
|
+
/** Does this feature's effects fold right now? An activatable feature folds ONLY
|
|
195
|
+
* while its id is in the active set; a buff is off until toggled. Shared by
|
|
196
|
+
* computeFeatureEffects and the display-side proficiency collector so the fold
|
|
197
|
+
* and the display can never disagree about boons (spec fence F3). */
|
|
198
|
+
export declare function foldsNow(rf: ResolvedFeature, activeBuffs: Set<string>): boolean;
|
|
199
|
+
/** The ONE assembly of everything whose effects fold: authored features plus
|
|
200
|
+
* pool-granted and pool-selected boons, with the active-buff set.
|
|
201
|
+
*
|
|
202
|
+
* Returning BOTH halves is load-bearing: a caller handed only the feature list
|
|
203
|
+
* would drop activeBuffs and computeFeatureEffects would skip every activatable
|
|
204
|
+
* boon · exactly the display/gate divergence this helper exists to close.
|
|
205
|
+
*
|
|
206
|
+
* The guards defend against CAST-BUILT FIXTURES, not against the type: `features`,
|
|
207
|
+
* `pools` and `state` are all non-optional on ResolvedCharacter, but `tests/` is
|
|
208
|
+
* typechecked by nothing. Only fixtures that actually reach this function count,
|
|
209
|
+
* and today that is THREE entry points: recalc, the display-side walk
|
|
210
|
+
* (computeEffectiveProficiencies → collectProficiencyGrants), and this
|
|
211
|
+
* function's own direct tests. All three guards are LIVE. Counts below were
|
|
212
|
+
* measured by deleting each guard and running the suite · re-derive them the
|
|
213
|
+
* same way rather than trusting the numbers, which drift as tests are added:
|
|
214
|
+
* · `pools ?? []` is exercised by `emptyResolved()` in
|
|
215
|
+
* tests/pc-recalc-feature-effects.test.ts, which omits pools and IS fed to
|
|
216
|
+
* recalc, and by `dwarf()` in tests/pc-proficiency-effective.test.ts. This
|
|
217
|
+
* guard predates the extraction; it was already here. Deleting it: 149 tests
|
|
218
|
+
* red across 13 files.
|
|
219
|
+
* · `state?.` is exercised by the "survives a cast-built fixture with no pools
|
|
220
|
+
* and no state" case in tests/pc-feature-effects.test.ts, which calls this
|
|
221
|
+
* function directly with `{ features: [...] } as never`, and again by
|
|
222
|
+
* `dwarf()`. Deleting it: 11 red across 2 files.
|
|
223
|
+
* · `features ?? []` is exercised by `dwarf()` in
|
|
224
|
+
* tests/pc-proficiency-effective.test.ts, which omits `features` and reaches
|
|
225
|
+
* here through the display walk. Deleting it: 7 red, all in that file.
|
|
226
|
+
* ⚠️ This bullet used to read "NOT exercised today, and is the one kept
|
|
227
|
+
* purely for the display-side callers". That was TRUE when written and was
|
|
228
|
+
* falsified LATER IN THIS SAME PHASE by the tasks that wired the display
|
|
229
|
+
* path, because nobody re-read it. A guard's own justification is the thing
|
|
230
|
+
* most likely to go stale under you · re-run the deletion before restating
|
|
231
|
+
* any of these three.
|
|
232
|
+
* Do not "simplify" any of the three away. */
|
|
233
|
+
export declare function assembleEffectFeatures(resolved: ResolvedCharacter): {
|
|
234
|
+
features: ResolvedFeature[];
|
|
235
|
+
activeBuffs: Set<string>;
|
|
236
|
+
};
|
|
237
|
+
/** Spec R4-G1a D2. Whether an effect changes the character it is written on. `subject` is absent on every SRD
|
|
238
|
+
* effect (=== "self"); the converter emits "self" today and will emit other creatures later (Hound of Ill Omen
|
|
239
|
+
* imposes disadvantage on a TARGET). A non-self effect never folds onto the PC: not here, not in
|
|
240
|
+
* collectProficiencyEffectGrants, not in unarmoredACBreakdown, not in recalc's weapon-ability scan. Its FEATURE
|
|
241
|
+
* still resolves and renders (it is not buildOnly); R4-G3a §4 gives it a row-local caption.
|
|
242
|
+
*
|
|
243
|
+
* R4-G3a §4.4: a subject that is neither absent nor "self" warns ONCE per distinct subject string
|
|
244
|
+
* (`warnOnce`, keyed on the subject). The converter's vocabulary here is open, so an unexpected
|
|
245
|
+
* spelling ("Self", "target creature") silently stops folding today; the warning names it without
|
|
246
|
+
* changing the answer. Never a refusal: the return value is unchanged. */
|
|
247
|
+
export declare function foldsOnSelf(eff: {
|
|
248
|
+
subject?: string;
|
|
249
|
+
}): boolean;
|
|
250
|
+
/** The effects of a feature that fold on the character: every reader of `feature.effects` for a derived stat goes through this. */
|
|
251
|
+
export declare function selfEffectsOf(f: {
|
|
252
|
+
effects?: FeatureEffect[];
|
|
253
|
+
}): FeatureEffect[];
|
|
122
254
|
export declare function computeFeatureEffects(features: ResolvedFeature[], opts?: FeatureEffectsOpts): FeatureEffectTotals;
|
|
255
|
+
/** The ONE interpreter of `kind: "proficiency"` effects (spec fence F3).
|
|
256
|
+
*
|
|
257
|
+
* Two consumers with different needs, so the return carries BOTH forms:
|
|
258
|
+
* `applyEffect` pushes `value` (normalized) into FeatureEffectTotals, while the
|
|
259
|
+
* display collector reads `raw` for armor/weapons · composeGrantEntries stores
|
|
260
|
+
* the raw authored string as ProficiencyEntry.value (spec §4.8 #1) and computes
|
|
261
|
+
* its dedupe key separately. Tools/languages read `value`.
|
|
262
|
+
*
|
|
263
|
+
* `saves` is discriminated because FeatureEffectTotals.proficiencies.saves is
|
|
264
|
+
* Ability[], not string[] (fence F1). A null return means "not a proficiency
|
|
265
|
+
* effect, or an unresolvable ability".
|
|
266
|
+
*
|
|
267
|
+
* `expertise` is REQUIRED and always a boolean, never absent: the authored key
|
|
268
|
+
* is optional, so folding it here once means no consumer has to tell
|
|
269
|
+
* `undefined` from `false`. It is reported for EVERY bucket · deciding that
|
|
270
|
+
* only skills act on it belongs to the fold (spec §7.2), not to the
|
|
271
|
+
* classification. */
|
|
272
|
+
export type ProficiencyClassification = {
|
|
273
|
+
bucket: "skills" | "tools" | "languages" | "armor" | "weapons";
|
|
274
|
+
value: string;
|
|
275
|
+
raw: string;
|
|
276
|
+
expertise: boolean;
|
|
277
|
+
} | {
|
|
278
|
+
bucket: "saves";
|
|
279
|
+
value: Ability;
|
|
280
|
+
raw: string;
|
|
281
|
+
expertise: boolean;
|
|
282
|
+
};
|
|
283
|
+
export declare function classifyProficiencyEffect(eff: FeatureEffect): ProficiencyClassification | null;
|
|
284
|
+
/** One proficiency an effect grants, tagged with the source it came from.
|
|
285
|
+
*
|
|
286
|
+
* BOTH forms are carried because the two display consumers need different ones
|
|
287
|
+
* and, since the tool/language canonicalization landed, they no longer coincide:
|
|
288
|
+
* armor/weapons read `raw` (composeGrantEntries stores the raw authored string
|
|
289
|
+
* as ProficiencyEntry.value and keys its dedupe on toProfSlug separately), while
|
|
290
|
+
* tools/languages read `value`, the canonical slug the effective-proficiency
|
|
291
|
+
* matcher matches its vocabulary and suppressions on. Reaching for the wrong
|
|
292
|
+
* field is silently wrong, not harmlessly identical.
|
|
293
|
+
*
|
|
294
|
+
* `sourceSlug` is the source's slug VERBATIM (namespace and all), because the
|
|
295
|
+
* consumer resolves it against the resolved character's entities to get a
|
|
296
|
+
* display name. */
|
|
297
|
+
export interface EffectProficiencyGrant {
|
|
298
|
+
value: string;
|
|
299
|
+
raw: string;
|
|
300
|
+
sourceKind: FeatureSource["kind"];
|
|
301
|
+
sourceSlug: string;
|
|
302
|
+
/**
|
|
303
|
+
* Set (to `true`) only when the granting effect carried `expertise: true`, and
|
|
304
|
+
* ABSENT otherwise, so a plain grant keeps the exact four-key shape the two
|
|
305
|
+
* display consumers already read. Reported for every bucket, unlike the fold,
|
|
306
|
+
* which routes skills only (spec §7.2). CONSUMED since R4-G4 §9.2 by
|
|
307
|
+
* `toGrants` in pc.proficiency-grants.ts, which spreads it onto
|
|
308
|
+
* `ProficiencyGrant` for every bucket; the tools display is the one reader
|
|
309
|
+
* today, and it reads this rather than re-deriving it from the raw effects.
|
|
310
|
+
*/
|
|
311
|
+
expertise?: boolean;
|
|
312
|
+
}
|
|
313
|
+
export type EffectProficiencyGrants = Record<"skills" | "tools" | "languages" | "saves" | "armor" | "weapons", EffectProficiencyGrant[]>;
|
|
314
|
+
/** Every proficiency an effect grants, tagged with the FeatureSource it came from.
|
|
315
|
+
*
|
|
316
|
+
* Deliberately NOT deduped: composeGrantEntries collects distinct SOURCES per
|
|
317
|
+
* value, so collapsing on value alone here would silently drop the second
|
|
318
|
+
* granting entity and blank half the provenance.
|
|
319
|
+
*
|
|
320
|
+
* `skills` and `saves` are collected because classifyProficiencyEffect's bucket
|
|
321
|
+
* union has SIX members and this function indexes the record by it · drop either
|
|
322
|
+
* key and `out[c.bucket].push(...)` is a type error, and a hand-built record
|
|
323
|
+
* missing one crashes on the first such effect. They have NO display consumer:
|
|
324
|
+
* effect-granted skills already reach the sheet via recalc's skill union and
|
|
325
|
+
* saves via its save set, and the Proficiencies panel has only four rows. No
|
|
326
|
+
* test asserts either bucket, and `toGrants` in pc.proficiency-grants.ts reads
|
|
327
|
+
* only the other four. That is deliberate, not an oversight.
|
|
328
|
+
*
|
|
329
|
+
* Reads the same three rules as the fold, and NOTHING else: foldsNow decides
|
|
330
|
+
* what is active, selfEffectsOf decides whose numbers an effect changes and
|
|
331
|
+
* classifyProficiencyEffect decides what a proficiency effect means (spec fence
|
|
332
|
+
* F3), so the display can never disagree with the engine.
|
|
333
|
+
*
|
|
334
|
+
* Reads only effects that fold on self (`selfEffectsOf`), the same predicate as
|
|
335
|
+
* the fold, so a non-self proficiency is neither granted nor displayed. */
|
|
336
|
+
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
emptyFeatureEffectTotals,
|
|
7
|
+
foldsNow,
|
|
8
|
+
foldsOnSelf,
|
|
9
|
+
selfEffectsOf
|
|
10
|
+
} from "../chunk-JJ46T5XX.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
|
};
|
package/dist/pc/pc.parser.js
CHANGED
package/dist/pc/pc.pools.d.ts
CHANGED
|
@@ -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[];
|
package/dist/pc/pc.pools.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
levelPrereqMax,
|
|
3
3
|
resolveAllPools,
|
|
4
|
-
resolvePool
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
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,8 +1,64 @@
|
|
|
1
1
|
import type { ResolvedCharacter } from "./pc.types.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`. */
|
|
2
13
|
export interface ProficiencyAggregate {
|
|
3
|
-
armor:
|
|
4
|
-
weapons:
|
|
5
|
-
tools:
|
|
6
|
-
languages:
|
|
14
|
+
armor: ProficiencyEntry[];
|
|
15
|
+
weapons: ProficiencyEntry[];
|
|
16
|
+
tools: ProficiencyEntry[];
|
|
17
|
+
languages: ProficiencyEntry[];
|
|
7
18
|
}
|
|
19
|
+
/** Normalized intermediate of every proficiency/language source the DISPLAY path
|
|
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. */
|
|
43
|
+
export interface ProficiencySources {
|
|
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[];
|
|
59
|
+
chosenLanguages: string[];
|
|
60
|
+
chosenTools: string[];
|
|
61
|
+
chosenWeapons: string[];
|
|
62
|
+
}
|
|
63
|
+
export declare function collectProficiencySources(resolved: ResolvedCharacter): ProficiencySources;
|
|
8
64
|
export declare function aggregateProficiencies(resolved: ResolvedCharacter): ProficiencyAggregate;
|