@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
package/dist/pc/pc.schema.js
CHANGED
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,23 +1,30 @@
|
|
|
1
|
+
import type { EntityRegistry } from "@archivist-gg/core";
|
|
1
2
|
import type { Ability } from "@archivist-gg/dnd5e";
|
|
2
|
-
import type { KnownSpellEntry, ResolvedClass } from "./pc.types.js";
|
|
3
|
+
import type { CharacterOverrides, KnownSpellEntry, ResolvedClass } from "./pc.types.js";
|
|
3
4
|
import type { CasterType } from "@archivist-gg/dnd5e/class/class.types";
|
|
4
5
|
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;
|
|
24
|
+
/** The ability a class actually casts with: a per-class override wins, else the
|
|
25
|
+
* data ability from resolveSpellcasting. `dataAbility` is non-nullable by
|
|
26
|
+
* contract: resolve caster-ness (a non-null SpellcastingProfile) BEFORE calling. */
|
|
27
|
+
export declare function effectiveSpellcastingAbility(classSlug: string, dataAbility: Ability, overrides: CharacterOverrides): Ability;
|
|
21
28
|
export interface NormalizedKnownSpell {
|
|
22
29
|
slug: string;
|
|
23
30
|
classSlug: string | null;
|
|
@@ -26,6 +33,44 @@ export interface NormalizedKnownSpell {
|
|
|
26
33
|
alwaysPrepared: boolean;
|
|
27
34
|
}
|
|
28
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;
|
|
29
74
|
export interface CasterClassInput {
|
|
30
75
|
casterType: CasterType;
|
|
31
76
|
level: number;
|
|
@@ -39,6 +84,16 @@ export interface DerivedSpellSlots {
|
|
|
39
84
|
total: number;
|
|
40
85
|
} | null;
|
|
41
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
|
+
*/
|
|
42
97
|
export declare function deriveSpellSlots(classes: CasterClassInput[]): DerivedSpellSlots;
|
|
43
98
|
export interface LimitClassInput {
|
|
44
99
|
classSlug: string;
|
|
@@ -53,4 +108,22 @@ export interface SpellLimit {
|
|
|
53
108
|
cantripsKnown: number | null;
|
|
54
109
|
preparedOrKnown: number | null;
|
|
55
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
|
+
*/
|
|
56
128
|
export declare function computeSpellLimits(inputs: LimitClassInput[]): SpellLimit[];
|
|
129
|
+
export {};
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PREPARED_COLUMNS,
|
|
3
|
+
attributeUnclassedSpell,
|
|
2
4
|
computeSpellLimits,
|
|
3
5
|
deriveSpellSlots,
|
|
6
|
+
effectiveSpellcastingAbility,
|
|
7
|
+
listedSpellClassNames,
|
|
4
8
|
normalizeKnownSpell,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
preparedLevelTerm,
|
|
10
|
+
resolveSpellcasting,
|
|
11
|
+
slotTableFor
|
|
12
|
+
} from "../chunk-DPUJDOKV.js";
|
|
13
|
+
import "../chunk-IUAAXXNO.js";
|
|
8
14
|
import "../chunk-VTTA2ALZ.js";
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
15
|
+
import "../chunk-PDJSUH24.js";
|
|
16
|
+
import "../chunk-WELIKLZF.js";
|
|
11
17
|
export {
|
|
18
|
+
PREPARED_COLUMNS,
|
|
19
|
+
attributeUnclassedSpell,
|
|
12
20
|
computeSpellLimits,
|
|
13
21
|
deriveSpellSlots,
|
|
22
|
+
effectiveSpellcastingAbility,
|
|
23
|
+
listedSpellClassNames,
|
|
14
24
|
normalizeKnownSpell,
|
|
15
|
-
|
|
25
|
+
preparedLevelTerm,
|
|
26
|
+
resolveSpellcasting,
|
|
27
|
+
slotTableFor
|
|
16
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;
|
|
@@ -126,16 +134,57 @@ export interface CharacterOverrides {
|
|
|
126
134
|
ac?: number;
|
|
127
135
|
speed?: number;
|
|
128
136
|
initiative?: number;
|
|
129
|
-
|
|
130
|
-
saveDC?: number;
|
|
131
|
-
attackBonus?: number;
|
|
132
|
-
};
|
|
137
|
+
spellcasting_ability_by_class?: Partial<Record<string, Ability>>;
|
|
133
138
|
/** Character-level spellcasting ability for spells that carry no ability of
|
|
134
139
|
* their own (e.g. a Spell Scroll cast by a non-caster): the DC-ability fallback
|
|
135
140
|
* after a per-instance override and the character's own class ability. */
|
|
136
141
|
spellcasting_ability?: Ability;
|
|
137
142
|
spell_slots?: Record<number, number>;
|
|
138
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
|
+
};
|
|
139
188
|
}
|
|
140
189
|
export interface CharacterState {
|
|
141
190
|
hp: {
|
|
@@ -239,6 +288,17 @@ export interface Character {
|
|
|
239
288
|
};
|
|
240
289
|
state: CharacterState;
|
|
241
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
|
+
};
|
|
242
302
|
export type FeatureSource = {
|
|
243
303
|
kind: "class";
|
|
244
304
|
slug: string;
|
|
@@ -256,6 +316,7 @@ export type FeatureSource = {
|
|
|
256
316
|
} | {
|
|
257
317
|
kind: "feat";
|
|
258
318
|
slug: string;
|
|
319
|
+
via?: FeatVia;
|
|
259
320
|
};
|
|
260
321
|
export interface ResolvedFeature {
|
|
261
322
|
feature: Feature;
|
|
@@ -273,6 +334,33 @@ export interface ResolvedFeature {
|
|
|
273
334
|
renderSuppressed?: boolean;
|
|
274
335
|
/** The feat's entire substance is a full ability-score improvement (a pure ASI). The DISPLAY layer hides these. */
|
|
275
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
|
+
}[];
|
|
276
364
|
}
|
|
277
365
|
export interface ResolvedSpell {
|
|
278
366
|
entity: Spell;
|
|
@@ -289,6 +377,9 @@ export interface ResolvedSpell {
|
|
|
289
377
|
/** Index of the originating equipment entry when this spell was granted by a
|
|
290
378
|
* per-instance item (e.g. a Spell Scroll). Absent for class/feat/race spells. */
|
|
291
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;
|
|
292
383
|
}
|
|
293
384
|
export interface ResolvedClass {
|
|
294
385
|
entity: ClassEntity | null;
|
|
@@ -299,6 +390,15 @@ export interface ResolvedClass {
|
|
|
299
390
|
export interface ResolvedPoolEntry {
|
|
300
391
|
slug: string;
|
|
301
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;
|
|
302
402
|
}
|
|
303
403
|
/** A materialized selection pool: count from the table column, prereq-filtered
|
|
304
404
|
* candidates, player picks (from the choices ledger), and subclass auto-grants. */
|
|
@@ -314,6 +414,16 @@ export interface ResolvedPool {
|
|
|
314
414
|
selected: ResolvedPoolEntry[];
|
|
315
415
|
available: ResolvedPoolEntry[];
|
|
316
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;
|
|
317
427
|
}
|
|
318
428
|
export interface ResolvedCharacter {
|
|
319
429
|
definition: Character;
|
|
@@ -321,10 +431,41 @@ export interface ResolvedCharacter {
|
|
|
321
431
|
classes: ResolvedClass[];
|
|
322
432
|
background: BackgroundEntity | null;
|
|
323
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;
|
|
324
438
|
totalLevel: number;
|
|
325
439
|
features: ResolvedFeature[];
|
|
326
440
|
spells: ResolvedSpell[];
|
|
327
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;
|
|
328
469
|
/** 2024 Weapon Mastery: bare-normalized slugs of the weapons the character has
|
|
329
470
|
* chosen mastery of, unioned across every class/level `weapon-mastery` pick
|
|
330
471
|
* (e.g. "srd-2024_greatsword" → "greatsword"). Empty when the character has no
|
|
@@ -340,6 +481,10 @@ export interface ACTerm {
|
|
|
340
481
|
source: string;
|
|
341
482
|
amount: number;
|
|
342
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;
|
|
343
488
|
}
|
|
344
489
|
/** Structured max-HP math for the Max HP modal. Spec: P5 2026-07-21. */
|
|
345
490
|
export interface HPBreakdown {
|
|
@@ -365,12 +510,19 @@ export interface HPBreakdown {
|
|
|
365
510
|
/** An additive on-hit damage rider (dice or flat) applied to an attack, from a
|
|
366
511
|
* feature (global — all weapon attacks) or a magic item (that weapon only).
|
|
367
512
|
* `amount` is a dice/flat string ("2d6", "1d8", "2"); `damage_type` optional
|
|
368
|
-
* (a bare manual override string carries its type inline
|
|
369
|
-
*
|
|
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`. */
|
|
370
521
|
export interface DamageRider {
|
|
371
522
|
amount: string;
|
|
372
523
|
damage_type?: string;
|
|
373
524
|
source?: string;
|
|
525
|
+
condition?: string;
|
|
374
526
|
}
|
|
375
527
|
export interface AttackRow {
|
|
376
528
|
id: string;
|
|
@@ -403,6 +555,9 @@ export interface AttackRow {
|
|
|
403
555
|
/** Action-economy cost for the Cost badge. Defaults to "action" at
|
|
404
556
|
* render time when absent. Settable via entry.overrides.action. */
|
|
405
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;
|
|
406
561
|
/** When set, the weapon is being wielded one-handed but has a versatile
|
|
407
562
|
* two-handed option. The Actions tab renders both lines stacked in the
|
|
408
563
|
* damage cell instead of emitting a second row. */
|
|
@@ -479,12 +634,84 @@ export interface DerivedEquipment {
|
|
|
479
634
|
* `condition` absent = always-on (not situational).
|
|
480
635
|
*/
|
|
481
636
|
export interface RollModifierEntry {
|
|
482
|
-
mode: "advantage" | "disadvantage";
|
|
637
|
+
mode: "advantage" | "disadvantage" | "reroll" | "add-d4";
|
|
483
638
|
roll: "ability-check" | "saving-throw" | "attack";
|
|
484
639
|
scope?: string;
|
|
485
640
|
condition?: string;
|
|
486
641
|
label: string;
|
|
487
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
|
+
}
|
|
488
715
|
export interface DerivedStats {
|
|
489
716
|
totalLevel: number;
|
|
490
717
|
proficiencyBonus: number;
|
|
@@ -550,10 +777,10 @@ export interface DerivedStats {
|
|
|
550
777
|
spellLimits: SpellLimitInfo[];
|
|
551
778
|
warnings: string[];
|
|
552
779
|
defenses: {
|
|
553
|
-
resistances:
|
|
554
|
-
immunities:
|
|
555
|
-
vulnerabilities:
|
|
556
|
-
condition_immunities:
|
|
780
|
+
resistances: DefenseEntry[];
|
|
781
|
+
immunities: DefenseEntry[];
|
|
782
|
+
vulnerabilities: DefenseEntry[];
|
|
783
|
+
condition_immunities: DefenseEntry[];
|
|
557
784
|
};
|
|
558
785
|
acBreakdown: ACTerm[];
|
|
559
786
|
hpBreakdown: HPBreakdown;
|
|
@@ -576,19 +803,29 @@ export interface DerivedStats {
|
|
|
576
803
|
attunementLimit: number;
|
|
577
804
|
conditionEffects: ConditionEffects;
|
|
578
805
|
/**
|
|
579
|
-
* Structured
|
|
580
|
-
* 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.
|
|
581
808
|
*/
|
|
582
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[];
|
|
583
815
|
}
|
|
584
816
|
export interface SpellcastingClassInfo {
|
|
585
817
|
classSlug: string;
|
|
586
818
|
className: string;
|
|
587
819
|
ability: Ability;
|
|
820
|
+
defaultAbility: Ability;
|
|
588
821
|
saveDC: number;
|
|
589
822
|
attackBonus: number;
|
|
590
|
-
|
|
823
|
+
/** The shared caster-type vocabulary; see schemas/caster-type-schema.ts (never spell the members out again). */
|
|
824
|
+
casterType: CasterType;
|
|
591
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;
|
|
592
829
|
}
|
|
593
830
|
export interface SpellLimitInfo {
|
|
594
831
|
classSlug: string;
|