@archivist-gg/dnd5e 0.3.1 → 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 +26 -0
- package/dist/background/background.schema.js +5 -4
- 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-6IRYTM5V.js → chunk-DPUJDOKV.js} +119 -28
- 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-BIK4DFWK.js → chunk-HJ5EEZ47.js} +1 -1
- package/dist/{chunk-6NK2MUD3.js → chunk-HZA7OE7N.js} +9 -2
- package/dist/{chunk-TN6ZLTNU.js → chunk-I2NDWFDD.js} +51 -4
- 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-SE7AZDR3.js → chunk-JRDPUDT5.js} +29 -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-5ZBE4CWV.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-D3CLOBE3.js → chunk-OSQFPGLM.js} +44 -8
- 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 +2033 -6907
- 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 +105 -17
- package/dist/pc/pc.decision-engine.js +28 -12
- 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 -8
- 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 +54 -29
- package/dist/pc/pc.proficiencies.js +74 -95
- package/dist/pc/pc.proficiency-grants.d.ts +58 -0
- package/dist/pc/pc.proficiency-normalize.d.ts +24 -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 +1 -1
- package/dist/pc/pc.recalc.d.ts +69 -14
- package/dist/pc/pc.recalc.js +261 -122
- 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 +139 -3
- package/dist/pc/pc.schema.js +4 -2
- package/dist/pc/pc.slotting.js +3 -3
- package/dist/pc/pc.spellcasting.d.ts +75 -6
- package/dist/pc/pc.spellcasting.js +16 -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 +251 -12
- 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 +53 -2
- package/dist/types/choice.js +13 -1
- 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-5XUXXAQJ.js +0 -30
- package/dist/chunk-7P65Q426.js +0 -158
- package/dist/chunk-7TEK5KK7.js +0 -49
- package/dist/chunk-AIRFC5NQ.js +0 -143
- package/dist/chunk-CZPMTQOY.js +0 -68
- package/dist/chunk-N2JE7BWL.js +0 -81
- package/dist/chunk-NXR3BD2V.js +0 -90
- package/dist/chunk-O5EYMKFH.js +0 -6
- package/dist/chunk-Q5K6LWBZ.js +0 -45
- package/dist/chunk-TNUFPIXI.js +0 -1110
- package/dist/chunk-TZK36BU7.js +0 -38
- package/dist/chunk-W7HWMMBF.js +0 -46
- package/dist/chunk-Y4I74YEG.js +0 -33
package/dist/pc/pc.slotting.js
CHANGED
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
isShieldArmor,
|
|
7
7
|
isWeaponEntity,
|
|
8
8
|
resolveEntityForEntry
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-UBBKSYBY.js";
|
|
10
|
+
import "../chunk-WXWPK3LI.js";
|
|
11
|
+
import "../chunk-6X4KUDMU.js";
|
|
12
12
|
export {
|
|
13
13
|
defaultSlotForType,
|
|
14
14
|
effectiveArmor,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EntityRegistry } from "@archivist-gg/core";
|
|
1
2
|
import type { Ability } from "@archivist-gg/dnd5e";
|
|
2
3
|
import type { CharacterOverrides, KnownSpellEntry, ResolvedClass } from "./pc.types.js";
|
|
3
4
|
import type { CasterType } from "@archivist-gg/dnd5e/class/class.types";
|
|
@@ -5,17 +6,19 @@ export interface SpellcastingProfile {
|
|
|
5
6
|
ability: Ability;
|
|
6
7
|
casterType: CasterType;
|
|
7
8
|
preparation: "known" | "prepared";
|
|
8
|
-
|
|
9
|
+
/** null when neither block names a list (the converter's EK/AT). Read by the known-spell attribution guard
|
|
10
|
+
* (`attributeUnclassedSpell`, guard (i)) and carried to `SpellcastingClassInfo.spellList`. */
|
|
11
|
+
spellList: string | null;
|
|
9
12
|
table: Record<number, {
|
|
10
13
|
columns?: Record<string, string | number>;
|
|
11
14
|
}>;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
|
-
* Data-driven spellcasting profile for a resolved class
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Data-driven spellcasting profile for a resolved class, composed FIELD BY FIELD (R4-G1a D7): the caster type comes
|
|
18
|
+
* from the subclass block, else the class block, and without one the entity is not a caster (null: a subclass that
|
|
19
|
+
* only names a save ability grants no Spells tab). The table is the SUBCLASS table when the subclass block supplies
|
|
20
|
+
* the caster type, else the class table, else {}. Preparation is explicit, else inferred from a Prepared Spells /
|
|
21
|
+
* Spells Prepared column in the resolved table, else known. No hardcoded class knowledge.
|
|
19
22
|
*/
|
|
20
23
|
export declare function resolveSpellcasting(rc: ResolvedClass): SpellcastingProfile | null;
|
|
21
24
|
/** The ability a class actually casts with: a per-class override wins, else the
|
|
@@ -30,6 +33,44 @@ export interface NormalizedKnownSpell {
|
|
|
30
33
|
alwaysPrepared: boolean;
|
|
31
34
|
}
|
|
32
35
|
export declare function normalizeKnownSpell(entry: KnownSpellEntry): NormalizedKnownSpell;
|
|
36
|
+
/** One caster class as the attribution rule reads it: a class slug and its profile's `spellList` (null when none is named).
|
|
37
|
+
* The slug is compared through `baseClassName` and also looks up the registered `class` document for its compendium, so it
|
|
38
|
+
* must be a registered class slug: the resolver passes the class-ref slug its own lookup used, the add drawer the resolved
|
|
39
|
+
* entity's `slug` (equal to the registered slug on every bundle and converter class document, and backfilled from it when a
|
|
40
|
+
* custom body omits it). */
|
|
41
|
+
export interface AttributionCaster {
|
|
42
|
+
classSlug: string;
|
|
43
|
+
spellList: string | null;
|
|
44
|
+
}
|
|
45
|
+
/** Every base class name a spell of ONE compendium lists in its `classes`, the compendium read off the registered spell
|
|
46
|
+
* document (`RegisteredEntity.compendium`). ONE walk of the spell bucket per registry fills every compendium's set at once,
|
|
47
|
+
* so each compendium is computed once; the walk repeats only when `registry.count()` changes (an entity registered under a
|
|
48
|
+
* new slug, or one removed). The core registry exposes no change generation, version or event, so the count is the only
|
|
49
|
+
* key. Known gap: re-registering an existing spell in place (same slug, same count; the plugin's `updateEntity` does this)
|
|
50
|
+
* with a different `classes` list is not seen until the count next moves. A compendium with no spells gets the empty set. */
|
|
51
|
+
export declare function listedSpellClassNames(registry: EntityRegistry, compendium: string): ReadonlySet<string>;
|
|
52
|
+
/**
|
|
53
|
+
* R4-G7 T8 RIDER-19 (F-PACT) and its fix rounds 1 and 2 (review C-1): the caster class an UN-CLASSED known spell belongs to.
|
|
54
|
+
* The ONE rule, read by the resolver (an entry with no `class:`) and by the Spells tab's add drawer (the `class:` it writes).
|
|
55
|
+
* The spell stays with the FIRST caster class (class-entry order) unless the first caster's own list is OBSERVABLE and does
|
|
56
|
+
* not name the spell, in which case it goes to the first LATER caster whose base class name the spell's `classes` names.
|
|
57
|
+
* Observable means (i) the first caster's profile names a list (`spellList` non-null; the converter's Arcane Trickster and
|
|
58
|
+
* Eldritch Knight name none, and their spells say `wizard`, never `rogue` / `fighter`) AND (ii) its base class name is
|
|
59
|
+
* listed on at least one spell of the first class document's OWN compendium (`RegisteredEntity.compendium` of the registered
|
|
60
|
+
* `class` document and of each spell document): the SRD 5e bundle lists `paladin` on no spell (DATA-SRD) while a real vault
|
|
61
|
+
* also holds SRD 2024 and converted books that do, so only the class's own book can speak for its list. A class document the
|
|
62
|
+
* registry does not hold, or a compendium with no spells, is unobservable. So a single caster, a spell the first caster's list
|
|
63
|
+
* names (Detect Magic on a Paladin / Warlock), a spell no caster names, and every spell of an unobservable first caster
|
|
64
|
+
* resolve exactly as before RIDER-19; Armor of Agathys (`classes: [warlock]`) on a Player's Handbook Paladin-first Paladin /
|
|
65
|
+
* Warlock goes to the Warlock. Null only when there is no caster class.
|
|
66
|
+
* Consequences a move carries: the spell's DC / attack become the new class's, it follows that class's Pact Magic routing,
|
|
67
|
+
* and a plain entry (no prepared flag) moved from a `known` caster to a `prepared` one is not castable until prepared.
|
|
68
|
+
* Known limit: a list EXTENSION on an observable first caster (a 2014 Fiend Warlock's patron-granted Burning Hands, Bard
|
|
69
|
+
* Magical Secrets, Divine Soul) is not visible in the spell's base-list `classes`, so such a spell moves to a later caster
|
|
70
|
+
* that names it; an explicit `class:` is the override. The class lookup and the registry walk for (ii) run only when a move
|
|
71
|
+
* is in question.
|
|
72
|
+
*/
|
|
73
|
+
export declare function attributeUnclassedSpell(spellClasses: readonly unknown[] | undefined, casters: readonly AttributionCaster[], registry: EntityRegistry): string | null;
|
|
33
74
|
export interface CasterClassInput {
|
|
34
75
|
casterType: CasterType;
|
|
35
76
|
level: number;
|
|
@@ -43,6 +84,16 @@ export interface DerivedSpellSlots {
|
|
|
43
84
|
total: number;
|
|
44
85
|
} | null;
|
|
45
86
|
}
|
|
87
|
+
type SlotCaster = Exclude<CasterType, "pact">;
|
|
88
|
+
/** The single-class slot table for a progression. Exhaustive by construction: an unhandled member is a COMPILE
|
|
89
|
+
* error at the `never` assignment (a bare switch statement would fall through silently). */
|
|
90
|
+
export declare function slotTableFor(caster: SlotCaster): number[][];
|
|
91
|
+
/**
|
|
92
|
+
* Standard spell slots plus Pact Magic for a character's casting classes. A SINGLE regular caster reads its own
|
|
93
|
+
* progression table through `slotTableFor` (the artificer row included: level-1 slots, then the half-caster rows).
|
|
94
|
+
* TWO OR MORE pool into a multiclass caster level, `full + floor(half/2) + floor(third/3) + ceil(artificer/2)` on
|
|
95
|
+
* the SUMS, read off FULL_CASTER_SLOTS. Warlock levels never join that pool: they produce a separate pact entry.
|
|
96
|
+
*/
|
|
46
97
|
export declare function deriveSpellSlots(classes: CasterClassInput[]): DerivedSpellSlots;
|
|
47
98
|
export interface LimitClassInput {
|
|
48
99
|
classSlug: string;
|
|
@@ -57,4 +108,22 @@ export interface SpellLimit {
|
|
|
57
108
|
cantripsKnown: number | null;
|
|
58
109
|
preparedOrKnown: number | null;
|
|
59
110
|
}
|
|
111
|
+
/** The converter spells the 2024 column two ways; the bundle uses the first. Serves the PREPARED branch of
|
|
112
|
+
* `computeSpellLimits` and `resolveSpellcasting`'s preparation inference (`hasPreparedColumn`). The KNOWN branch
|
|
113
|
+
* keeps its own candidates (the XPHB Sorcerer and Warlock are known casters whose count sits under
|
|
114
|
+
* "Prepared Spells"). */
|
|
115
|
+
export declare const PREPARED_COLUMNS: string[];
|
|
116
|
+
/** The level term of the prepared-count FALLBACK (used only when no table column supplies the count). Slots round
|
|
117
|
+
* the artificer progression UP; its prepared count rounds DOWN (TCE: "half your artificer level, rounded down").
|
|
118
|
+
* `third` is a DELTA from the shipped `else` arm, which used the whole `level`: reachable in principle now that
|
|
119
|
+
* `resolveSpellcasting` infers `prepared` for the XPHB Arcane Trickster, whose table supplies the count, so no
|
|
120
|
+
* shipped number moves (spec D6c, §8). */
|
|
121
|
+
export declare function preparedLevelTerm(caster: CasterType, level: number): number;
|
|
122
|
+
/**
|
|
123
|
+
* Cantrip and prepared/known counts per casting class. The PREPARED count is TABLE-FIRST (R4-G1a D6c): a
|
|
124
|
+
* PREPARED_COLUMNS cell on the class table row wins outright, and only a row without one falls back to the formula
|
|
125
|
+
* `max(1, abilityMod + preparedLevelTerm(casterType, level))`. The KNOWN count is read from the table alone and is
|
|
126
|
+
* null when the row carries no candidate column.
|
|
127
|
+
*/
|
|
60
128
|
export declare function computeSpellLimits(inputs: LimitClassInput[]): SpellLimit[];
|
|
129
|
+
export {};
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PREPARED_COLUMNS,
|
|
3
|
+
attributeUnclassedSpell,
|
|
2
4
|
computeSpellLimits,
|
|
3
5
|
deriveSpellSlots,
|
|
4
6
|
effectiveSpellcastingAbility,
|
|
7
|
+
listedSpellClassNames,
|
|
5
8
|
normalizeKnownSpell,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
preparedLevelTerm,
|
|
10
|
+
resolveSpellcasting,
|
|
11
|
+
slotTableFor
|
|
12
|
+
} from "../chunk-DPUJDOKV.js";
|
|
13
|
+
import "../chunk-IUAAXXNO.js";
|
|
9
14
|
import "../chunk-VTTA2ALZ.js";
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
15
|
+
import "../chunk-PDJSUH24.js";
|
|
16
|
+
import "../chunk-WELIKLZF.js";
|
|
12
17
|
export {
|
|
18
|
+
PREPARED_COLUMNS,
|
|
19
|
+
attributeUnclassedSpell,
|
|
13
20
|
computeSpellLimits,
|
|
14
21
|
deriveSpellSlots,
|
|
15
22
|
effectiveSpellcastingAbility,
|
|
23
|
+
listedSpellClassNames,
|
|
16
24
|
normalizeKnownSpell,
|
|
17
|
-
|
|
25
|
+
preparedLevelTerm,
|
|
26
|
+
resolveSpellcasting,
|
|
27
|
+
slotTableFor
|
|
18
28
|
};
|
|
@@ -10,3 +10,26 @@ export declare function readTableColumn(table: Record<number, {
|
|
|
10
10
|
export declare function numericColumnsAt(table: Record<number, {
|
|
11
11
|
columns?: Record<string, string | number>;
|
|
12
12
|
}> | undefined, level: number): Record<string, number>;
|
|
13
|
+
/** Class-table COLUMN names per `select-entity` choice id (R4-G5 §6.2, invariant 3): the DATA route that makes a
|
|
14
|
+
* levelled pick count GROW. MEASURED 2026-09-07 on both corpora: the Fighter 2024's `weapon-mastery` choice
|
|
15
|
+
* authors `count: 3` at levels 1 / 4 / 10 / 16 while the class table's "Weapon Mastery" column reads
|
|
16
|
+
* 3 / 4 / 5 / 6 (byte-identical between the converter document and the SRD 2024 bundle record); the Barbarian
|
|
17
|
+
* 2024's column reads 2 / 3 / 4 against an authored 2; Paladin / Ranger / Rogue 2024 carry `count: 2` and NO
|
|
18
|
+
* column, which is the correct RAW count. Read only INSIDE dnd5e (`buildDecisionLedger`). G7 handoff: the
|
|
19
|
+
* converter emits `count.column` on the choice itself and this table retires. */
|
|
20
|
+
export declare const COUNT_COLUMNS: Readonly<Record<string, string[]>>;
|
|
21
|
+
/** The column keys for a choice id, by OWN-property lookup (`derivePoolLayout`'s precedent, invariant 3). The
|
|
22
|
+
* guard is LOAD-BEARING, not defensive: `readTableColumn` above iterates its `keys` with `for ... of`, so a
|
|
23
|
+
* plain index on a choice id of `constructor` hands it the Object constructor FUNCTION and THROWS while the
|
|
24
|
+
* ledger is being built. Returns undefined for every id the table does not own. */
|
|
25
|
+
export declare function countColumnsFor(choiceId: string): string[] | undefined;
|
|
26
|
+
/** A DICE cell (`1d6`) of a (sub)class table column at `level`, or null when absent, non-string or not dice-shaped.
|
|
27
|
+
* `readTableColumn` / `numericColumnsAt` parse INTEGERS and would read `1d6` as 1 (R4-G6b §5.3). Own-property
|
|
28
|
+
* reads only: a key of `constructor` hands back the prototype's function otherwise. */
|
|
29
|
+
export declare function diceColumnAt(table: Record<number, {
|
|
30
|
+
columns?: Record<string, string | number>;
|
|
31
|
+
}> | undefined, level: number, key: string): string | null;
|
|
32
|
+
/** The class-table COLUMN that scales an unarmed strike's die, per FEATURE id slug (R4-G6b §5.4's synthetic; the
|
|
33
|
+
* `COUNT_COLUMNS` shape). Mirrors the G7 overlay entry this synthetic retires for SRD data; kept for homebrew. */
|
|
34
|
+
export declare const UNARMED_DIE_COLUMNS: Readonly<Record<string, string>>;
|
|
35
|
+
export declare function unarmedDieColumnFor(featureSlug: string): string | undefined;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
COUNT_COLUMNS,
|
|
3
|
+
UNARMED_DIE_COLUMNS,
|
|
4
|
+
countColumnsFor,
|
|
5
|
+
diceColumnAt,
|
|
2
6
|
numericColumnsAt,
|
|
3
|
-
readTableColumn
|
|
4
|
-
|
|
7
|
+
readTableColumn,
|
|
8
|
+
unarmedDieColumnFor
|
|
9
|
+
} from "../chunk-IUAAXXNO.js";
|
|
5
10
|
export {
|
|
11
|
+
COUNT_COLUMNS,
|
|
12
|
+
UNARMED_DIE_COLUMNS,
|
|
13
|
+
countColumnsFor,
|
|
14
|
+
diceColumnAt,
|
|
6
15
|
numericColumnsAt,
|
|
7
|
-
readTableColumn
|
|
16
|
+
readTableColumn,
|
|
17
|
+
unarmedDieColumnFor
|
|
8
18
|
};
|
package/dist/pc/pc.types.d.ts
CHANGED
|
@@ -2,15 +2,18 @@ import type { Ability } from "../types/choice.js";
|
|
|
2
2
|
import type { ArmorEntity } from "../armor/armor.types.js";
|
|
3
3
|
import type { WeaponEntity } from "../weapon/weapon.types.js";
|
|
4
4
|
import type { ItemEntity } from "../item/item.types.js";
|
|
5
|
-
import type { SkillSlug, Feature } from "@archivist-gg/dnd5e";
|
|
5
|
+
import type { SkillSlug, Feature, Choice } from "@archivist-gg/dnd5e";
|
|
6
6
|
import type { ClassEntity } from "@archivist-gg/dnd5e/class/class.types";
|
|
7
|
+
import type { CasterType } from "@archivist-gg/dnd5e/schemas/caster-type-schema";
|
|
7
8
|
import type { RaceEntity } from "@archivist-gg/dnd5e/race/race.types";
|
|
8
9
|
import type { SubclassEntity } from "@archivist-gg/dnd5e/subclass/subclass.types";
|
|
9
10
|
import type { BackgroundEntity } from "@archivist-gg/dnd5e/background/background.types";
|
|
10
11
|
import type { FeatEntity } from "@archivist-gg/dnd5e/feat/feat.types";
|
|
11
12
|
import type { Spell } from "@archivist-gg/dnd5e/spell/spell.types";
|
|
12
13
|
import type { OptionalFeatureEntity } from "@archivist-gg/dnd5e/types/optional-feature.types";
|
|
14
|
+
import type { PoolLayout } from "../types/selection-pool.js";
|
|
13
15
|
import type { ConditionSlug } from "./conditions.constants.js";
|
|
16
|
+
import type { ResourceIndex } from "./pc.resources.js";
|
|
14
17
|
/** A persisted decision value: entity slug / inline value (string), multi-select
|
|
15
18
|
* slugs (string[]), or an ability-points allocation. Stale/odd legacy values
|
|
16
19
|
* survive parsing (schema is permissive); readers narrow defensively. */
|
|
@@ -103,6 +106,11 @@ export interface KnownSpellObject {
|
|
|
103
106
|
}
|
|
104
107
|
export type KnownSpellEntry = string | KnownSpellObject;
|
|
105
108
|
export type PassiveKind = "perception" | "investigation" | "insight";
|
|
109
|
+
/** The manual proficiency tri, in ONE place. The runtime twin is `proficiencyTri`
|
|
110
|
+
* (pc.schema.ts); `pc.recalc.ts` imports this name for the skills tri, and the plugin's
|
|
111
|
+
* `CharacterEditState.setToolProficiency` and `ProficiencyEditModal` import it for the
|
|
112
|
+
* tools tri (R4-G4 §9.3, UR1), so no consumer re-spells the union. */
|
|
113
|
+
export type ProficiencyTri = "none" | "proficient" | "expertise";
|
|
106
114
|
export interface CharacterOverrides {
|
|
107
115
|
scores?: Partial<Record<Ability, number>>;
|
|
108
116
|
saves?: Partial<Record<Ability, {
|
|
@@ -111,7 +119,7 @@ export interface CharacterOverrides {
|
|
|
111
119
|
}>>;
|
|
112
120
|
skills?: Partial<Record<SkillSlug, {
|
|
113
121
|
bonus: number;
|
|
114
|
-
proficiency?:
|
|
122
|
+
proficiency?: ProficiencyTri;
|
|
115
123
|
}>>;
|
|
116
124
|
passives?: Partial<{
|
|
117
125
|
perception: number;
|
|
@@ -133,6 +141,50 @@ export interface CharacterOverrides {
|
|
|
133
141
|
spellcasting_ability?: Ability;
|
|
134
142
|
spell_slots?: Record<number, number>;
|
|
135
143
|
attunement_limit?: number;
|
|
144
|
+
/** Manual proficiency edits, mirroring `characterOverridesShape` (pc.schema.ts). `remove`
|
|
145
|
+
* SUPPRESSES a rules-granted entry ("a dwarf who doesn't know dwarvish"). Both keys and EVERY
|
|
146
|
+
* leaf are optional with no defaults, so an untouched note stays byte-identical: every reader
|
|
147
|
+
* uses `?? []` (`?? {}` for the tools tri).
|
|
148
|
+
*
|
|
149
|
+
* `tools.proficiency` is the per-tool manual tri (R4-G4 §9.3, UR1); `languages` deliberately
|
|
150
|
+
* gets none. This line is the ONLY thing keeping the interface honest about it, and what it
|
|
151
|
+
* buys is TYPE VISIBILITY, not survival: the `NoOverridesKeyDrift` pair below compares
|
|
152
|
+
* TOP-LEVEL keys only, so a sibling added to the schema and not here ROUND-TRIPS at runtime
|
|
153
|
+
* (the plugin's `characterToYaml` deep-clones and dumps the RUNTIME object, and `parsePC`
|
|
154
|
+
* runs `characterSchema.safeParse`, which keeps a key the schema declares) while staying
|
|
155
|
+
* UNTYPED, so no consumer can read or write it without a cast. That is the same direction the
|
|
156
|
+
* parity block below states; the STRIPPED case is the other one, a key here that the schema
|
|
157
|
+
* lacks. */
|
|
158
|
+
languages?: {
|
|
159
|
+
add?: string[];
|
|
160
|
+
remove?: string[];
|
|
161
|
+
};
|
|
162
|
+
tools?: {
|
|
163
|
+
add?: string[];
|
|
164
|
+
remove?: string[];
|
|
165
|
+
proficiency?: Record<string, ProficiencyTri>;
|
|
166
|
+
};
|
|
167
|
+
/** Manual defense edits, mirroring `characterOverridesShape` (pc.schema.ts). SUPPRESSION-ONLY:
|
|
168
|
+
* the absent `add` channel is a DELIBERATE asymmetry with `languages`/`tools` above, not an
|
|
169
|
+
* oversight · do not "complete the pattern". The additive store is `character.defenses.*`,
|
|
170
|
+
* which already exists; a second additive channel would recreate the two-writers divergence
|
|
171
|
+
* R4-P5 exists to remove. Every level is optional with no defaults, so an untouched note stays
|
|
172
|
+
* byte-identical. The store has NO consumer as of this commit · Task 5 writes the first, and
|
|
173
|
+
* should read every level as possibly absent (`?? []`) and match with `toDefenseSlug`. */
|
|
174
|
+
defenses?: {
|
|
175
|
+
resistances?: {
|
|
176
|
+
remove?: string[];
|
|
177
|
+
};
|
|
178
|
+
immunities?: {
|
|
179
|
+
remove?: string[];
|
|
180
|
+
};
|
|
181
|
+
vulnerabilities?: {
|
|
182
|
+
remove?: string[];
|
|
183
|
+
};
|
|
184
|
+
condition_immunities?: {
|
|
185
|
+
remove?: string[];
|
|
186
|
+
};
|
|
187
|
+
};
|
|
136
188
|
}
|
|
137
189
|
export interface CharacterState {
|
|
138
190
|
hp: {
|
|
@@ -236,6 +288,17 @@ export interface Character {
|
|
|
236
288
|
};
|
|
237
289
|
state: CharacterState;
|
|
238
290
|
}
|
|
291
|
+
/** R4-G7 T8 RIDER-23 (F-FEATSRC) · the slot that granted a feat: a class's `choices[<level>].feat` pick, or a 2024
|
|
292
|
+
* background's origin feat. Its two members ARE the `class` and `background` sources below, so a renderer formats `via`
|
|
293
|
+
* through the arms it already has ("Fighter 4", "Background: Soldier"). */
|
|
294
|
+
export type FeatVia = {
|
|
295
|
+
kind: "class";
|
|
296
|
+
slug: string;
|
|
297
|
+
level: number;
|
|
298
|
+
} | {
|
|
299
|
+
kind: "background";
|
|
300
|
+
slug: string;
|
|
301
|
+
};
|
|
239
302
|
export type FeatureSource = {
|
|
240
303
|
kind: "class";
|
|
241
304
|
slug: string;
|
|
@@ -253,6 +316,7 @@ export type FeatureSource = {
|
|
|
253
316
|
} | {
|
|
254
317
|
kind: "feat";
|
|
255
318
|
slug: string;
|
|
319
|
+
via?: FeatVia;
|
|
256
320
|
};
|
|
257
321
|
export interface ResolvedFeature {
|
|
258
322
|
feature: Feature;
|
|
@@ -270,6 +334,33 @@ export interface ResolvedFeature {
|
|
|
270
334
|
renderSuppressed?: boolean;
|
|
271
335
|
/** The feat's entire substance is a full ability-score improvement (a pure ASI). The DISPLAY layer hides these. */
|
|
272
336
|
buildOnly?: boolean;
|
|
337
|
+
/** R4-G7 §7.1: EVERY LOWER copy the resolve-time fold collapsed into this wrapper, ASCENDING by level, with
|
|
338
|
+
* that copy's own card identity (`name`, and `description` = its `description` else its `entries` joined) and
|
|
339
|
+
* its `choices` when it carries any. A wrapper-only field, the `chosenInline` precedent: the shared registry
|
|
340
|
+
* `Feature` is never touched.
|
|
341
|
+
*
|
|
342
|
+
* The invariant every walker relies on: the TOP copy's own name, prose and choices stay on `feature` and are
|
|
343
|
+
* NEVER repeated here, so visiting `foldedFrom` (each entry at its own `level`) and then `feature` (at
|
|
344
|
+
* `source.level`) visits every folded copy EXACTLY ONCE, in ascending level order · the order the un-folded
|
|
345
|
+
* list had. That is what keeps "a level-N decision on a repeated feature stays a decision at level N" true,
|
|
346
|
+
* and what keeps the builder's per-level strip complete, while the sheet renders ONE row.
|
|
347
|
+
*
|
|
348
|
+
* Both readers live in `pc.decision-engine.ts`: `visitProficiencyChoices` (through
|
|
349
|
+
* `collectChosenProficiencies`) walks the entries that carry `choices` and nothing else, and
|
|
350
|
+
* `buildDecisionLedger` runs each entry at its own level through the SAME branch it runs the wrapper through
|
|
351
|
+
* (`emitCopy`): its authored choices when it has them, else `recognizeDecision`'s synthesized decision under
|
|
352
|
+
* the suppression filter, else the informational card, else nothing. Per-copy `name` and `description` are
|
|
353
|
+
* load-bearing, not decoration: they decide what the recognizer sees and what the row prints. MEASURED over
|
|
354
|
+
* both corpora, 63 of the 131 repeated-id families have a copy whose description differs from the top copy's
|
|
355
|
+
* and 1 has a differing name (an UPPER BOUND: it counts `ability-score-improvement` families, which are
|
|
356
|
+
* `buildOnly` and never fold; the non-ASI carriers are `artificer-specialist-feature`, `path-feature`,
|
|
357
|
+
* `bard-college-feature`, `expertise` and their siblings). Absent on an unfolded wrapper. */
|
|
358
|
+
foldedFrom?: {
|
|
359
|
+
level: number;
|
|
360
|
+
name: string;
|
|
361
|
+
description?: string;
|
|
362
|
+
choices?: Choice[];
|
|
363
|
+
}[];
|
|
273
364
|
}
|
|
274
365
|
export interface ResolvedSpell {
|
|
275
366
|
entity: Spell;
|
|
@@ -286,6 +377,9 @@ export interface ResolvedSpell {
|
|
|
286
377
|
/** Index of the originating equipment entry when this spell was granted by a
|
|
287
378
|
* per-instance item (e.g. a Spell Scroll). Absent for class/feat/race spells. */
|
|
288
379
|
entryIndex?: number;
|
|
380
|
+
/** True ONLY for a row from character.spells.known (removable / toggleable);
|
|
381
|
+
* absent on every grant · R4-G3b §5.2.7. */
|
|
382
|
+
persisted?: boolean;
|
|
289
383
|
}
|
|
290
384
|
export interface ResolvedClass {
|
|
291
385
|
entity: ClassEntity | null;
|
|
@@ -296,6 +390,15 @@ export interface ResolvedClass {
|
|
|
296
390
|
export interface ResolvedPoolEntry {
|
|
297
391
|
slug: string;
|
|
298
392
|
entity: OptionalFeatureEntity;
|
|
393
|
+
/** The compendium NAME the REGISTERED entity carries (R4-G5 §9.2.1), stamped at resolve time because `entity`
|
|
394
|
+
* is `e.data`, which carries none: MEASURED 2026-09-07 over the WHOLE converter output (226 documents under
|
|
395
|
+
* its `OptionalFeatures` folders) and the 7 bundle ones, 0 of 226 and 0 of 7 declare a `compendium` key
|
|
396
|
+
* INSIDE the fenced entity body, while 226 of 226 and 7 of 7 declare one in the note's FRONTMATTER, which
|
|
397
|
+
* core keeps on `RegisteredEntity`.
|
|
398
|
+
* The plugin's ONE visibility predicate reads it on BOTH surfaces (§3.2.2). Absent when the registry entry
|
|
399
|
+
* carries none, which in practice means a test double. The COLLAPSE does not read it: its identity is the
|
|
400
|
+
* slug PREFIX (§9.2.2). */
|
|
401
|
+
compendium?: string;
|
|
299
402
|
}
|
|
300
403
|
/** A materialized selection pool: count from the table column, prereq-filtered
|
|
301
404
|
* candidates, player picks (from the choices ledger), and subclass auto-grants. */
|
|
@@ -311,6 +414,16 @@ export interface ResolvedPool {
|
|
|
311
414
|
selected: ResolvedPoolEntry[];
|
|
312
415
|
available: ResolvedPoolEntry[];
|
|
313
416
|
grants: ResolvedPoolEntry[];
|
|
417
|
+
/** Derived at resolve time from the members' `rendering_hint` (pool-layout.ts),
|
|
418
|
+
* unconditionally: an authored `TabDecl.renders.layout` does not suppress the
|
|
419
|
+
* derivation, it OUTRANKS this field at the tab (§4.2.5). R4-G4 T5 LANDED that precedence in the
|
|
420
|
+
* plugin: `TabsContainer`'s dynamic-pool-tab loop is the ONE reader of this field (measured
|
|
421
|
+
* 2026-09-05 with `grep -rn "pool\.layout" packages/obsidian/src`, one line) and reads
|
|
422
|
+
* authored-then-derived-then-default before handing the string to the `PoolTab` constructor. */
|
|
423
|
+
layout?: PoolLayout;
|
|
424
|
+
/** The OWNED resource id the members spend (majority of `consumes.resource` among the ids the
|
|
425
|
+
* character owns); undefined when no member consumes an owned id (R4-G4 §4.2.3, Gate 0 B1). */
|
|
426
|
+
resource?: string;
|
|
314
427
|
}
|
|
315
428
|
export interface ResolvedCharacter {
|
|
316
429
|
definition: Character;
|
|
@@ -318,10 +431,41 @@ export interface ResolvedCharacter {
|
|
|
318
431
|
classes: ResolvedClass[];
|
|
319
432
|
background: BackgroundEntity | null;
|
|
320
433
|
feats: FeatEntity[];
|
|
434
|
+
/** The bare/full slug of the background's resolved origin feat (R4-G3b §8): stamped by the resolver OUTSIDE the
|
|
435
|
+
* feats de-dup guard, so it is set even when the same feat is ALSO a class-slot pick. Read by
|
|
436
|
+
* visitProficiencyChoices' origin arm; `resolved.feats` itself carries no provenance. */
|
|
437
|
+
originFeatSlug?: string;
|
|
321
438
|
totalLevel: number;
|
|
322
439
|
features: ResolvedFeature[];
|
|
323
440
|
spells: ResolvedSpell[];
|
|
324
441
|
pools: ResolvedPool[];
|
|
442
|
+
/** Every `resources[]` entry of every resolved feature, keyed by id, with the DECLARING feature
|
|
443
|
+
* stamped as owner, PLUS every pool pick's own `uses` with the POOL stamped as owner
|
|
444
|
+
* (R4-G4 §3.2.1 and §12). REQUIRED: the resolver's own literal sets it like `pools` and
|
|
445
|
+
* reassigns it after pools resolve. Cast test fixtures omit it. Its readers outside the
|
|
446
|
+
* resolver landed with R4-G4 T3, T4, T5 and T7b. Re-measured 2026-09-06 on the SHIPPED plugin
|
|
447
|
+
* tree: `grep -rn "resolved\.resources" packages/obsidian/src` returns TEN lines, of
|
|
448
|
+
* which SIX are read EXPRESSIONS and four are comment prose. The six expressions sit in six
|
|
449
|
+
* functions across five files: the module-private `resourceLevel`
|
|
450
|
+
* helper of `components/actions/feature-rows.ts` (T3, reading `?.owner`), `renderSpendControl`
|
|
451
|
+
* (`components/actions/spend-control.ts`, reading the entry for its `name` and `die`),
|
|
452
|
+
* `consumeCost` (`components/pool-tab.ts`, reading `?.name` for the Cost label), both T4,
|
|
453
|
+
* `renderPoolHead` (`components/pool-tab.ts`, T5, reading the entry for the tab head's `name`,
|
|
454
|
+
* `die`, `reset` and owner: §4.2.6), and T7b's two: `renderPickTracker`
|
|
455
|
+
* (`components/actions/pick-tracker.ts`, reading the entry for a pick's `reset` and `name`) and
|
|
456
|
+
* `seedFeatureUses` (`pc.resource-seed.ts`), which is the ONE expression that is not a
|
|
457
|
+
* `?.get(...)`: it walks `?.values()` for the entries whose `owner.kind` is `pool`, because a
|
|
458
|
+
* pick's `uses` reaches the seed through no other route. §3.2.1 names its consumers: T3's
|
|
459
|
+
* `resourceLevel(id, ctx)`
|
|
460
|
+
* die helper (§6.2.4), T4's spend control and Cost label (§3.2.2 / §3.2.4), T5's pool-tab
|
|
461
|
+
* head and T7b's tracker and seed (§12.2), all LANDED. T7's restore affordance has LANDED too and
|
|
462
|
+
* reads NOTHING here: its rest arm
|
|
463
|
+
* walks the index `computeRestPlan` re-derives and its card arm routes on `resolveRecovery(rec)`
|
|
464
|
+
* over the RAW `recovery[0]`.
|
|
465
|
+
* Every one of the six reaches it through `?.`, because the plugin's card / row / rest fixtures
|
|
466
|
+
* cast a `ResolvedCharacter` with no index. `computeRestPlan` never reads it at all, it re-derives
|
|
467
|
+
* the index. */
|
|
468
|
+
resources: ResourceIndex;
|
|
325
469
|
/** 2024 Weapon Mastery: bare-normalized slugs of the weapons the character has
|
|
326
470
|
* chosen mastery of, unioned across every class/level `weapon-mastery` pick
|
|
327
471
|
* (e.g. "srd-2024_greatsword" → "greatsword"). Empty when the character has no
|
|
@@ -337,6 +481,10 @@ export interface ACTerm {
|
|
|
337
481
|
source: string;
|
|
338
482
|
amount: number;
|
|
339
483
|
kind: "armor" | "shield" | "item" | "unarmored" | "override" | "dex" | "ability" | "feature";
|
|
484
|
+
/** Situational qualifier carried verbatim from the granting `ac-bonus` effect's `condition`
|
|
485
|
+
* (R4-G3a §3.2.5), e.g. "While wearing heavy armor". Absent = unconditional. Rendered as the
|
|
486
|
+
* AC-tooltip row's `title`; NEVER evaluated. */
|
|
487
|
+
condition?: string;
|
|
340
488
|
}
|
|
341
489
|
/** Structured max-HP math for the Max HP modal. Spec: P5 2026-07-21. */
|
|
342
490
|
export interface HPBreakdown {
|
|
@@ -362,12 +510,19 @@ export interface HPBreakdown {
|
|
|
362
510
|
/** An additive on-hit damage rider (dice or flat) applied to an attack, from a
|
|
363
511
|
* feature (global — all weapon attacks) or a magic item (that weapon only).
|
|
364
512
|
* `amount` is a dice/flat string ("2d6", "1d8", "2"); `damage_type` optional
|
|
365
|
-
* (a bare manual override string carries its type inline
|
|
366
|
-
*
|
|
513
|
+
* (a bare manual override string carries its type inline, and the schema's
|
|
514
|
+
* `chosen` sentinel is kept, never replaced by the row's type, RIDER-13); `source`
|
|
515
|
+
* is a human label for the expand/tooltip. `condition` is the source's own
|
|
516
|
+
* qualifier ("for a 1st-level spell slot"), CARRIED verbatim and never evaluated,
|
|
517
|
+
* from a feature effect (R4-G7 T8 RIDER-12) and from an item's `damage_riders`
|
|
518
|
+
* entry alike; absent when the source authors none. How the sheet prints a rider
|
|
519
|
+
* (a damage chip, or the row's caption) is the plugin renderer's policy, stated
|
|
520
|
+
* once at `weapons-table.ts` `renderWeaponRow`. */
|
|
367
521
|
export interface DamageRider {
|
|
368
522
|
amount: string;
|
|
369
523
|
damage_type?: string;
|
|
370
524
|
source?: string;
|
|
525
|
+
condition?: string;
|
|
371
526
|
}
|
|
372
527
|
export interface AttackRow {
|
|
373
528
|
id: string;
|
|
@@ -400,6 +555,9 @@ export interface AttackRow {
|
|
|
400
555
|
/** Action-economy cost for the Cost badge. Defaults to "action" at
|
|
401
556
|
* render time when absent. Settable via entry.overrides.action. */
|
|
402
557
|
actionCost?: "action" | "bonus-action" | "reaction" | "free" | "special";
|
|
558
|
+
/** The engine's always-present unarmed row (R4-G6b §5): never an equipment entry, never `equipped`, no `slotKey`.
|
|
559
|
+
* The weapons table gates its expand on this flag BEFORE the equipment lookup. Absent on every weapon row. */
|
|
560
|
+
unarmed?: true;
|
|
403
561
|
/** When set, the weapon is being wielded one-handed but has a versatile
|
|
404
562
|
* two-handed option. The Actions tab renders both lines stacked in the
|
|
405
563
|
* damage cell instead of emitting a second row. */
|
|
@@ -476,12 +634,84 @@ export interface DerivedEquipment {
|
|
|
476
634
|
* `condition` absent = always-on (not situational).
|
|
477
635
|
*/
|
|
478
636
|
export interface RollModifierEntry {
|
|
479
|
-
mode: "advantage" | "disadvantage";
|
|
637
|
+
mode: "advantage" | "disadvantage" | "reroll" | "add-d4";
|
|
480
638
|
roll: "ability-check" | "saving-throw" | "attack";
|
|
481
639
|
scope?: string;
|
|
482
640
|
condition?: string;
|
|
483
641
|
label: string;
|
|
484
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* The two members' aliases, extracted from the entry's inline unions (R4-G3a §5.2). They are
|
|
645
|
+
* declared HERE and nowhere else: `pc/roll-tag-labels.ts`'s `Record<RollModifierMode, string>`
|
|
646
|
+
* tables and `pc/roll-scope.ts` import them, so widening the entry above is a COMPILE error in
|
|
647
|
+
* every label table that has not been extended (§14 row 8i).
|
|
648
|
+
*/
|
|
649
|
+
export type RollModifierMode = RollModifierEntry["mode"];
|
|
650
|
+
export type RollKind = RollModifierEntry["roll"];
|
|
651
|
+
/** The closed `save-outcome` result vocabulary, extracted the same way (R4-G3a §5.3): how much
|
|
652
|
+
* of the effect still lands. `pc/roll-tag-labels.ts` types its OUTCOME glyph table
|
|
653
|
+
* `Record<SaveOutcome, string>`, so the union is spelled ONCE and a widened member is a compile
|
|
654
|
+
* error in the table rather than a silent missing glyph. */
|
|
655
|
+
export type SaveOutcome = SaveOutcomeEntry["on_success"];
|
|
656
|
+
/**
|
|
657
|
+
* A `save-outcome` effect as it leaves the fold (R4-G3a §5.3) · Evasion and its shape-mates
|
|
658
|
+
* ("on a failed save you take half damage, on a success none").
|
|
659
|
+
*
|
|
660
|
+
* `ability` ABSENT means every save, which is the save chip's existing "absent scope = every
|
|
661
|
+
* chip" idiom: the effect schema's `ability: "any"` maps to `undefined` at the fold rather than
|
|
662
|
+
* duplicating the entry six times. `appliesTo` is the effect's `applies_to` prose ("a spell"),
|
|
663
|
+
* carried for the tooltip only; `condition` is the situational qualifier, never evaluated.
|
|
664
|
+
*/
|
|
665
|
+
export interface SaveOutcomeEntry {
|
|
666
|
+
ability?: Ability;
|
|
667
|
+
on_success: "none" | "half" | "full";
|
|
668
|
+
on_failure: SaveOutcomeEntry["on_success"];
|
|
669
|
+
appliesTo?: string;
|
|
670
|
+
condition?: string;
|
|
671
|
+
label: string;
|
|
672
|
+
}
|
|
673
|
+
/** Where a defense came from. Ordered weakest to strongest for the precedence rule in recalc. */
|
|
674
|
+
export type DefenseOrigin = "manual" | "equipment" | "grant";
|
|
675
|
+
/**
|
|
676
|
+
* One defense a feature effect grants, as it leaves the fold (R4-G3a §3.2.1).
|
|
677
|
+
*
|
|
678
|
+
* The four defense totals on FeatureEffectTotals are lists of these. `value` is the AUTHORED
|
|
679
|
+
* spelling, trimmed and never slugified · `toDefenseSlug` runs later, in pc.recalc.ts, which is
|
|
680
|
+
* why the fold's dedupe is a case-insensitive comparison rather than a slug lookup. `sources`
|
|
681
|
+
* holds the granting FEATURE NAMES, merged per value at the fold (two features granting the same
|
|
682
|
+
* immunity yield ONE grant with TWO sources · dropping the second would lose it before
|
|
683
|
+
* composeDefenseEntries ever saw it). `condition` is the first qualifier seen for that value,
|
|
684
|
+
* carried verbatim and NEVER evaluated.
|
|
685
|
+
*/
|
|
686
|
+
export interface DefenseGrant {
|
|
687
|
+
value: string;
|
|
688
|
+
sources: string[];
|
|
689
|
+
condition?: string;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* One entry in a derived defense bucket.
|
|
693
|
+
*
|
|
694
|
+
* `value` is ALWAYS `toDefenseSlug(raw)` · it is what every comparison keys on.
|
|
695
|
+
* `label` is the first-spelling-wins display string, which is what preserves an authored "Psychic".
|
|
696
|
+
* `origin` is the STRONGEST contributing source, not the first list to supply it.
|
|
697
|
+
*
|
|
698
|
+
* `sources` is GRANT-ORIGIN attribution only (R4-G3a §3.2.3): the names of the features whose
|
|
699
|
+
* effects granted this value, copied from the DefenseGrant. EQUIPMENT-origin attribution (the
|
|
700
|
+
* `pc.equipment.ts` half of R4-P5 §10.1 · archivist-obsidian/docs/superpowers/specs/
|
|
701
|
+
* 2026-08-05-r4-p5-defenses-design.md, the spec lives in the PLUGIN repo, not this one) stays
|
|
702
|
+
* DEFERRED, so an equipment-only entry carries no `sources` at all and the plugin's chip host
|
|
703
|
+
* must guard on emptiness rather than on origin alone.
|
|
704
|
+
*
|
|
705
|
+
* `condition` is the situational qualifier from the granting effect, carried verbatim for the
|
|
706
|
+
* chip tooltip and NEVER evaluated. Both fields are optional: a manual entry has neither.
|
|
707
|
+
*/
|
|
708
|
+
export interface DefenseEntry {
|
|
709
|
+
value: string;
|
|
710
|
+
label: string;
|
|
711
|
+
origin: DefenseOrigin;
|
|
712
|
+
condition?: string;
|
|
713
|
+
sources?: string[];
|
|
714
|
+
}
|
|
485
715
|
export interface DerivedStats {
|
|
486
716
|
totalLevel: number;
|
|
487
717
|
proficiencyBonus: number;
|
|
@@ -547,10 +777,10 @@ export interface DerivedStats {
|
|
|
547
777
|
spellLimits: SpellLimitInfo[];
|
|
548
778
|
warnings: string[];
|
|
549
779
|
defenses: {
|
|
550
|
-
resistances:
|
|
551
|
-
immunities:
|
|
552
|
-
vulnerabilities:
|
|
553
|
-
condition_immunities:
|
|
780
|
+
resistances: DefenseEntry[];
|
|
781
|
+
immunities: DefenseEntry[];
|
|
782
|
+
vulnerabilities: DefenseEntry[];
|
|
783
|
+
condition_immunities: DefenseEntry[];
|
|
554
784
|
};
|
|
555
785
|
acBreakdown: ACTerm[];
|
|
556
786
|
hpBreakdown: HPBreakdown;
|
|
@@ -573,10 +803,15 @@ export interface DerivedStats {
|
|
|
573
803
|
attunementLimit: number;
|
|
574
804
|
conditionEffects: ConditionEffects;
|
|
575
805
|
/**
|
|
576
|
-
* Structured
|
|
577
|
-
* Order-preserving pass-through (no dedupe/merge); rendered as ADV/DIS chips.
|
|
806
|
+
* Structured `roll-modifier` entries: four `mode` members since R4-G3a §6, not only advantage/disadvantage.
|
|
807
|
+
* Order-preserving pass-through (no dedupe/merge); rendered as ADV/DIS/RR/+D4 chips.
|
|
578
808
|
*/
|
|
579
809
|
rollModifiers: RollModifierEntry[];
|
|
810
|
+
/**
|
|
811
|
+
* `save-outcome` entries (R4-G3a §5.3). Order-preserving pass-through; rendered as an outcome
|
|
812
|
+
* tag ("0/½") on the matching save chip, or on every save chip when `ability` is absent.
|
|
813
|
+
*/
|
|
814
|
+
saveOutcomes: SaveOutcomeEntry[];
|
|
580
815
|
}
|
|
581
816
|
export interface SpellcastingClassInfo {
|
|
582
817
|
classSlug: string;
|
|
@@ -585,8 +820,12 @@ export interface SpellcastingClassInfo {
|
|
|
585
820
|
defaultAbility: Ability;
|
|
586
821
|
saveDC: number;
|
|
587
822
|
attackBonus: number;
|
|
588
|
-
|
|
823
|
+
/** The shared caster-type vocabulary; see schemas/caster-type-schema.ts (never spell the members out again). */
|
|
824
|
+
casterType: CasterType;
|
|
589
825
|
preparation: "known" | "prepared";
|
|
826
|
+
/** The profile's `spellList` (null when neither block names one), so the add drawer can call the same
|
|
827
|
+
* `attributeUnclassedSpell` the resolver does. recalc always sets it; optional so hand-built rows read as unobservable. */
|
|
828
|
+
spellList?: string | null;
|
|
590
829
|
}
|
|
591
830
|
export interface SpellLimitInfo {
|
|
592
831
|
classSlug: string;
|