@archivist-gg/dnd5e 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/background/background.codec.js +3 -3
  2. package/dist/background/background.parser.js +2 -2
  3. package/dist/background/background.schema.d.ts +8 -0
  4. package/dist/background/background.schema.js +5 -3
  5. package/dist/{chunk-DZXSWSBP.js → chunk-5ZBE4CWV.js} +1 -1
  6. package/dist/{chunk-M2EMFD6S.js → chunk-6IRYTM5V.js} +5 -0
  7. package/dist/chunk-7TEK5KK7.js +49 -0
  8. package/dist/{chunk-VTYUBINW.js → chunk-BIK4DFWK.js} +1 -1
  9. package/dist/{chunk-EMOORSQ7.js → chunk-D3CLOBE3.js} +4 -1
  10. package/dist/chunk-O5EYMKFH.js +6 -0
  11. package/dist/{chunk-TUIFSVLI.js → chunk-SE7AZDR3.js} +1 -0
  12. package/dist/{chunk-A4WENOUQ.js → chunk-TN6ZLTNU.js} +1 -4
  13. package/dist/{chunk-5MZSB6OM.js → chunk-TNUFPIXI.js} +41 -20
  14. package/dist/index.js +90 -19
  15. package/dist/item/item.ai-schema.d.ts +1 -1
  16. package/dist/pc/pc.decision-engine.d.ts +13 -0
  17. package/dist/pc/pc.decision-engine.js +5 -3
  18. package/dist/pc/pc.equipment.js +2 -1
  19. package/dist/pc/pc.parser.js +1 -1
  20. package/dist/pc/pc.pools.js +3 -3
  21. package/dist/pc/pc.proficiencies.d.ts +31 -0
  22. package/dist/pc/pc.proficiencies.js +96 -23
  23. package/dist/pc/pc.proficiency-normalize.d.ts +5 -0
  24. package/dist/pc/pc.proficiency-query.js +5 -1
  25. package/dist/pc/pc.recalc.js +45 -35
  26. package/dist/pc/pc.resolver.js +3 -3
  27. package/dist/pc/pc.schema.d.ts +8 -4
  28. package/dist/pc/pc.schema.js +1 -1
  29. package/dist/pc/pc.spellcasting.d.ts +5 -1
  30. package/dist/pc/pc.spellcasting.js +3 -1
  31. package/dist/pc/pc.types.d.ts +2 -4
  32. package/dist/types/choice.d.ts +5 -0
  33. package/dist/types/choice.js +9 -3
  34. package/package.json +1 -1
  35. package/dist/chunk-BH6AMZVY.js +0 -25
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  backgroundCodec
3
- } from "../chunk-VTYUBINW.js";
4
- import "../chunk-DZXSWSBP.js";
5
- import "../chunk-TUIFSVLI.js";
3
+ } from "../chunk-BIK4DFWK.js";
4
+ import "../chunk-5ZBE4CWV.js";
5
+ import "../chunk-SE7AZDR3.js";
6
6
  import "../chunk-3DZ74AKK.js";
7
7
  import "../chunk-NXSWH3AE.js";
8
8
  import "../chunk-NXR3BD2V.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseBackground
3
- } from "../chunk-DZXSWSBP.js";
4
- import "../chunk-TUIFSVLI.js";
3
+ } from "../chunk-5ZBE4CWV.js";
4
+ import "../chunk-SE7AZDR3.js";
5
5
  import "../chunk-3DZ74AKK.js";
6
6
  import "../chunk-NXSWH3AE.js";
7
7
  import "../chunk-NXR3BD2V.js";
@@ -1,4 +1,12 @@
1
1
  import { z } from "zod";
2
+ export declare const langProfSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ kind: z.ZodLiteral<"fixed">;
4
+ languages: z.ZodArray<z.ZodString>;
5
+ }, z.core.$strip>, z.ZodObject<{
6
+ kind: z.ZodLiteral<"choice">;
7
+ count: z.ZodNumber;
8
+ from: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
9
+ }, z.core.$strip>], "kind">;
2
10
  export declare const backgroundEntitySchema: z.ZodObject<{
3
11
  slug: z.ZodString;
4
12
  name: z.ZodString;
@@ -1,9 +1,11 @@
1
1
  import {
2
- backgroundEntitySchema
3
- } from "../chunk-TUIFSVLI.js";
2
+ backgroundEntitySchema,
3
+ langProfSchema
4
+ } from "../chunk-SE7AZDR3.js";
4
5
  import "../chunk-3DZ74AKK.js";
5
6
  import "../chunk-NXSWH3AE.js";
6
7
  import "../chunk-NXR3BD2V.js";
7
8
  export {
8
- backgroundEntitySchema
9
+ backgroundEntitySchema,
10
+ langProfSchema
9
11
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backgroundEntitySchema
3
- } from "./chunk-TUIFSVLI.js";
3
+ } from "./chunk-SE7AZDR3.js";
4
4
 
5
5
  // src/background/background.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -24,6 +24,10 @@ function resolveSpellcasting(rc) {
24
24
  table
25
25
  };
26
26
  }
27
+ function effectiveSpellcastingAbility(classSlug, dataAbility, overrides) {
28
+ var _a, _b;
29
+ return (_b = (_a = overrides.spellcasting_ability_by_class) == null ? void 0 : _a[classSlug]) != null ? _b : dataAbility;
30
+ }
27
31
  function normalizeKnownSpell(entry) {
28
32
  var _a, _b;
29
33
  if (typeof entry === "string") {
@@ -263,6 +267,7 @@ function computeSpellLimits(inputs) {
263
267
 
264
268
  export {
265
269
  resolveSpellcasting,
270
+ effectiveSpellcastingAbility,
266
271
  normalizeKnownSpell,
267
272
  deriveSpellSlots,
268
273
  computeSpellLimits
@@ -0,0 +1,49 @@
1
+ // src/types/choice.ts
2
+ var ALL_SKILL_SLUGS = [
3
+ "acrobatics",
4
+ "animal-handling",
5
+ "arcana",
6
+ "athletics",
7
+ "deception",
8
+ "history",
9
+ "insight",
10
+ "intimidation",
11
+ "investigation",
12
+ "medicine",
13
+ "nature",
14
+ "perception",
15
+ "performance",
16
+ "persuasion",
17
+ "religion",
18
+ "sleight-of-hand",
19
+ "stealth",
20
+ "survival"
21
+ ];
22
+ var STANDARD_LANGUAGES = [
23
+ "common",
24
+ "dwarvish",
25
+ "elvish",
26
+ "giant",
27
+ "gnomish",
28
+ "goblin",
29
+ "halfling",
30
+ "orc"
31
+ ];
32
+ var EXOTIC_LANGUAGES = [
33
+ "abyssal",
34
+ "celestial",
35
+ "deep-speech",
36
+ "draconic",
37
+ "infernal",
38
+ "primordial",
39
+ "sylvan",
40
+ "undercommon"
41
+ ];
42
+ var ALL_LANGUAGES = [...STANDARD_LANGUAGES, ...EXOTIC_LANGUAGES];
43
+
44
+ export {
45
+ ALL_SKILL_SLUGS,
46
+ STANDARD_LANGUAGES,
47
+ EXOTIC_LANGUAGES,
48
+ ALL_LANGUAGES
49
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBackground
3
- } from "./chunk-DZXSWSBP.js";
3
+ } from "./chunk-5ZBE4CWV.js";
4
4
 
5
5
  // src/background/background.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  classifyWeaponRange
3
3
  } from "./chunk-353WXS6S.js";
4
+ import {
5
+ normKey
6
+ } from "./chunk-O5EYMKFH.js";
4
7
  import {
5
8
  defaultSlotForType,
6
9
  effectiveArmor,
@@ -361,7 +364,7 @@ function attackAbility(weapon, mods, weaponAbilities) {
361
364
  }
362
365
  function isWeaponSlugProficient(weapon, weaponSlug, profs) {
363
366
  const wp = profs.weapons;
364
- if (wp.specific.includes(weaponSlug)) return true;
367
+ if (wp.specific.some((e) => e === weaponSlug || normKey(e) === normKey(weapon.name))) return true;
365
368
  const cat = weapon.category;
366
369
  if (typeof cat === "string") {
367
370
  if (/^simple\b/.test(cat) && wp.categories.includes("simple")) return true;
@@ -0,0 +1,6 @@
1
+ // src/pc/pc.proficiency-normalize.ts
2
+ var normKey = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean).map((w) => w.endsWith("s") ? w.slice(0, -1) : w).sort().join(" ");
3
+
4
+ export {
5
+ normKey
6
+ };
@@ -79,5 +79,6 @@ var backgroundEntitySchema = z.object({
79
79
  });
80
80
 
81
81
  export {
82
+ langProfSchema,
82
83
  backgroundEntitySchema
83
84
  };
@@ -102,10 +102,7 @@ var characterOverridesSchema = z.object({
102
102
  ac: z.number().int().optional(),
103
103
  speed: z.number().int().optional(),
104
104
  initiative: z.number().int().optional(),
105
- spellcasting: z.object({
106
- saveDC: z.number().int().optional(),
107
- attackBonus: z.number().int().optional()
108
- }).optional(),
105
+ spellcasting_ability_by_class: z.record(z.string(), abilityEnum).optional(),
109
106
  // Character-level DC-ability fallback for own-ability-less spells (e.g. a Spell
110
107
  // Scroll cast by a non-caster). Same ability enum as the per-instance spell_ability.
111
108
  spellcasting_ability: abilityEnum.optional(),
@@ -1,10 +1,12 @@
1
1
  import {
2
+ ALL_LANGUAGES,
2
3
  ALL_SKILL_SLUGS
3
- } from "./chunk-BH6AMZVY.js";
4
+ } from "./chunk-7TEK5KK7.js";
4
5
  import {
6
+ effectiveSpellcastingAbility,
5
7
  normalizeKnownSpell,
6
8
  resolveSpellcasting
7
- } from "./chunk-M2EMFD6S.js";
9
+ } from "./chunk-6IRYTM5V.js";
8
10
  import {
9
11
  readTableColumn
10
12
  } from "./chunk-5XUXXAQJ.js";
@@ -134,8 +136,9 @@ var PCResolver = class {
134
136
  }
135
137
  }
136
138
  const ownSpellcastingAbility = (_h = classes.map((c) => {
137
- var _a2, _b2;
138
- return (_b2 = (_a2 = resolveSpellcasting(c)) == null ? void 0 : _a2.ability) != null ? _b2 : null;
139
+ if (!c.entity) return null;
140
+ const p = resolveSpellcasting(c);
141
+ return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
139
142
  }).find((a) => a != null)) != null ? _h : null;
140
143
  spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
141
144
  const chosenMasteries = collectChosenWeaponMasteries(character);
@@ -545,7 +548,7 @@ function enumerateOptions(choice, ctx, ownerBare) {
545
548
  return filtered.map((e) => ({ value: e.slug, label: e.name, entity: e }));
546
549
  }
547
550
  case "select-proficiency": {
548
- const pool = (_a = choice.from) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : [];
551
+ const pool = (_a = choice.from) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : choice.domain === "language" ? [...ALL_LANGUAGES] : [];
549
552
  return pool.map((v) => ({ value: v, label: v.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) }));
550
553
  }
551
554
  case "ability-points": {
@@ -648,22 +651,11 @@ function buildSubclassItem(choice, source, level, featureName, c, ctx, ownerBare
648
651
  status: selected ? "resolved" : "unresolved"
649
652
  };
650
653
  }
651
- function collectChosenProficiencies(resolved) {
654
+ function visitProficiencyChoices(resolved, visit) {
652
655
  var _a, _b;
653
- const out = { skills: [], expertise: [], languages: [], tools: [] };
654
- const apply = (choice, selected) => {
655
- if (choice.kind !== "select-proficiency") return;
656
- const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
657
- const pool = choice.from;
658
- const valid = pool ? vals.filter((v) => pool.includes(v)) : vals;
659
- const bucket = choice.domain === "skill" ? choice.expertise ? out.expertise : out.skills : choice.domain === "language" ? out.languages : choice.domain === "tool" ? out.tools : null;
660
- if (bucket) {
661
- for (const v of valid) if (!bucket.includes(v)) bucket.push(v);
662
- }
663
- };
664
656
  const walk = (choices, read) => {
665
657
  for (const ch of choices != null ? choices : []) {
666
- apply(ch, read(ch.id));
658
+ visit(ch, read(ch.id));
667
659
  if (ch.kind === "select-inline") {
668
660
  const sel = read(ch.id);
669
661
  const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
@@ -680,7 +672,7 @@ function collectChosenProficiencies(resolved) {
680
672
  return (_a3 = c.choices[lvl]) == null ? void 0 : _a3[id];
681
673
  };
682
674
  if (i === 0 && ((_b2 = (_a2 = entity.skill_choices) == null ? void 0 : _a2.from) == null ? void 0 : _b2.length)) {
683
- apply({
675
+ visit({
684
676
  kind: "select-proficiency",
685
677
  id: "skills",
686
678
  count: entity.skill_choices.count,
@@ -704,11 +696,39 @@ function collectChosenProficiencies(resolved) {
704
696
  if (resolved.background) {
705
697
  walk(resolved.background.choices, originRead("background"));
706
698
  if (resolved.background.feature) {
707
- walk(resolved.background.feature.choices, originRead("background"));
699
+ const feature = resolved.background.feature;
700
+ walk(feature.choices, originRead("background"));
708
701
  }
709
702
  }
703
+ }
704
+ function collectChosenProficiencies(resolved) {
705
+ const out = { skills: [], expertise: [], languages: [], tools: [] };
706
+ visitProficiencyChoices(resolved, (choice, selected) => {
707
+ if (choice.kind !== "select-proficiency") return;
708
+ const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
709
+ const pool = choice.from;
710
+ const valid = pool ? vals.filter((v) => pool.includes(v)) : vals;
711
+ const bucket = choice.domain === "skill" ? choice.expertise ? out.expertise : out.skills : choice.domain === "language" ? out.languages : choice.domain === "tool" ? out.tools : null;
712
+ if (bucket) {
713
+ for (const v of valid) if (!bucket.includes(v)) bucket.push(v);
714
+ }
715
+ });
710
716
  return out;
711
717
  }
718
+ function collectLanguageToolChoiceStatus(resolved) {
719
+ const languages = [];
720
+ const tools = [];
721
+ visitProficiencyChoices(resolved, (choice, selected) => {
722
+ if (choice.kind !== "select-proficiency") return;
723
+ if (choice.domain !== "language" && choice.domain !== "tool") return;
724
+ const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
725
+ const pool = choice.from;
726
+ const valid = pool ? vals.filter((v) => pool.includes(v)) : vals;
727
+ const status = { id: choice.id, count: choice.count, selected: valid.length };
728
+ (choice.domain === "language" ? languages : tools).push(status);
729
+ });
730
+ return { languages, tools };
731
+ }
712
732
  function collectChosenAbilityPoints(resolved) {
713
733
  var _a, _b, _c;
714
734
  const oc = (_a = resolved.definition.origin_choices) != null ? _a : {};
@@ -1084,6 +1104,7 @@ export {
1084
1104
  wikilinkTailSlug,
1085
1105
  __matchesFilterForTest,
1086
1106
  collectChosenProficiencies,
1107
+ collectLanguageToolChoiceStatus,
1087
1108
  collectChosenAbilityPoints,
1088
1109
  buildDecisionLedger
1089
1110
  };
package/dist/index.js CHANGED
@@ -82,9 +82,9 @@ import "./chunk-HCP3GMHS.js";
82
82
  import "./chunk-XJNECUPT.js";
83
83
  import {
84
84
  backgroundCodec
85
- } from "./chunk-VTYUBINW.js";
86
- import "./chunk-DZXSWSBP.js";
87
- import "./chunk-TUIFSVLI.js";
85
+ } from "./chunk-BIK4DFWK.js";
86
+ import "./chunk-5ZBE4CWV.js";
87
+ import "./chunk-SE7AZDR3.js";
88
88
  import "./chunk-3DZ74AKK.js";
89
89
  import "./chunk-NXSWH3AE.js";
90
90
  import "./chunk-NXR3BD2V.js";
@@ -137665,7 +137665,14 @@ var background_2024_default = [
137665
137665
  ]
137666
137666
  }
137667
137667
  ],
137668
- language_proficiencies: [],
137668
+ language_proficiencies: [
137669
+ {
137670
+ kind: "fixed",
137671
+ languages: [
137672
+ "common"
137673
+ ]
137674
+ }
137675
+ ],
137669
137676
  equipment: [
137670
137677
  {
137671
137678
  kind: "choice",
@@ -137713,6 +137720,12 @@ var background_2024_default = [
137713
137720
  "wis",
137714
137721
  "cha"
137715
137722
  ]
137723
+ },
137724
+ {
137725
+ kind: "select-proficiency",
137726
+ id: "languages",
137727
+ count: 2,
137728
+ domain: "language"
137716
137729
  }
137717
137730
  ]
137718
137731
  },
@@ -137734,7 +137747,14 @@ var background_2024_default = [
137734
137747
  ]
137735
137748
  }
137736
137749
  ],
137737
- language_proficiencies: [],
137750
+ language_proficiencies: [
137751
+ {
137752
+ kind: "fixed",
137753
+ languages: [
137754
+ "common"
137755
+ ]
137756
+ }
137757
+ ],
137738
137758
  equipment: [
137739
137759
  {
137740
137760
  kind: "choice",
@@ -137786,6 +137806,12 @@ var background_2024_default = [
137786
137806
  "con",
137787
137807
  "int"
137788
137808
  ]
137809
+ },
137810
+ {
137811
+ kind: "select-proficiency",
137812
+ id: "languages",
137813
+ count: 2,
137814
+ domain: "language"
137789
137815
  }
137790
137816
  ]
137791
137817
  },
@@ -137807,7 +137833,14 @@ var background_2024_default = [
137807
137833
  ]
137808
137834
  }
137809
137835
  ],
137810
- language_proficiencies: [],
137836
+ language_proficiencies: [
137837
+ {
137838
+ kind: "fixed",
137839
+ languages: [
137840
+ "common"
137841
+ ]
137842
+ }
137843
+ ],
137811
137844
  equipment: [
137812
137845
  {
137813
137846
  kind: "choice",
@@ -137858,6 +137891,12 @@ var background_2024_default = [
137858
137891
  "int",
137859
137892
  "wis"
137860
137893
  ]
137894
+ },
137895
+ {
137896
+ kind: "select-proficiency",
137897
+ id: "languages",
137898
+ count: 2,
137899
+ domain: "language"
137861
137900
  }
137862
137901
  ]
137863
137902
  },
@@ -137871,15 +137910,15 @@ var background_2024_default = [
137871
137910
  "athletics",
137872
137911
  "intimidation"
137873
137912
  ],
137874
- tool_proficiencies: [
137913
+ tool_proficiencies: [],
137914
+ language_proficiencies: [
137875
137915
  {
137876
137916
  kind: "fixed",
137877
- items: [
137878
- "choose-one-kind-of-gaming-set"
137917
+ languages: [
137918
+ "common"
137879
137919
  ]
137880
137920
  }
137881
137921
  ],
137882
- language_proficiencies: [],
137883
137922
  equipment: [
137884
137923
  {
137885
137924
  kind: "choice",
@@ -137939,6 +137978,12 @@ var background_2024_default = [
137939
137978
  id: "tool",
137940
137979
  count: 1,
137941
137980
  domain: "tool"
137981
+ },
137982
+ {
137983
+ kind: "select-proficiency",
137984
+ id: "languages",
137985
+ count: 2,
137986
+ domain: "language"
137942
137987
  }
137943
137988
  ]
137944
137989
  }
@@ -137961,7 +138006,10 @@ var race_2014_default = [
137961
138006
  age: "Young dragonborn grow quickly. They walk hours after hatching, attain the size and development of a 10-year-old human child by the age of 3, and reach adulthood by 15. They live to be around 80.",
137962
138007
  alignment: "Dragonborn tend to extremes, making a conscious choice for one side or the other in the cosmic war between good and evil. Most dragonborn are good, but those who side with evil can be terrible villains.",
137963
138008
  languages: {
137964
- fixed: []
138009
+ fixed: [
138010
+ "common",
138011
+ "draconic"
138012
+ ]
137965
138013
  },
137966
138014
  variant_label: "base",
137967
138015
  traits: [
@@ -138148,7 +138196,10 @@ var race_2014_default = [
138148
138196
  age: "Dwarves mature at the same rate as humans, but they're considered young until they reach the age of 50. On average, they live about 350 years.",
138149
138197
  alignment: "Most dwarves are lawful, believing firmly in the benefits of a well-ordered society. They tend toward good as well, with a strong sense of fair play and a belief that everyone deserves to share in the benefits of a just order.",
138150
138198
  languages: {
138151
- fixed: []
138199
+ fixed: [
138200
+ "common",
138201
+ "dwarvish"
138202
+ ]
138152
138203
  },
138153
138204
  variant_label: "base",
138154
138205
  traits: [
@@ -138229,7 +138280,10 @@ var race_2014_default = [
138229
138280
  age: "Although elves reach physical maturity at about the same age as humans, the elven understanding of adulthood goes beyond physical growth to encompass worldly experience. An elf typically claims adulthood and an adult name around the age of 100 and can live to be 750 years old.",
138230
138281
  alignment: "Elves love freedom, variety, and self-\xADexpression, so they lean strongly toward the gentler aspects of chaos. They value and protect others\u2019 freedom as well as their own, and they are more often good than not.",
138231
138282
  languages: {
138232
- fixed: []
138283
+ fixed: [
138284
+ "common",
138285
+ "elvish"
138286
+ ]
138233
138287
  },
138234
138288
  variant_label: "base",
138235
138289
  traits: [
@@ -138292,7 +138346,10 @@ var race_2014_default = [
138292
138346
  age: "Gnomes mature at the same rate humans do, and most are expected to settle down into an adult life by around age 40. They can live 350 to almost 500 years.",
138293
138347
  alignment: "Gnomes are most often good. Those who tend toward law are sages, engineers, researchers, scholars, investigators, or inventors. Those who tend toward chaos are minstrels, tricksters, wanderers, or fanciful jewelers. Gnomes are good-hearted, and even the tricksters among them are more playful than vicious.",
138294
138348
  languages: {
138295
- fixed: []
138349
+ fixed: [
138350
+ "common",
138351
+ "gnomish"
138352
+ ]
138296
138353
  },
138297
138354
  variant_label: "base",
138298
138355
  traits: [
@@ -138347,7 +138404,10 @@ var race_2014_default = [
138347
138404
  age: "Half-elves mature at the same rate humans do and reach adulthood around the age of 20. They live much longer than humans, however, often exceeding 180 years.",
138348
138405
  alignment: "Half-elves share the chaotic bent of their elven heritage. They value both personal freedom and creative expression, demonstrating neither love of leaders nor desire for followers. They chafe at rules, resent others' demands, and sometimes prove unreliable, or at least unpredictable.",
138349
138406
  languages: {
138350
- fixed: []
138407
+ fixed: [
138408
+ "common",
138409
+ "elvish"
138410
+ ]
138351
138411
  },
138352
138412
  variant_label: "base",
138353
138413
  traits: [
@@ -138436,7 +138496,10 @@ var race_2014_default = [
138436
138496
  age: "Half-orcs mature a little faster than humans, reaching adulthood around age 14. They age noticeably faster and rarely live longer than 75 years.",
138437
138497
  alignment: "Half-orcs inherit a tendency toward chaos from their orc parents and are not strongly inclined toward good. Half-orcs raised among orcs and willing to live out their lives among them are usually evil.",
138438
138498
  languages: {
138439
- fixed: []
138499
+ fixed: [
138500
+ "common",
138501
+ "orc"
138502
+ ]
138440
138503
  },
138441
138504
  variant_label: "base",
138442
138505
  traits: [
@@ -138507,7 +138570,10 @@ var race_2014_default = [
138507
138570
  age: "A halfling reaches adulthood at the age of 20 and generally lives into the middle of his or her second century.",
138508
138571
  alignment: "Most halflings are lawful good. As a rule, they are good-hearted and kind, hate to see others in pain, and have no tolerance for oppression. They are also very orderly and traditional, leaning heavily on the support of their community and the comfort of their old ways.",
138509
138572
  languages: {
138510
- fixed: []
138573
+ fixed: [
138574
+ "common",
138575
+ "halfling"
138576
+ ]
138511
138577
  },
138512
138578
  variant_label: "base",
138513
138579
  traits: [
@@ -138637,7 +138703,9 @@ var race_2014_default = [
138637
138703
  age: "Humans reach adulthood in their late teens and live less than a century.",
138638
138704
  alignment: "Humans tend toward no particular alignment. The best and the worst are found among them.",
138639
138705
  languages: {
138640
- fixed: []
138706
+ fixed: [
138707
+ "common"
138708
+ ]
138641
138709
  },
138642
138710
  variant_label: "base",
138643
138711
  traits: [
@@ -138752,7 +138820,10 @@ var race_2014_default = [
138752
138820
  age: "Tieflings mature at the same rate as humans but live a few years longer.",
138753
138821
  alignment: "Tieflings might not have an innate tendency toward evil, but many of them end up there. Evil or not, an independent nature inclines many tieflings toward a chaotic alignment.",
138754
138822
  languages: {
138755
- fixed: []
138823
+ fixed: [
138824
+ "common",
138825
+ "infernal"
138826
+ ]
138756
138827
  },
138757
138828
  variant_label: "base",
138758
138829
  traits: [
@@ -14,8 +14,8 @@ export declare const itemInputSchema: z.ZodObject<{
14
14
  "wondrous item": "wondrous item";
15
15
  }>;
16
16
  rarity: z.ZodEnum<{
17
- legendary: "legendary";
18
17
  common: "common";
18
+ legendary: "legendary";
19
19
  uncommon: "uncommon";
20
20
  rare: "rare";
21
21
  "very rare": "very rare";
@@ -80,6 +80,19 @@ export declare function collectChosenProficiencies(resolved: ResolvedCharacter):
80
80
  languages: string[];
81
81
  tools: string[];
82
82
  };
83
+ export interface ChoiceStatus {
84
+ id: string;
85
+ count: number;
86
+ selected: number;
87
+ }
88
+ /** Per-choice status for language/tool select-proficiency decisions, using the
89
+ * SAME walk as collectChosenProficiencies so required/selected counts cannot
90
+ * diverge from the picks. `selected` counts persisted picks validated against
91
+ * the choice's `from` pool (stale slugs ignored, mirroring the pick fold). */
92
+ export declare function collectLanguageToolChoiceStatus(resolved: ResolvedCharacter): {
93
+ languages: ChoiceStatus[];
94
+ tools: ChoiceStatus[];
95
+ };
83
96
  export interface OriginAbilityPoints {
84
97
  race: Partial<Record<Ability, number>>;
85
98
  background: Partial<Record<Ability, number>>;
@@ -3,10 +3,11 @@ import {
3
3
  buildDecisionLedger,
4
4
  collectChosenAbilityPoints,
5
5
  collectChosenProficiencies,
6
+ collectLanguageToolChoiceStatus,
6
7
  wikilinkTailSlug
7
- } from "../chunk-5MZSB6OM.js";
8
- import "../chunk-BH6AMZVY.js";
9
- import "../chunk-M2EMFD6S.js";
8
+ } from "../chunk-TNUFPIXI.js";
9
+ import "../chunk-7TEK5KK7.js";
10
+ import "../chunk-6IRYTM5V.js";
10
11
  import "../chunk-5XUXXAQJ.js";
11
12
  import "../chunk-JEYQU3JT.js";
12
13
  import "../chunk-N6N74K6X.js";
@@ -20,5 +21,6 @@ export {
20
21
  buildDecisionLedger,
21
22
  collectChosenAbilityPoints,
22
23
  collectChosenProficiencies,
24
+ collectLanguageToolChoiceStatus,
23
25
  wikilinkTailSlug
24
26
  };
@@ -2,8 +2,9 @@ import {
2
2
  computeAppliedBonuses,
3
3
  computeSlotsAndAttacks,
4
4
  emptyAppliedBonuses
5
- } from "../chunk-EMOORSQ7.js";
5
+ } from "../chunk-D3CLOBE3.js";
6
6
  import "../chunk-353WXS6S.js";
7
+ import "../chunk-O5EYMKFH.js";
7
8
  import "../chunk-N6N74K6X.js";
8
9
  import "../chunk-LWFTHJIE.js";
9
10
  import "../chunk-DNAJRMNN.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  characterSchema
3
- } from "../chunk-A4WENOUQ.js";
3
+ } from "../chunk-TN6ZLTNU.js";
4
4
  import "../chunk-TZK36BU7.js";
5
5
 
6
6
  // src/pc/pc.parser.ts
@@ -2,9 +2,9 @@ import {
2
2
  levelPrereqMax,
3
3
  resolveAllPools,
4
4
  resolvePool
5
- } from "../chunk-5MZSB6OM.js";
6
- import "../chunk-BH6AMZVY.js";
7
- import "../chunk-M2EMFD6S.js";
5
+ } from "../chunk-TNUFPIXI.js";
6
+ import "../chunk-7TEK5KK7.js";
7
+ import "../chunk-6IRYTM5V.js";
8
8
  import "../chunk-5XUXXAQJ.js";
9
9
  import "../chunk-JEYQU3JT.js";
10
10
  import "../chunk-N6N74K6X.js";
@@ -1,8 +1,39 @@
1
1
  import type { ResolvedCharacter } from "./pc.types.js";
2
+ import { type ChoiceStatus } from "./pc.decision-engine.js";
2
3
  export interface ProficiencyAggregate {
3
4
  armor: string[];
4
5
  weapons: string[];
5
6
  tools: string[];
6
7
  languages: string[];
8
+ /** Unresolved language/tool decisions as "choose N" placeholders (N = count -
9
+ * selected, only when > 0). Additive; consumed by the plugin proficiency panel. */
10
+ choices: {
11
+ languages: string[];
12
+ tools: string[];
13
+ };
7
14
  }
15
+ /** Normalized intermediate of every proficiency/language source the DISPLAY path
16
+ * reads, in one place. Only the class branch is a live proficiency source today
17
+ * (race/bg/feat `.proficiencies` object fields do not exist on those entities);
18
+ * race languages, bg tool/lang, and feat grants read their real fields. Chosen
19
+ * picks + per-choice status come from the single decision walk in
20
+ * pc.decision-engine so display can never diverge from the persisted picks. */
21
+ export interface ProficiencySources {
22
+ classArmor: string[];
23
+ classWeaponFixed: string[];
24
+ classWeaponCategories: string[];
25
+ classToolFixed: string[];
26
+ raceLangFixed: string[];
27
+ bgToolFixed: string[];
28
+ bgLangFixed: string[];
29
+ featArmor: string[];
30
+ featWeapons: string[];
31
+ featTools: string[];
32
+ featLanguages: string[];
33
+ chosenLanguages: string[];
34
+ chosenTools: string[];
35
+ languageChoices: ChoiceStatus[];
36
+ toolChoices: ChoiceStatus[];
37
+ }
38
+ export declare function collectProficiencySources(resolved: ResolvedCharacter): ProficiencySources;
8
39
  export declare function aggregateProficiencies(resolved: ResolvedCharacter): ProficiencyAggregate;
@@ -1,47 +1,120 @@
1
+ import {
2
+ collectChosenProficiencies,
3
+ collectLanguageToolChoiceStatus
4
+ } from "../chunk-TNUFPIXI.js";
5
+ import "../chunk-7TEK5KK7.js";
6
+ import "../chunk-6IRYTM5V.js";
7
+ import "../chunk-5XUXXAQJ.js";
8
+ import "../chunk-JEYQU3JT.js";
9
+ import "../chunk-N6N74K6X.js";
10
+ import "../chunk-4LZJXJJS.js";
11
+ import "../chunk-LV4SNWZQ.js";
12
+ import "../chunk-VTTA2ALZ.js";
13
+ import "../chunk-NAEPSZAM.js";
14
+ import "../chunk-WP2CX2JO.js";
15
+
1
16
  // src/pc/pc.proficiencies.ts
2
- function aggregateProficiencies(resolved) {
3
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
4
- const armor = /* @__PURE__ */ new Set();
5
- const weapons = /* @__PURE__ */ new Set();
6
- const tools = /* @__PURE__ */ new Set();
7
- const languages = /* @__PURE__ */ new Set();
17
+ function collectProficiencySources(resolved) {
18
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
19
+ const classArmor = [];
20
+ const classWeaponFixed = [];
21
+ const classWeaponCategories = [];
22
+ const classToolFixed = [];
8
23
  for (const c of resolved.classes) {
9
24
  const prof = (_a = c.entity) == null ? void 0 : _a.proficiencies;
10
25
  if (!prof) continue;
11
- for (const a of (_b = prof.armor) != null ? _b : []) armor.add(prettyName(a));
12
- if ((_c = prof.weapons) == null ? void 0 : _c.fixed) for (const w of prof.weapons.fixed) weapons.add(prettyName(w));
13
- if ((_d = prof.weapons) == null ? void 0 : _d.categories) for (const w of prof.weapons.categories) weapons.add(prettyName(w));
14
- if ((_e = prof.tools) == null ? void 0 : _e.fixed) for (const t of prof.tools.fixed) tools.add(prettyName(t));
26
+ for (const a of (_b = prof.armor) != null ? _b : []) classArmor.push(a);
27
+ for (const w of (_d = (_c = prof.weapons) == null ? void 0 : _c.fixed) != null ? _d : []) classWeaponFixed.push(w);
28
+ for (const w of (_f = (_e = prof.weapons) == null ? void 0 : _e.categories) != null ? _f : []) classWeaponCategories.push(w);
29
+ for (const t of (_h = (_g = prof.tools) == null ? void 0 : _g.fixed) != null ? _h : []) classToolFixed.push(t);
15
30
  }
16
- const raceLangs = (_f = resolved.race) == null ? void 0 : _f.languages;
17
- if (raceLangs == null ? void 0 : raceLangs.fixed) for (const l of raceLangs.fixed) languages.add(prettyName(l));
31
+ const raceLangFixed = [...(_k = (_j = (_i = resolved.race) == null ? void 0 : _i.languages) == null ? void 0 : _j.fixed) != null ? _k : []];
32
+ const bgToolFixed = [];
33
+ const bgLangFixed = [];
18
34
  const bg = resolved.background;
19
35
  if (bg) {
20
- for (const entry of (_g = bg.tool_proficiencies) != null ? _g : []) {
21
- if (entry.kind === "fixed") for (const t of entry.items) tools.add(prettyName(t));
36
+ for (const entry of (_l = bg.tool_proficiencies) != null ? _l : []) {
37
+ if (entry.kind === "fixed") bgToolFixed.push(...entry.items);
22
38
  }
23
- for (const entry of (_h = bg.language_proficiencies) != null ? _h : []) {
24
- if (entry.kind === "fixed") for (const l of entry.languages) languages.add(prettyName(l));
39
+ for (const entry of (_m = bg.language_proficiencies) != null ? _m : []) {
40
+ if (entry.kind === "fixed") bgLangFixed.push(...entry.languages);
25
41
  }
26
42
  }
43
+ const featArmor = [];
44
+ const featWeapons = [];
45
+ const featTools = [];
46
+ const featLanguages = [];
27
47
  for (const f of resolved.feats) {
28
- const grants = f.proficiencies;
48
+ const featView = f;
49
+ const grants = featView.proficiencies;
29
50
  if (!grants) continue;
30
- for (const a of (_i = grants.armor) != null ? _i : []) armor.add(prettyName(a));
31
- for (const w of (_j = grants.weapons) != null ? _j : []) weapons.add(prettyName(w));
32
- for (const t of (_k = grants.tools) != null ? _k : []) tools.add(prettyName(t));
33
- for (const l of (_l = grants.languages) != null ? _l : []) languages.add(prettyName(l));
51
+ featArmor.push(...(_n = grants.armor) != null ? _n : []);
52
+ featWeapons.push(...(_o = grants.weapons) != null ? _o : []);
53
+ featTools.push(...(_p = grants.tools) != null ? _p : []);
54
+ featLanguages.push(...(_q = grants.languages) != null ? _q : []);
34
55
  }
56
+ const chosen = collectChosenProficiencies(resolved);
57
+ const status = collectLanguageToolChoiceStatus(resolved);
58
+ return {
59
+ classArmor,
60
+ classWeaponFixed,
61
+ classWeaponCategories,
62
+ classToolFixed,
63
+ raceLangFixed,
64
+ bgToolFixed,
65
+ bgLangFixed,
66
+ featArmor,
67
+ featWeapons,
68
+ featTools,
69
+ featLanguages,
70
+ chosenLanguages: chosen.languages,
71
+ chosenTools: chosen.tools,
72
+ languageChoices: status.languages,
73
+ toolChoices: status.tools
74
+ };
75
+ }
76
+ function aggregateProficiencies(resolved) {
77
+ const src = collectProficiencySources(resolved);
78
+ const armor = /* @__PURE__ */ new Set();
79
+ const weapons = /* @__PURE__ */ new Set();
80
+ const tools = /* @__PURE__ */ new Set();
81
+ const languages = /* @__PURE__ */ new Set();
82
+ for (const a of src.classArmor) armor.add(prettyName(a));
83
+ for (const w of src.classWeaponFixed) weapons.add(prettyName(w));
84
+ for (const w of src.classWeaponCategories) weapons.add(prettyName(w));
85
+ for (const t of src.classToolFixed) tools.add(prettyName(t));
86
+ for (const l of src.raceLangFixed) languages.add(prettyName(l));
87
+ for (const t of src.bgToolFixed) tools.add(prettyName(t));
88
+ for (const l of src.bgLangFixed) languages.add(prettyName(l));
89
+ for (const a of src.featArmor) armor.add(prettyName(a));
90
+ for (const w of src.featWeapons) weapons.add(prettyName(w));
91
+ for (const t of src.featTools) tools.add(prettyName(t));
92
+ for (const l of src.featLanguages) languages.add(prettyName(l));
93
+ for (const l of src.chosenLanguages) languages.add(prettyName(l));
94
+ for (const t of src.chosenTools) tools.add(prettyName(t));
35
95
  return {
36
96
  armor: [...armor].sort(),
37
97
  weapons: [...weapons].sort(),
38
98
  tools: [...tools].sort(),
39
- languages: [...languages].sort()
99
+ languages: [...languages].sort(),
100
+ choices: {
101
+ languages: placeholders(src.languageChoices),
102
+ tools: placeholders(src.toolChoices)
103
+ }
40
104
  };
41
105
  }
106
+ function placeholders(choices) {
107
+ const out = [];
108
+ for (const c of choices) {
109
+ const remaining = c.count - c.selected;
110
+ if (remaining > 0) out.push("choose " + remaining);
111
+ }
112
+ return out;
113
+ }
42
114
  function prettyName(slug) {
43
115
  return slug.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
44
116
  }
45
117
  export {
46
- aggregateProficiencies
118
+ aggregateProficiencies,
119
+ collectProficiencySources
47
120
  };
@@ -0,0 +1,5 @@
1
+ /** Normalized token-set key for matching class weapon DISPLAY names ("hand crossbows")
2
+ * against weapon-entity names ("Crossbow, hand"). EXACT equality on the key; verified
3
+ * collision-free across the SRD weapon set. Strip-"s" singularizer does not handle
4
+ * irregular plurals; no SRD weapon triggers that. */
5
+ export declare const normKey: (s: string) => string;
@@ -1,6 +1,10 @@
1
+ import {
2
+ normKey
3
+ } from "../chunk-O5EYMKFH.js";
4
+
1
5
  // src/pc/pc.proficiency-query.ts
2
6
  function isProficientWithWeapon(weapon, profs) {
3
- if (profs.weapons.specific.includes(weapon.slug)) return true;
7
+ if (profs.weapons.specific.some((e) => e === weapon.slug || normKey(e) === normKey(weapon.name))) return true;
4
8
  const baseCategory = weapon.category.split("-")[0];
5
9
  return profs.weapons.categories.includes(baseCategory);
6
10
  }
@@ -4,21 +4,23 @@ import {
4
4
  import {
5
5
  collectChosenAbilityPoints,
6
6
  collectChosenProficiencies
7
- } from "../chunk-5MZSB6OM.js";
8
- import "../chunk-BH6AMZVY.js";
7
+ } from "../chunk-TNUFPIXI.js";
8
+ import "../chunk-7TEK5KK7.js";
9
9
  import {
10
10
  computeSpellLimits,
11
11
  deriveSpellSlots,
12
+ effectiveSpellcastingAbility,
12
13
  resolveSpellcasting
13
- } from "../chunk-M2EMFD6S.js";
14
+ } from "../chunk-6IRYTM5V.js";
14
15
  import "../chunk-5XUXXAQJ.js";
15
16
  import "../chunk-JEYQU3JT.js";
16
17
  import {
17
18
  computeAppliedBonuses,
18
19
  computeSlotsAndAttacks,
19
20
  emptyAppliedBonuses
20
- } from "../chunk-EMOORSQ7.js";
21
+ } from "../chunk-D3CLOBE3.js";
21
22
  import "../chunk-353WXS6S.js";
23
+ import "../chunk-O5EYMKFH.js";
22
24
  import "../chunk-N6N74K6X.js";
23
25
  import {
24
26
  computeFeatureEffects
@@ -308,12 +310,18 @@ function computeAbilityScores(resolved, overrides) {
308
310
  }
309
311
  return out;
310
312
  }
311
- function normalizeProfList(input) {
313
+ function normalizeArmorProf(input) {
312
314
  var _a, _b;
313
315
  if (!input) return { categories: [], specific: [] };
314
- if (Array.isArray(input)) return { categories: [], specific: input };
316
+ if (Array.isArray(input)) return { categories: input, specific: [] };
315
317
  return { categories: (_a = input.categories) != null ? _a : [], specific: (_b = input.specific) != null ? _b : [] };
316
318
  }
319
+ function normalizeWeaponToolProf(input) {
320
+ var _a, _b, _c;
321
+ if (!input) return { categories: [], specific: [] };
322
+ if (Array.isArray(input)) return { categories: [], specific: input };
323
+ return { categories: (_a = input.categories) != null ? _a : [], specific: ((_b = input.fixed) != null ? _b : []).concat((_c = input.specific) != null ? _c : []) };
324
+ }
317
325
  function mergeInto(target, source) {
318
326
  for (const c of source.categories) if (!target.categories.includes(c)) target.categories.push(c);
319
327
  for (const s of source.specific) if (!target.specific.includes(s)) target.specific.push(s);
@@ -340,31 +348,31 @@ function computeProficiencies(resolved) {
340
348
  for (const c of resolved.classes) {
341
349
  const p = (_a = c.entity) == null ? void 0 : _a.proficiencies;
342
350
  if (!p) continue;
343
- mergeInto(armor, normalizeProfList(p.armor));
344
- mergeInto(weapons, normalizeProfList(p.weapons));
345
- mergeInto(tools, normalizeProfList(p.tools));
351
+ mergeInto(armor, normalizeArmorProf(p.armor));
352
+ mergeInto(weapons, normalizeWeaponToolProf(p.weapons));
353
+ mergeInto(tools, normalizeWeaponToolProf(p.tools));
346
354
  (_b = p.languages) == null ? void 0 : _b.forEach((l) => languages.add(l));
347
355
  }
348
356
  const racePr = (_c = resolved.race) == null ? void 0 : _c.proficiencies;
349
357
  if (racePr) {
350
- mergeInto(armor, normalizeProfList(racePr.armor));
351
- mergeInto(weapons, normalizeProfList(racePr.weapons));
352
- mergeInto(tools, normalizeProfList(racePr.tools));
358
+ mergeInto(armor, normalizeArmorProf(racePr.armor));
359
+ mergeInto(weapons, normalizeWeaponToolProf(racePr.weapons));
360
+ mergeInto(tools, normalizeWeaponToolProf(racePr.tools));
353
361
  (_d = racePr.languages) == null ? void 0 : _d.forEach((l) => languages.add(l));
354
362
  }
355
363
  const bgPr = (_e = resolved.background) == null ? void 0 : _e.proficiencies;
356
364
  if (bgPr) {
357
- mergeInto(armor, normalizeProfList(bgPr.armor));
358
- mergeInto(weapons, normalizeProfList(bgPr.weapons));
359
- mergeInto(tools, normalizeProfList(bgPr.tools));
365
+ mergeInto(armor, normalizeArmorProf(bgPr.armor));
366
+ mergeInto(weapons, normalizeWeaponToolProf(bgPr.weapons));
367
+ mergeInto(tools, normalizeWeaponToolProf(bgPr.tools));
360
368
  (_f = bgPr.languages) == null ? void 0 : _f.forEach((l) => languages.add(l));
361
369
  }
362
370
  for (const f of resolved.feats) {
363
371
  const grants = f.grants_proficiency;
364
372
  if (!grants) continue;
365
- mergeInto(armor, normalizeProfList(grants.armor));
366
- mergeInto(weapons, normalizeProfList(grants.weapons));
367
- mergeInto(tools, normalizeProfList(grants.tools));
373
+ mergeInto(armor, normalizeArmorProf(grants.armor));
374
+ mergeInto(weapons, normalizeWeaponToolProf(grants.weapons));
375
+ mergeInto(tools, normalizeWeaponToolProf(grants.tools));
368
376
  (_g = grants.languages) == null ? void 0 : _g.forEach((l) => languages.add(l));
369
377
  }
370
378
  return {
@@ -376,7 +384,7 @@ function computeProficiencies(resolved) {
376
384
  };
377
385
  }
378
386
  function recalc(resolved, registry) {
379
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa;
387
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y;
380
388
  const warnings = [];
381
389
  const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
382
390
  const chosenProfs = collectChosenProficiencies(resolved);
@@ -541,7 +549,7 @@ function recalc(resolved, registry) {
541
549
  if (!c.entity) continue;
542
550
  const profile = resolveSpellcasting(c);
543
551
  if (profile) {
544
- primaryCasterAbility = profile.ability;
552
+ primaryCasterAbility = effectiveSpellcastingAbility(c.entity.slug, profile.ability, overrides);
545
553
  break;
546
554
  }
547
555
  }
@@ -596,19 +604,21 @@ function recalc(resolved, registry) {
596
604
  if (!c.entity) continue;
597
605
  const profile = resolveSpellcasting(c);
598
606
  if (!profile) continue;
599
- const dc = saveDC(scores[profile.ability], proficiencyBonus) + applied.spell_save_dc;
600
- const atk = attackBonus(scores[profile.ability], proficiencyBonus) + applied.spell_attack;
607
+ const ab = effectiveSpellcastingAbility(c.entity.slug, profile.ability, overrides);
608
+ const dc = saveDC(scores[ab], proficiencyBonus) + applied.spell_save_dc;
609
+ const atk = attackBonus(scores[ab], proficiencyBonus) + applied.spell_attack;
601
610
  spellcastingClasses.push({
602
611
  classSlug: c.entity.slug,
603
612
  className: c.entity.name,
604
- ability: profile.ability,
605
- saveDC: (_M = (_L = overrides.spellcasting) == null ? void 0 : _L.saveDC) != null ? _M : dc,
606
- attackBonus: (_O = (_N = overrides.spellcasting) == null ? void 0 : _N.attackBonus) != null ? _O : atk,
613
+ ability: ab,
614
+ defaultAbility: profile.ability,
615
+ saveDC: dc,
616
+ attackBonus: atk,
607
617
  casterType: profile.casterType,
608
618
  preparation: profile.preparation
609
619
  });
610
620
  slotInputs.push({ casterType: profile.casterType, level: c.level });
611
- limitInputs.push({ classSlug: c.entity.slug, level: c.level, profile, abilityScore: scores[profile.ability] });
621
+ limitInputs.push({ classSlug: c.entity.slug, level: c.level, profile, abilityScore: scores[ab] });
612
622
  }
613
623
  const spellcasting = spellcastingClasses.length > 0 ? {
614
624
  ability: spellcastingClasses[0].ability,
@@ -628,20 +638,20 @@ function recalc(resolved, registry) {
628
638
  const spellLimits = computeSpellLimits(limitInputs);
629
639
  const defenses = {
630
640
  resistances: dedupeDefenseList([
631
- (_Q = (_P = resolved.definition.defenses) == null ? void 0 : _P.resistances) != null ? _Q : [],
641
+ (_M = (_L = resolved.definition.defenses) == null ? void 0 : _L.resistances) != null ? _M : [],
632
642
  applied.defenses.resistances,
633
643
  featureEffects.resistances
634
644
  ]),
635
645
  immunities: dedupeDefenseList([
636
- (_S = (_R = resolved.definition.defenses) == null ? void 0 : _R.immunities) != null ? _S : [],
646
+ (_O = (_N = resolved.definition.defenses) == null ? void 0 : _N.immunities) != null ? _O : [],
637
647
  applied.defenses.immunities
638
648
  ]),
639
649
  vulnerabilities: dedupeDefenseList([
640
- (_U = (_T = resolved.definition.defenses) == null ? void 0 : _T.vulnerabilities) != null ? _U : [],
650
+ (_Q = (_P = resolved.definition.defenses) == null ? void 0 : _P.vulnerabilities) != null ? _Q : [],
641
651
  applied.defenses.vulnerabilities
642
652
  ]),
643
653
  condition_immunities: dedupeDefenseList([
644
- (_W = (_V = resolved.definition.defenses) == null ? void 0 : _V.condition_immunities) != null ? _W : [],
654
+ (_S = (_R = resolved.definition.defenses) == null ? void 0 : _R.condition_immunities) != null ? _S : [],
645
655
  applied.defenses.condition_immunities,
646
656
  featureEffects.condition_immunities
647
657
  ])
@@ -685,7 +695,7 @@ function recalc(resolved, registry) {
685
695
  // untouched rows keep `critRange`/`attackNotes` ABSENT (not 20 / not []).
686
696
  // crit-range: folded weapon crit threshold, only when an effect lowered it.
687
697
  // attackNotes: reroll-damage / attack-rule captions, only when non-empty.
688
- attacks: ((_X = derivedEquipment == null ? void 0 : derivedEquipment.attacks) != null ? _X : []).map((a) => {
698
+ attacks: ((_T = derivedEquipment == null ? void 0 : derivedEquipment.attacks) != null ? _T : []).map((a) => {
689
699
  var _a2;
690
700
  const riders = [...(_a2 = a.damageRiders) != null ? _a2 : [], ...featureEffects.damageBonuses];
691
701
  return {
@@ -696,10 +706,10 @@ function recalc(resolved, registry) {
696
706
  ...riders.length ? { damageRiders: riders } : {}
697
707
  };
698
708
  }),
699
- equippedSlots: (_Y = derivedEquipment == null ? void 0 : derivedEquipment.equippedSlots) != null ? _Y : {},
700
- carriedWeight: (_Z = derivedEquipment == null ? void 0 : derivedEquipment.carriedWeight) != null ? _Z : 0,
701
- attunementUsed: (__ = derivedEquipment == null ? void 0 : derivedEquipment.attunementUsed) != null ? __ : 0,
702
- attunementLimit: (_aa = derivedEquipment == null ? void 0 : derivedEquipment.attunementLimit) != null ? _aa : (_$ = overrides.attunement_limit) != null ? _$ : 3,
709
+ equippedSlots: (_U = derivedEquipment == null ? void 0 : derivedEquipment.equippedSlots) != null ? _U : {},
710
+ carriedWeight: (_V = derivedEquipment == null ? void 0 : derivedEquipment.carriedWeight) != null ? _V : 0,
711
+ attunementUsed: (_W = derivedEquipment == null ? void 0 : derivedEquipment.attunementUsed) != null ? _W : 0,
712
+ attunementLimit: (_Y = derivedEquipment == null ? void 0 : derivedEquipment.attunementLimit) != null ? _Y : (_X = overrides.attunement_limit) != null ? _X : 3,
703
713
  conditionEffects,
704
714
  rollModifiers: featureEffects.rollModifiers
705
715
  };
@@ -9,9 +9,9 @@ import {
9
9
  dedupeResolvedSpells,
10
10
  resolveOriginFeat,
11
11
  stripSlug
12
- } from "../chunk-5MZSB6OM.js";
13
- import "../chunk-BH6AMZVY.js";
14
- import "../chunk-M2EMFD6S.js";
12
+ } from "../chunk-TNUFPIXI.js";
13
+ import "../chunk-7TEK5KK7.js";
14
+ import "../chunk-6IRYTM5V.js";
15
15
  import "../chunk-5XUXXAQJ.js";
16
16
  import "../chunk-JEYQU3JT.js";
17
17
  import "../chunk-N6N74K6X.js";
@@ -227,10 +227,14 @@ export declare const characterSchema: z.ZodObject<{
227
227
  ac: z.ZodOptional<z.ZodNumber>;
228
228
  speed: z.ZodOptional<z.ZodNumber>;
229
229
  initiative: z.ZodOptional<z.ZodNumber>;
230
- spellcasting: z.ZodOptional<z.ZodObject<{
231
- saveDC: z.ZodOptional<z.ZodNumber>;
232
- attackBonus: z.ZodOptional<z.ZodNumber>;
233
- }, z.core.$strip>>;
230
+ spellcasting_ability_by_class: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
231
+ str: "str";
232
+ dex: "dex";
233
+ con: "con";
234
+ int: "int";
235
+ wis: "wis";
236
+ cha: "cha";
237
+ }>>>;
234
238
  spellcasting_ability: z.ZodOptional<z.ZodEnum<{
235
239
  str: "str";
236
240
  dex: "dex";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  characterSchema
3
- } from "../chunk-A4WENOUQ.js";
3
+ } from "../chunk-TN6ZLTNU.js";
4
4
  import "../chunk-TZK36BU7.js";
5
5
  export {
6
6
  characterSchema
@@ -1,5 +1,5 @@
1
1
  import type { Ability } from "@archivist-gg/dnd5e";
2
- import type { KnownSpellEntry, ResolvedClass } from "./pc.types.js";
2
+ import type { CharacterOverrides, KnownSpellEntry, ResolvedClass } from "./pc.types.js";
3
3
  import type { CasterType } from "@archivist-gg/dnd5e/class/class.types";
4
4
  export interface SpellcastingProfile {
5
5
  ability: Ability;
@@ -18,6 +18,10 @@ export interface SpellcastingProfile {
18
18
  * non-casters. No hardcoded class knowledge — everything comes from the data.
19
19
  */
20
20
  export declare function resolveSpellcasting(rc: ResolvedClass): SpellcastingProfile | null;
21
+ /** The ability a class actually casts with: a per-class override wins, else the
22
+ * data ability from resolveSpellcasting. `dataAbility` is non-nullable by
23
+ * contract: resolve caster-ness (a non-null SpellcastingProfile) BEFORE calling. */
24
+ export declare function effectiveSpellcastingAbility(classSlug: string, dataAbility: Ability, overrides: CharacterOverrides): Ability;
21
25
  export interface NormalizedKnownSpell {
22
26
  slug: string;
23
27
  classSlug: string | null;
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  computeSpellLimits,
3
3
  deriveSpellSlots,
4
+ effectiveSpellcastingAbility,
4
5
  normalizeKnownSpell,
5
6
  resolveSpellcasting
6
- } from "../chunk-M2EMFD6S.js";
7
+ } from "../chunk-6IRYTM5V.js";
7
8
  import "../chunk-5XUXXAQJ.js";
8
9
  import "../chunk-VTTA2ALZ.js";
9
10
  import "../chunk-NAEPSZAM.js";
@@ -11,6 +12,7 @@ import "../chunk-WP2CX2JO.js";
11
12
  export {
12
13
  computeSpellLimits,
13
14
  deriveSpellSlots,
15
+ effectiveSpellcastingAbility,
14
16
  normalizeKnownSpell,
15
17
  resolveSpellcasting
16
18
  };
@@ -126,10 +126,7 @@ export interface CharacterOverrides {
126
126
  ac?: number;
127
127
  speed?: number;
128
128
  initiative?: number;
129
- spellcasting?: {
130
- saveDC?: number;
131
- attackBonus?: number;
132
- };
129
+ spellcasting_ability_by_class?: Partial<Record<string, Ability>>;
133
130
  /** Character-level spellcasting ability for spells that carry no ability of
134
131
  * their own (e.g. a Spell Scroll cast by a non-caster): the DC-ability fallback
135
132
  * after a per-instance override and the character's own class ability. */
@@ -585,6 +582,7 @@ export interface SpellcastingClassInfo {
585
582
  classSlug: string;
586
583
  className: string;
587
584
  ability: Ability;
585
+ defaultAbility: Ability;
588
586
  saveDC: number;
589
587
  attackBonus: number;
590
588
  casterType: "full" | "half" | "third" | "pact";
@@ -3,6 +3,11 @@ import type { FeatureEffect } from "./feature-effect.js";
3
3
  export type Ability = keyof Abilities;
4
4
  export type SkillSlug = "acrobatics" | "animal-handling" | "arcana" | "athletics" | "deception" | "history" | "insight" | "intimidation" | "investigation" | "medicine" | "nature" | "perception" | "performance" | "persuasion" | "religion" | "sleight-of-hand" | "stealth" | "survival";
5
5
  export declare const ALL_SKILL_SLUGS: SkillSlug[];
6
+ /** The SRD language set: 8 standard + 8 exotic. Authoritative and reused as
7
+ * KNOWN_LANGUAGES by the proficiency aggregate; slugs are edition-agnostic. */
8
+ export declare const STANDARD_LANGUAGES: string[];
9
+ export declare const EXOTIC_LANGUAGES: string[];
10
+ export declare const ALL_LANGUAGES: string[];
6
11
  export type FeatCategory = "origin" | "general" | "fighting-style" | "epic-boon";
7
12
  /** Closed, enumerated registry filter — YAML can never express a query the
8
13
  * engine would apply unsafely. `"self"` resolves against the owning
@@ -1,6 +1,12 @@
1
1
  import {
2
- ALL_SKILL_SLUGS
3
- } from "../chunk-BH6AMZVY.js";
2
+ ALL_LANGUAGES,
3
+ ALL_SKILL_SLUGS,
4
+ EXOTIC_LANGUAGES,
5
+ STANDARD_LANGUAGES
6
+ } from "../chunk-7TEK5KK7.js";
4
7
  export {
5
- ALL_SKILL_SLUGS
8
+ ALL_LANGUAGES,
9
+ ALL_SKILL_SLUGS,
10
+ EXOTIC_LANGUAGES,
11
+ STANDARD_LANGUAGES
6
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archivist-gg/dnd5e",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1,25 +0,0 @@
1
- // src/types/choice.ts
2
- var ALL_SKILL_SLUGS = [
3
- "acrobatics",
4
- "animal-handling",
5
- "arcana",
6
- "athletics",
7
- "deception",
8
- "history",
9
- "insight",
10
- "intimidation",
11
- "investigation",
12
- "medicine",
13
- "nature",
14
- "perception",
15
- "performance",
16
- "persuasion",
17
- "religion",
18
- "sleight-of-hand",
19
- "stealth",
20
- "survival"
21
- ];
22
-
23
- export {
24
- ALL_SKILL_SLUGS
25
- };